Merge pull request #10 from RxSwiftCommunity/mt-LinkingObjects

Adds Linking objects and source docs
This commit is contained in:
Marin Todorov
2016-05-01 20:28:11 -07:00
9 changed files with 1070 additions and 1160 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,132 @@
//
// RxRealmLinkingObjectsTests.swift
// RxRealm
//
// Created by Marin Todorov on 5/1/16.
// Copyright © 2016 CocoaPods. All rights reserved.
//
import XCTest
import RxSwift
import RealmSwift
import RxRealm
import RxTests
class RxRealmLinkingObjectsTests: XCTestCase {
private func realmInMemory(name: String) -> Realm {
var conf = Realm.Configuration()
conf.inMemoryIdentifier = name
return try! Realm(configuration: conf)
}
private func clearRealm(realm: Realm) {
try! realm.write {
realm.deleteAll()
}
}
func testLinkingObjectsType() {
let expectation1 = expectationWithDescription("LinkingObjects<User> first")
let realm = realmInMemory(#function)
clearRealm(realm)
let bag = DisposeBag()
let scheduler = TestScheduler(initialClock: 0)
let observer = scheduler.createObserver(LinkingObjects<User>)
let message = Message("first")
try! realm.write {
realm.add(message)
}
let users$ = message.mentions.asObservable().shareReplay(1)
users$.scan(0, accumulator: {acc, _ in return acc+1})
.filter { $0 == 3 }.map {_ in ()}.subscribeNext(expectation1.fulfill).addDisposableTo(bag)
users$
.subscribe(observer).addDisposableTo(bag)
//interact with Realm here
let user1 = User("user1")
user1.lastMessage = message
delay(0.1) {
try! realm.write {
realm.add(user1)
}
}
delay(0.2) {
try! realm.write {
realm.delete(user1)
}
}
scheduler.start()
waitForExpectationsWithTimeout(0.5) {error in
//do tests here
XCTAssertTrue(error == nil)
XCTAssertEqual(observer.events.count, 3)
XCTAssertEqual(message.mentions.count, 0)
}
}
func testLinkingObjectsTypeChangeset() {
let expectation1 = expectationWithDescription("LinkingObjects<User> first")
let realm = realmInMemory(#function)
clearRealm(realm)
let bag = DisposeBag()
let scheduler = TestScheduler(initialClock: 0)
let observer = scheduler.createObserver(String)
let message = Message("first")
try! realm.write {
realm.add(message)
}
let users$ = message.mentions.asObservableChangeset().shareReplay(1)
users$.scan(0, accumulator: {acc, _ in return acc+1})
.filter { $0 == 3 }.map {_ in ()}.subscribeNext(expectation1.fulfill).addDisposableTo(bag)
users$
.map {linkingObjects, changes in
if let changes = changes {
return "i:\(changes.inserted) d:\(changes.deleted) u:\(changes.updated)"
} else {
return "initial"
}
}
.subscribe(observer).addDisposableTo(bag)
//interact with Realm here
let user1 = User("user1")
user1.lastMessage = message
delay(0.1) {
try! realm.write {
realm.add(user1)
}
}
delay(0.2) {
try! realm.write {
realm.delete(user1)
}
}
scheduler.start()
waitForExpectationsWithTimeout(0.5) {error in
//do tests here
XCTAssertTrue(error == nil)
XCTAssertEqual(observer.events.count, 3)
XCTAssertEqual(observer.events[0].value.element!, "initial")
XCTAssertEqual(observer.events[1].value.element!, "i:[0] d:[] u:[]")
XCTAssertEqual(observer.events[2].value.element!, "i:[] d:[0] u:[]")
}
}
}

View File

@@ -0,0 +1,135 @@
//
// RxRealmListTests.swift
// RxRealm
//
// Created by Marin Todorov on 5/1/16.
// Copyright © 2016 CocoaPods. All rights reserved.
//
//
// RxRealmCollectionsTests.swift
// RxRealm
//
// Created by Marin Todorov on 4/30/16.
// Copyright © 2016 CocoaPods. All rights reserved.
//
import XCTest
import RxSwift
import RealmSwift
import RxRealm
import RxTests
class RxRealmListTests: XCTestCase {
private func realmInMemory(name: String) -> Realm {
var conf = Realm.Configuration()
conf.inMemoryIdentifier = name
return try! Realm(configuration: conf)
}
private func clearRealm(realm: Realm) {
try! realm.write {
realm.deleteAll()
}
}
func testListType() {
let expectation1 = expectationWithDescription("List<User> first")
let realm = realmInMemory(#function)
clearRealm(realm)
let bag = DisposeBag()
let scheduler = TestScheduler(initialClock: 0)
let observer = scheduler.createObserver(List<User>)
let message = Message("first")
try! realm.write {
realm.add(message)
}
let users$ = message.recipients.asObservable().shareReplay(1)
users$.scan(0, accumulator: {acc, _ in return acc+1})
.filter { $0 == 3 }.map {_ in ()}.subscribeNext(expectation1.fulfill).addDisposableTo(bag)
users$
.subscribe(observer).addDisposableTo(bag)
//interact with Realm here
delay(0.1) {
try! realm.write {
message.recipients.append(User("user1"))
}
}
delay(0.2) {
try! realm.write {
message.recipients.removeAtIndex(0)
}
}
scheduler.start()
waitForExpectationsWithTimeout(0.5) {error in
//do tests here
XCTAssertTrue(error == nil)
XCTAssertEqual(observer.events.count, 3)
XCTAssertEqual(message.recipients.count, 0)
}
}
func testLustTypeChangeset() {
let expectation1 = expectationWithDescription("List<User> first")
let realm = realmInMemory(#function)
clearRealm(realm)
let bag = DisposeBag()
let scheduler = TestScheduler(initialClock: 0)
let observer = scheduler.createObserver(String)
let message = Message("first")
try! realm.write {
realm.add(message)
}
let users$ = message.recipients.asObservableChangeset().shareReplay(1)
users$.scan(0, accumulator: {acc, _ in return acc+1})
.filter { $0 == 3 }.map {_ in ()}.subscribeNext(expectation1.fulfill).addDisposableTo(bag)
users$
.map {list, changes in
if let changes = changes {
return "i:\(changes.inserted) d:\(changes.deleted) u:\(changes.updated)"
} else {
return "initial"
}
}
.subscribe(observer).addDisposableTo(bag)
//interact with Realm here
delay(0.1) {
try! realm.write {
message.recipients.append(User("user1"))
}
}
delay(0.2) {
try! realm.write {
message.recipients.removeAtIndex(0)
}
}
scheduler.start()
waitForExpectationsWithTimeout(0.5) {error in
//do tests here
XCTAssertTrue(error == nil)
XCTAssertEqual(observer.events.count, 3)
XCTAssertEqual(observer.events[0].value.element!, "initial")
XCTAssertEqual(observer.events[1].value.element!, "i:[0] d:[] u:[]")
XCTAssertEqual(observer.events[2].value.element!, "i:[] d:[0] u:[]")
}
}
}

View File

@@ -0,0 +1,124 @@
//
// RxRealmCollectionsTests.swift
// RxRealm
//
// Created by Marin Todorov on 4/30/16.
// Copyright © 2016 CocoaPods. All rights reserved.
//
import XCTest
import RxSwift
import RealmSwift
import RxRealm
import RxTests
class RxRealmResultsTests: XCTestCase {
private func realmInMemory(name: String) -> Realm {
var conf = Realm.Configuration()
conf.inMemoryIdentifier = name
return try! Realm(configuration: conf)
}
private func clearRealm(realm: Realm) {
try! realm.write {
realm.deleteAll()
}
}
func testResultsType() {
let expectation1 = expectationWithDescription("Results<Message> first")
let realm = realmInMemory(#function)
clearRealm(realm)
let bag = DisposeBag()
let scheduler = TestScheduler(initialClock: 0)
let observer = scheduler.createObserver(Results<Message>)
let messages$ = realm.objects(Message).asObservable().shareReplay(1)
messages$.scan(0, accumulator: {acc, _ in return acc+1})
.filter { $0 == 4 }.map {_ in ()}.subscribeNext(expectation1.fulfill).addDisposableTo(bag)
messages$
.subscribe(observer).addDisposableTo(bag)
//interact with Realm here
delay(0.1) {
try! realm.write {
realm.add(Message("first"))
}
}
delay(0.2) {
try! realm.write {
realm.delete(realm.objects(Message).first!)
}
}
delay(0.3) {
try! realm.write {
realm.add(Message("second"))
}
}
scheduler.start()
waitForExpectationsWithTimeout(0.5) {error in
//do tests here
XCTAssertTrue(error == nil)
XCTAssertEqual(observer.events.count, 4)
let results = observer.events.last!.value.element!
XCTAssertEqual(results.count, 1)
XCTAssertEqual(results.first!.text, "second")
}
}
func testResultsTypeChangeset() {
let expectation1 = expectationWithDescription("Results<Message> first")
let realm = realmInMemory(#function)
clearRealm(realm)
let bag = DisposeBag()
let scheduler = TestScheduler(initialClock: 0)
let observer = scheduler.createObserver(String)
let messages$ = realm.objects(Message).asObservableChangeset().shareReplay(1)
messages$.scan(0, accumulator: {acc, _ in return acc+1})
.filter { $0 == 3 }.map {_ in ()}.subscribeNext(expectation1.fulfill).addDisposableTo(bag)
messages$
.map {results, changes in
if let changes = changes {
return "i:\(changes.inserted) d:\(changes.deleted) u:\(changes.updated)"
} else {
return "initial"
}
}
.subscribe(observer).addDisposableTo(bag)
//interact with Realm here
delay(0.1) {
try! realm.write {
realm.add(Message("first"))
}
}
delay(0.2) {
try! realm.write {
realm.delete(realm.objects(Message).first!)
}
}
scheduler.start()
waitForExpectationsWithTimeout(0.5) {error in
//do tests here
XCTAssertTrue(error == nil)
XCTAssertEqual(observer.events.count, 3)
XCTAssertEqual(observer.events[0].value.element!, "initial")
XCTAssertEqual(observer.events[1].value.element!, "i:[0] d:[] u:[]")
XCTAssertEqual(observer.events[2].value.element!, "i:[] d:[0] u:[]")
}
}
}

View File

@@ -6,6 +6,7 @@
import XCTest
import Pods_RxRealm_Tests
import RxSwift
import RealmSwift
import RxRealm
@@ -16,18 +17,6 @@ func delay(delay: Double, closure: () -> Void) {
dispatch_get_main_queue(), closure)
}
class Message: Object, Equatable {
dynamic var text = ""
convenience init(_ text: String) {
self.init()
self.text = text
}
}
func ==(lhs: Message, rhs: Message) -> Bool {
return lhs.text == rhs.text
}
class RxRealm_Tests: XCTestCase {
private func realmInMemory(name: String) -> Realm {

View File

@@ -0,0 +1,44 @@
//
// TestModels.swift
// RxRealm
//
// Created by Marin Todorov on 4/30/16.
// Copyright © 2016 CocoaPods. All rights reserved.
//
import Foundation
import RealmSwift
//MARK: Message
class Message: Object, Equatable {
dynamic var text = ""
let recipients = List<User>()
let mentions = LinkingObjects(fromType: User.self, property: "lastMessage")
convenience init(_ text: String) {
self.init()
self.text = text
}
}
func ==(lhs: Message, rhs: Message) -> Bool {
return lhs.text == rhs.text
}
//MARK: User
class User: Object, Equatable {
dynamic var name = ""
dynamic var lastMessage: Message?
convenience init(_ name: String) {
self.init()
self.name = name
}
}
func ==(lhs: User, rhs: User) -> Bool {
return lhs.name == rhs.name
}

View File

@@ -15,15 +15,31 @@ public protocol NotificationEmitter {
extension List: NotificationEmitter {}
extension AnyRealmCollection: NotificationEmitter {}
extension Results: NotificationEmitter {}
extension LinkingObjects: NotificationEmitter {}
/**
`RealmChangeset` is a struct that contains the data about a single realm change set.
It includes the insertions, modifications, and deletions indexes in the data set that the current notification is about.
*/
public struct RealmChangeset {
/// the indexes in the collection that were deleted
public let deleted: [Int]
/// the indexes in the collection that were inserted
public let inserted: [Int]
/// the indexes in the collection that were modified
public let updated: [Int]
}
public extension NotificationEmitter where Self: RealmCollectionType {
/**
Returns an `Observable<Self>` that emits each time the collection data changes. The observable emits an initial value upon subscription.
- returns: `Observable<Self>`, e.g. when called on `Results<Model>` it will return `Observable<Results<Model>>`, on a `List<User>` it will return `Observable<List<User>>`, etc.
*/
public func asObservable() -> Observable<Self> {
return Observable.create {observer in
let token = self.addNotificationBlock {changeset in
@@ -51,10 +67,26 @@ public extension NotificationEmitter where Self: RealmCollectionType {
}
}
/**
Returns an `Observable<Array<Self.Generator.Element>>` that emits each time the collection data changes. The observable emits an initial value upon subscription.
This method emits an `Array` containing all the realm collection objects, this means they all live in the memory. If you're using this method to observe large collections you might hit memory warnings.
- returns: `Observable<Array<Self.Generator.Element>>`, e.g. when called on `Results<Model>` it will return `Observable<Array<Model>>`, on a `List<User>` it will return `Observable<Array<User>>`, etc.
*/
public func asObservableArray() -> Observable<Array<Self.Generator.Element>> {
return asObservable().map { Array($0) }
}
/**
Returns an `Observable<(Self, RealmChangeset?)>` that emits each time the collection data changes. The observable emits an initial value upon subscription.
When the observable emits for the first time (if the initial notification is not coalesced with an update) the second tuple value will be `nil`.
Each following emit will include a `RealmChangeset` with the indexes inserted, deleted or modified.
- returns: `Observable<(Self, RealmChangeset?)>`
*/
public func asObservableChangeset() -> Observable<(Self, RealmChangeset?)> {
return Observable.create {observer in
let token = self.addNotificationBlock {changeset in
@@ -76,6 +108,17 @@ public extension NotificationEmitter where Self: RealmCollectionType {
}
}
/**
Returns an `Observable<(Array<Self.Generator.Element>, RealmChangeset?)>` that emits each time the collection data changes. The observable emits an initial value upon subscription.
This method emits an `Array` containing all the realm collection objects, this means they all live in the memory. If you're using this method to observe large collections you might hit memory warnings.
When the observable emits for the first time (if the initial notification is not coalesced with an update) the second tuple value will be `nil`.
Each following emit will include a `RealmChangeset` with the indexes inserted, deleted or modified.
- returns: `Observable<(Array<Self.Generator.Element>, RealmChangeset?)>`
*/
public func asObservableArrayChangeset() -> Observable<(Array<Self.Generator.Element>, RealmChangeset?)> {
return asObservableChangeset().map { (Array($0), $1) }
}

View File

@@ -6,9 +6,9 @@
## Usage
This library is a very thin wrapper around the reactive collection types __RealmSwift__ provides: `Results`, `List` and `AnyRealmCollection`.
This library is a very thin wrapper around the reactive collection types that __RealmSwift__ provides.
The extension adds these methods to all of the above:
The extension adds to `Results`, `List`, `LinkingObjects` and `AnyRealmCollection` these methods:
#### asObservable()
`asObservable()` - emits every time the collection changes:
@@ -62,9 +62,11 @@ realm.objects(Lap).asObservableChangeset()
To run the example project, clone the repo, and run `pod install` from the Example directory first. The app uses RxSwift, RxCocoa using RealmSwift, RxRealm to observe Results from Realm.
Further you're welcome to peak into the __RxRealmTests__ folder of the example app, which features the library's unit tests.
## Installation
This library depends on both __RxSwift__ and __RealmSwift__ 0.99+.
This library depends on both __RxSwift__ and __RealmSwift__ 0.100+.
#### CocoaPods
RxRealm is available through [CocoaPods](http://cocoapods.org). To install it, simply add the following line to your Podfile:
@@ -81,17 +83,14 @@ Feel free to send a PR
You can grab the __RxRealm.swift__ file from this repo and include it in your project.
## Author
This library belongs to _RxSwiftCommunity_ and is based on the work of [@fpillet](https://github.com/fpillet)
## TODO
* Carthage
* Add `asObservable()` to the Realm class
* Test add platforms and add compatibility for the pod
* Document the source code
## License
RxRealm is available under the MIT license. See the LICENSE file for more info.
This library belongs to _RxSwiftCommunity_.
RxRealm is available under the MIT license. See the LICENSE file for more info.

View File

@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "RxRealm"
s.version = "0.1.3"
s.version = "0.1.4"
s.summary = "An Rx wrapper of Realm's collection type"
s.description = <<-DESC
@@ -22,7 +22,7 @@ Pod::Spec.new do |s|
s.source_files = 'Pod/Classes/*.swift'
s.frameworks = 'Foundation'
s.dependency 'RealmSwift', '~> 0.99'
s.dependency 'RealmSwift', '~> 0.100'
s.dependency 'RxSwift'
end