From 89c4410a66222c826506bbc408bf32875a46a639 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 19 Nov 2015 15:35:21 -0500 Subject: [PATCH 1/3] built in datastore basics implemented; tests passing --- SwiftDDP/Meteor.swift | 76 ++++++++++++++++++++++++++------- SwiftDDPTests/Data.swift | 9 +++- SwiftDDPTests/SwiftMeteor.swift | 41 +++++++++--------- 3 files changed, 88 insertions(+), 38 deletions(-) diff --git a/SwiftDDP/Meteor.swift b/SwiftDDP/Meteor.swift index aaba706..bcb41d7 100644 --- a/SwiftDDP/Meteor.swift +++ b/SwiftDDP/Meteor.swift @@ -32,16 +32,12 @@ case InternalServerError = "500" } */ -protocol MeteorCollectionType { +public protocol MeteorCollectionType { func documentWasAdded(collection:String, id:String, fields:NSDictionary?) func documentWasChanged(collection:String, id:String, fields:NSDictionary?, cleared:[String]?) func documentWasRemoved(collection:String, id:String) } -protocol MeteorDocument { - var id:String { get } -} - /** Meteor is a class to simplify communicating with and consuming MeteorJS server services */ @@ -270,24 +266,58 @@ public class Meteor { } } +public class MeteorDocument: NSObject { + + var id:String + + required public init(id: String, fields: NSDictionary?) { + self.id = id + super.init() + + if let properties = fields { + for (key,value) in properties { + self.setValue(value, forKey: key as! String) + } + } + } + + public func update(fields: NSDictionary?, cleared: [String]?) { + if let properties = fields { + for (key,value) in properties { + self.setValue(value, forKey: key as! String) + } + } + + if let deletions = cleared { + for property in deletions { + self.setNilValueForKey(property) + } + } + } + +} + /** MeteorCollection is a class created to provide a base class and api for integrating SwiftDDP with persistence stores. MeteorCollection should generally be subclassed, with the methods documentWasAdded, documentWasChanged and documentWasRemoved facilitating communicating with the datastore. */ -public class MeteorCollection: NSObject, MeteorCollectionType { +// MeteorCollectionType protocol declaration is necessary +public class MeteorCollection: NSObject, MeteorCollectionType { public var name:String public let client = Meteor.client - private var documents = [String:MeteorDocument]() + var documents = [String:T]() - // Alternative API to subclassing - // Can also set these closures to modify behavior on added, changed, removed - internal var onAdded:((collection:String, id:String, fields:NSDictionary?) -> ())? - internal var onChanged:((collection:String, id:String, fields:NSDictionary?, cleared:[String]?) -> ())? - internal var onRemoved:((collection:String, id:String) -> ())? + /** + Returns the number of documents in the collection + */ + + var count:Int { + return documents.count + } /** Initializes a MeteorCollection object @@ -305,6 +335,12 @@ public class MeteorCollection: NSObject, MeteorCollectionType { Meteor.collections[name] = nil } + /* + func sorted(property:String) -> [String] { + return [] + } + */ + /** Invoked when a document has been sent from the server. @@ -314,7 +350,11 @@ public class MeteorCollection: NSObject, MeteorCollectionType { */ public func documentWasAdded(collection:String, id:String, fields:NSDictionary?) { - if let added = onAdded { added(collection: collection, id: id, fields:fields) } + print("FOOOOOOO") + let document = T(id: id, fields: fields) + documents[id] = document + print("Document --> \(document)") + } /** @@ -327,7 +367,11 @@ public class MeteorCollection: NSObject, MeteorCollectionType { */ public func documentWasChanged(collection:String, id:String, fields:NSDictionary?, cleared:[String]?) { - if let changed = onChanged { changed(collection:collection, id:id, fields:fields, cleared:cleared) } + if let document = documents[id] { + document.update(fields, cleared: cleared) + documents[id] = document + } + } /** @@ -338,7 +382,9 @@ public class MeteorCollection: NSObject, MeteorCollectionType { */ public func documentWasRemoved(collection:String, id:String) { - if let removed = onRemoved { removed(collection:collection, id:id) } + if let _ = documents[id] { + documents[id] = nil + } } } diff --git a/SwiftDDPTests/Data.swift b/SwiftDDPTests/Data.swift index cd81fc1..4c3b119 100644 --- a/SwiftDDPTests/Data.swift +++ b/SwiftDDPTests/Data.swift @@ -1,6 +1,6 @@ import Foundation -import SwiftDDP +@testable import SwiftDDP // // @@ -8,6 +8,13 @@ import SwiftDDP // // +class Document: MeteorDocument { + + var state:String? + var city:String? + +} + // *** methods that are tested against a server are tested against the url below *** let url = "ws://swiftddp.meteor.com/websocket" diff --git a/SwiftDDPTests/SwiftMeteor.swift b/SwiftDDPTests/SwiftMeteor.swift index 8dca9a2..1b090e5 100644 --- a/SwiftDDPTests/SwiftMeteor.swift +++ b/SwiftDDPTests/SwiftMeteor.swift @@ -8,7 +8,7 @@ class MeteorTest: QuickSpec { override func spec() { let client = Meteor.client - let collection = MeteorCollection(name: "test-collection") + let collection = MeteorCollection(name: "test-collection") describe("Collections") { /* @@ -24,40 +24,37 @@ class MeteorTest: QuickSpec { describe("Document methods send notifications") { it("sends a message when a document is added") { - var _id:String! - - collection.onAdded = {collection, id, fields in - if (id == "2gAMzqvE8K8kBWK8F") { _id = id } - } try! client.ddpMessageHandler(added[0]) - expect(_id).toEventuallyNot(beNil()) - expect(_id).toEventually(equal("2gAMzqvE8K8kBWK8F")) + + print("Collection -> \(collection.documents)") + + expect(collection.documents["2gAMzqvE8K8kBWK8F"]).toEventuallyNot(beNil()) + expect(collection.documents["2gAMzqvE8K8kBWK8F"]?.city).toEventually(equal("Boston")) } it("sends a message when a document is removed") { - var _id:String! - collection.onRemoved = {collection, id in - if (id == "2gAMzqvE8K8kBWK8F") { _id = id } - } + try! client.ddpMessageHandler(added[1]) + expect(collection.documents["ByuwhKPGuLru8h4TT"]).toEventuallyNot(beNil()) + expect(collection.documents["ByuwhKPGuLru8h4TT"]!.city).toEventually(equal("Truro")) - try! client.ddpMessageHandler(removed[0]) - expect(_id).toEventuallyNot(beNil()) - expect(_id).toEventually(equal("2gAMzqvE8K8kBWK8F")) + try! client.ddpMessageHandler(removed[1]) + expect(collection.documents["ByuwhKPGuLru8h4TT"]).toEventually(beNil()) } + it("sends a message when a document is updated") { - var _id:String! - collection.onChanged = {collection, id, fields, cleared in - if (id == "2gAMzqvE8K8kBWK8F") { _id = id } - } + try! client.ddpMessageHandler(added[2]) + expect(collection.documents["AGX6vyxCJtjqdxbFH"]).toEventuallyNot(beNil()) + expect(collection.documents["AGX6vyxCJtjqdxbFH"]!.city).toEventually(equal("Austin")) - try! client.ddpMessageHandler(changed[0]) - expect(_id).toEventuallyNot(beNil()) - expect(_id).toEventually(equal("2gAMzqvE8K8kBWK8F")) + try! client.ddpMessageHandler(changed[2]) + expect(collection.documents["AGX6vyxCJtjqdxbFH"]!.city).toEventually(equal("Houston")) + } + } } } \ No newline at end of file From d21b54001bf1c6495e2c6b5fe36aa03995e875a3 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 19 Nov 2015 18:50:50 -0500 Subject: [PATCH 2/3] added dollar --- .gitmodules | 3 ++ Cartfile.resolved | 1 + Dollar.swift | 1 + SwiftDDP.podspec | 1 + SwiftDDP.xcodeproj/project.pbxproj | 50 ++++++++++++++++++ SwiftDDP/Meteor.swift | 84 ++++++++++++++++++++++-------- 6 files changed, 117 insertions(+), 23 deletions(-) create mode 100644 .gitmodules create mode 160000 Dollar.swift diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..3bb76e5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Dollar.swift"] + path = Dollar.swift + url = https://github.com/ankurp/Dollar.swift.git diff --git a/Cartfile.resolved b/Cartfile.resolved index b8dd35b..91fc81e 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,4 +1,5 @@ github "krzyzanowskim/CryptoSwift" "0.1.1" +github "ankurp/Dollar.swift" "4.0.1" github "Quick/Nimble" "v2.0.0-rc.3" github "Quick/Quick" "v0.6.0" github "tidwall/SwiftWebSocket" "v2.3.0" diff --git a/Dollar.swift b/Dollar.swift new file mode 160000 index 0000000..531b2af --- /dev/null +++ b/Dollar.swift @@ -0,0 +1 @@ +Subproject commit 531b2afea657ea173c908183bffd9d2f325ef989 diff --git a/SwiftDDP.podspec b/SwiftDDP.podspec index 7719787..8f232c2 100644 --- a/SwiftDDP.podspec +++ b/SwiftDDP.podspec @@ -19,6 +19,7 @@ Pod::Spec.new do |s| s.dependency 'CryptoSwift' s.dependency 'SwiftWebSocket' + s.dependency 'Dollar' s.dependency 'XCGLogger' end diff --git a/SwiftDDP.xcodeproj/project.pbxproj b/SwiftDDP.xcodeproj/project.pbxproj index 14f5199..f9d6f46 100644 --- a/SwiftDDP.xcodeproj/project.pbxproj +++ b/SwiftDDP.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ D0C71B561BC172F40089B6CE /* Meteor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C71B551BC172F40089B6CE /* Meteor.swift */; settings = {ASSET_TAGS = (); }; }; D0C71B581BC173030089B6CE /* SwiftMeteor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C71B571BC173030089B6CE /* SwiftMeteor.swift */; settings = {ASSET_TAGS = (); }; }; D0C71B5B1BC174280089B6CE /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C71B5A1BC174280089B6CE /* Data.swift */; settings = {ASSET_TAGS = (); }; }; + D0F6C9221BFE97C800A6CB70 /* Dollar.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0F6C91F1BFE97BC00A6CB70 /* Dollar.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -35,6 +36,20 @@ remoteGlobalIDString = D02A71DB1BBEFBCA00940C17; remoteInfo = SwiftDDP; }; + D0F6C91E1BFE97BC00A6CB70 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D0F6C9191BFE97BC00A6CB70 /* Dollar.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 92E0D03C19467C67002ACC3D; + remoteInfo = Dollar; + }; + D0F6C9201BFE97BC00A6CB70 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D0F6C9191BFE97BC00A6CB70 /* Dollar.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 92E6686F19F09C6400BB4FB8; + remoteInfo = DollarTests; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -79,6 +94,7 @@ D0C71B551BC172F40089B6CE /* Meteor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Meteor.swift; sourceTree = ""; }; D0C71B571BC173030089B6CE /* SwiftMeteor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftMeteor.swift; sourceTree = ""; }; D0C71B5A1BC174280089B6CE /* Data.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = ""; }; + D0F6C9191BFE97BC00A6CB70 /* Dollar.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Dollar.xcodeproj; path = Dollar.swift/Dollar/Dollar.xcodeproj; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -86,6 +102,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D0F6C9221BFE97C800A6CB70 /* Dollar.framework in Frameworks */, D02A72281BBF01E900940C17 /* CryptoSwift.framework in Frameworks */, D02A722A1BBF01ED00940C17 /* SwiftWebSocket.framework in Frameworks */, D02A722C1BBF01EF00940C17 /* XCGLogger.framework in Frameworks */, @@ -146,6 +163,7 @@ D02A72341BBF02C200940C17 /* Frameworks */ = { isa = PBXGroup; children = ( + D0F6C9191BFE97BC00A6CB70 /* Dollar.xcodeproj */, D02A72321BBF02B900940C17 /* XCGLogger.framework.dSYM */, D02A72301BBF02B400940C17 /* SwiftWebSocket.framework.dSYM */, D02A722E1BBF02AA00940C17 /* CryptoSwift.framework.dSYM */, @@ -170,6 +188,15 @@ path = SwiftDDPTests; sourceTree = ""; }; + D0F6C91A1BFE97BC00A6CB70 /* Products */ = { + isa = PBXGroup; + children = ( + D0F6C91F1BFE97BC00A6CB70 /* Dollar.framework */, + D0F6C9211BFE97BC00A6CB70 /* DollarTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -249,6 +276,12 @@ mainGroup = D02A71D21BBEFBCA00940C17; productRefGroup = D02A71DD1BBEFBCA00940C17 /* Products */; projectDirPath = ""; + projectReferences = ( + { + ProductGroup = D0F6C91A1BFE97BC00A6CB70 /* Products */; + ProjectRef = D0F6C9191BFE97BC00A6CB70 /* Dollar.xcodeproj */; + }, + ); projectRoot = ""; targets = ( D02A71DB1BBEFBCA00940C17 /* SwiftDDP */, @@ -257,6 +290,23 @@ }; /* End PBXProject section */ +/* Begin PBXReferenceProxy section */ + D0F6C91F1BFE97BC00A6CB70 /* Dollar.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Dollar.framework; + remoteRef = D0F6C91E1BFE97BC00A6CB70 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + D0F6C9211BFE97BC00A6CB70 /* DollarTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = DollarTests.xctest; + remoteRef = D0F6C9201BFE97BC00A6CB70 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + /* Begin PBXResourcesBuildPhase section */ D02A71DA1BBEFBCA00940C17 /* Resources */ = { isa = PBXResourcesBuildPhase; diff --git a/SwiftDDP/Meteor.swift b/SwiftDDP/Meteor.swift index bcb41d7..005b938 100644 --- a/SwiftDDP/Meteor.swift +++ b/SwiftDDP/Meteor.swift @@ -297,19 +297,70 @@ public class MeteorDocument: NSObject { } +public class AbstractCollection: NSObject, MeteorCollectionType { + + public var name:String + public let client = Meteor.client + + public init(name:String) { + self.name = name + super.init() + Meteor.collections[name] = self + } + + deinit { + Meteor.collections[name] = nil + } + + /** + Invoked when a document has been sent from the server. + + - parameter collection: the string name of the collection to which the document belongs + - parameter id: the string unique id that identifies the document on the server + - parameter fields: an optional NSDictionary with the documents properties + */ + + public func documentWasAdded(collection:String, id:String, fields:NSDictionary?) {} + + /** + Invoked when a document has been changed on the server. + + - parameter collection: the string name of the collection to which the document belongs + - parameter id: the string unique id that identifies the document on the server + - parameter fields: an optional NSDictionary with the documents properties + - parameter cleared: Optional array of strings (field names to delete) + */ + + public func documentWasChanged(collection:String, id:String, fields:NSDictionary?, cleared:[String]?) {} + + /** + Invoked when a document has been removed on the server. + + - parameter collection: the string name of the collection to which the document belongs + - parameter id: the string unique id that identifies the document on the server + */ + + public func documentWasRemoved(collection:String, id:String) {} + +} + + + /** -MeteorCollection is a class created to provide a base class and api for integrating SwiftDDP with persistence stores. MeteorCollection +MeteorCollection provides basic persistence as well as an api for integrating SwiftDDP with persistence stores. MeteorCollection should generally be subclassed, with the methods documentWasAdded, documentWasChanged and documentWasRemoved facilitating communicating with the datastore. */ // MeteorCollectionType protocol declaration is necessary -public class MeteorCollection: NSObject, MeteorCollectionType { +public class MeteorCollection: AbstractCollection { - public var name:String - public let client = Meteor.client var documents = [String:T]() + + var sorted:[T] { + return Array(documents.values).sort({ $0.id > $1.id }) + } /** Returns the number of documents in the collection @@ -325,22 +376,11 @@ public class MeteorCollection: NSObject, MeteorCollectionType - parameter name: The string name of the collection (must match the name of the collection on the server) */ - public init(name:String) { - self.name = name - super.init() - Meteor.collections[name] = self + private func sorted(property:String) -> [T] { + let values = Array(documents.values) + return values.sort({ $0.id > $1.id }) } - deinit { - Meteor.collections[name] = nil - } - - /* - func sorted(property:String) -> [String] { - return [] - } - */ - /** Invoked when a document has been sent from the server. @@ -349,11 +389,9 @@ public class MeteorCollection: NSObject, MeteorCollectionType - parameter fields: an optional NSDictionary with the documents properties */ - public func documentWasAdded(collection:String, id:String, fields:NSDictionary?) { - print("FOOOOOOO") + public override func documentWasAdded(collection:String, id:String, fields:NSDictionary?) { let document = T(id: id, fields: fields) documents[id] = document - print("Document --> \(document)") } @@ -366,7 +404,7 @@ public class MeteorCollection: NSObject, MeteorCollectionType - parameter cleared: Optional array of strings (field names to delete) */ - public func documentWasChanged(collection:String, id:String, fields:NSDictionary?, cleared:[String]?) { + public override func documentWasChanged(collection:String, id:String, fields:NSDictionary?, cleared:[String]?) { if let document = documents[id] { document.update(fields, cleared: cleared) documents[id] = document @@ -381,7 +419,7 @@ public class MeteorCollection: NSObject, MeteorCollectionType - parameter id: the string unique id that identifies the document on the server */ - public func documentWasRemoved(collection:String, id:String) { + public override func documentWasRemoved(collection:String, id:String) { if let _ = documents[id] { documents[id] = nil } From 75ac27cd6b5f36231c0ad7b65baed7b0cf65f997 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 19 Nov 2015 18:55:29 -0500 Subject: [PATCH 3/3] ... --- .../xcshareddata/SwiftDDP.xcscmblueprint | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 SwiftDDP.xcodeproj/project.xcworkspace/xcshareddata/SwiftDDP.xcscmblueprint diff --git a/SwiftDDP.xcodeproj/project.xcworkspace/xcshareddata/SwiftDDP.xcscmblueprint b/SwiftDDP.xcodeproj/project.xcworkspace/xcshareddata/SwiftDDP.xcscmblueprint new file mode 100644 index 0000000..ab311f1 --- /dev/null +++ b/SwiftDDP.xcodeproj/project.xcworkspace/xcshareddata/SwiftDDP.xcscmblueprint @@ -0,0 +1,30 @@ +{ + "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "8EA5B47AAF181A89417DB4A5B2BB211FCF91F09F", + "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { + + }, + "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { + "4E2F5E824C021282D3F5789E108FCDC580F66A19" : 0, + "8EA5B47AAF181A89417DB4A5B2BB211FCF91F09F" : 0 + }, + "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "61406E4F-759F-45A7-95EF-FD1785A38B4D", + "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { + "4E2F5E824C021282D3F5789E108FCDC580F66A19" : "SwiftDDP\/Dollar.swift\/", + "8EA5B47AAF181A89417DB4A5B2BB211FCF91F09F" : "SwiftDDP\/" + }, + "DVTSourceControlWorkspaceBlueprintNameKey" : "SwiftDDP", + "DVTSourceControlWorkspaceBlueprintVersion" : 204, + "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "SwiftDDP.xcodeproj", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ + { + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/ankurp\/Dollar.swift.git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "4E2F5E824C021282D3F5789E108FCDC580F66A19" + }, + { + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/siegesmund\/SwiftDDP.git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8EA5B47AAF181A89417DB4A5B2BB211FCF91F09F" + } + ] +} \ No newline at end of file