This commit is contained in:
Peter 2015-11-21 10:34:08 -05:00
commit 54b51e0b60
7 changed files with 203 additions and 54 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "Dollar.swift"]
path = Dollar.swift
url = https://github.com/ankurp/Dollar.swift.git

View File

@ -1,4 +1,5 @@
github "krzyzanowskim/CryptoSwift" "0.1.1" github "krzyzanowskim/CryptoSwift" "0.1.1"
github "ankurp/Dollar.swift" "4.0.1"
github "Quick/Nimble" "v2.0.0-rc.3" github "Quick/Nimble" "v2.0.0-rc.3"
github "Quick/Quick" "v0.6.0" github "Quick/Quick" "v0.6.0"
github "tidwall/SwiftWebSocket" "v2.3.0" github "tidwall/SwiftWebSocket" "v2.3.0"

View File

@ -19,6 +19,7 @@ Pod::Spec.new do |s|
s.dependency 'CryptoSwift' s.dependency 'CryptoSwift'
s.dependency 'SwiftWebSocket' s.dependency 'SwiftWebSocket'
s.dependency 'Dollar'
s.dependency 'XCGLogger' s.dependency 'XCGLogger'
end end

View File

@ -25,7 +25,11 @@
D0C71B561BC172F40089B6CE /* Meteor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C71B551BC172F40089B6CE /* Meteor.swift */; settings = {ASSET_TAGS = (); }; }; 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 = (); }; }; 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 = (); }; }; D0C71B5B1BC174280089B6CE /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C71B5A1BC174280089B6CE /* Data.swift */; settings = {ASSET_TAGS = (); }; };
<<<<<<< HEAD
D0F6C99D1BFFA04600A6CB70 /* EJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F6C99C1BFFA04600A6CB70 /* EJSON.swift */; settings = {ASSET_TAGS = (); }; }; D0F6C99D1BFFA04600A6CB70 /* EJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F6C99C1BFFA04600A6CB70 /* EJSON.swift */; settings = {ASSET_TAGS = (); }; };
=======
D0F6C9221BFE97C800A6CB70 /* Dollar.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0F6C91F1BFE97BC00A6CB70 /* Dollar.framework */; };
>>>>>>> 75ac27cd6b5f36231c0ad7b65baed7b0cf65f997
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -36,6 +40,20 @@
remoteGlobalIDString = D02A71DB1BBEFBCA00940C17; remoteGlobalIDString = D02A71DB1BBEFBCA00940C17;
remoteInfo = SwiftDDP; 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 */ /* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXCopyFilesBuildPhase section */
@ -80,7 +98,11 @@
D0C71B551BC172F40089B6CE /* Meteor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Meteor.swift; sourceTree = "<group>"; }; D0C71B551BC172F40089B6CE /* Meteor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Meteor.swift; sourceTree = "<group>"; };
D0C71B571BC173030089B6CE /* SwiftMeteor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftMeteor.swift; sourceTree = "<group>"; }; D0C71B571BC173030089B6CE /* SwiftMeteor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftMeteor.swift; sourceTree = "<group>"; };
D0C71B5A1BC174280089B6CE /* Data.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = "<group>"; }; D0C71B5A1BC174280089B6CE /* Data.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = "<group>"; };
<<<<<<< HEAD
D0F6C99C1BFFA04600A6CB70 /* EJSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EJSON.swift; sourceTree = "<group>"; }; D0F6C99C1BFFA04600A6CB70 /* EJSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EJSON.swift; sourceTree = "<group>"; };
=======
D0F6C9191BFE97BC00A6CB70 /* Dollar.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Dollar.xcodeproj; path = Dollar.swift/Dollar/Dollar.xcodeproj; sourceTree = "<group>"; };
>>>>>>> 75ac27cd6b5f36231c0ad7b65baed7b0cf65f997
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -88,6 +110,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
D0F6C9221BFE97C800A6CB70 /* Dollar.framework in Frameworks */,
D02A72281BBF01E900940C17 /* CryptoSwift.framework in Frameworks */, D02A72281BBF01E900940C17 /* CryptoSwift.framework in Frameworks */,
D02A722A1BBF01ED00940C17 /* SwiftWebSocket.framework in Frameworks */, D02A722A1BBF01ED00940C17 /* SwiftWebSocket.framework in Frameworks */,
D02A722C1BBF01EF00940C17 /* XCGLogger.framework in Frameworks */, D02A722C1BBF01EF00940C17 /* XCGLogger.framework in Frameworks */,
@ -149,6 +172,7 @@
D02A72341BBF02C200940C17 /* Frameworks */ = { D02A72341BBF02C200940C17 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D0F6C9191BFE97BC00A6CB70 /* Dollar.xcodeproj */,
D02A72321BBF02B900940C17 /* XCGLogger.framework.dSYM */, D02A72321BBF02B900940C17 /* XCGLogger.framework.dSYM */,
D02A72301BBF02B400940C17 /* SwiftWebSocket.framework.dSYM */, D02A72301BBF02B400940C17 /* SwiftWebSocket.framework.dSYM */,
D02A722E1BBF02AA00940C17 /* CryptoSwift.framework.dSYM */, D02A722E1BBF02AA00940C17 /* CryptoSwift.framework.dSYM */,
@ -173,6 +197,15 @@
path = SwiftDDPTests; path = SwiftDDPTests;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
D0F6C91A1BFE97BC00A6CB70 /* Products */ = {
isa = PBXGroup;
children = (
D0F6C91F1BFE97BC00A6CB70 /* Dollar.framework */,
D0F6C9211BFE97BC00A6CB70 /* DollarTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */ /* Begin PBXHeadersBuildPhase section */
@ -252,6 +285,12 @@
mainGroup = D02A71D21BBEFBCA00940C17; mainGroup = D02A71D21BBEFBCA00940C17;
productRefGroup = D02A71DD1BBEFBCA00940C17 /* Products */; productRefGroup = D02A71DD1BBEFBCA00940C17 /* Products */;
projectDirPath = ""; projectDirPath = "";
projectReferences = (
{
ProductGroup = D0F6C91A1BFE97BC00A6CB70 /* Products */;
ProjectRef = D0F6C9191BFE97BC00A6CB70 /* Dollar.xcodeproj */;
},
);
projectRoot = ""; projectRoot = "";
targets = ( targets = (
D02A71DB1BBEFBCA00940C17 /* SwiftDDP */, D02A71DB1BBEFBCA00940C17 /* SwiftDDP */,
@ -260,6 +299,23 @@
}; };
/* End PBXProject section */ /* 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 */ /* Begin PBXResourcesBuildPhase section */
D02A71DA1BBEFBCA00940C17 /* Resources */ = { D02A71DA1BBEFBCA00940C17 /* Resources */ = {
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;

View File

@ -32,16 +32,12 @@ case InternalServerError = "500"
} }
*/ */
protocol MeteorCollectionType { public protocol MeteorCollectionType {
func documentWasAdded(collection:String, id:String, fields:NSDictionary?) func documentWasAdded(collection:String, id:String, fields:NSDictionary?)
func documentWasChanged(collection:String, id:String, fields:NSDictionary?, cleared:[String]?) func documentWasChanged(collection:String, id:String, fields:NSDictionary?, cleared:[String]?)
func documentWasRemoved(collection:String, id: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 Meteor is a class to simplify communicating with and consuming MeteorJS server services
*/ */
@ -270,31 +266,42 @@ public class Meteor {
} }
} }
/** public class MeteorDocument: NSObject {
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 { 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)
}
}
}
}
public class AbstractCollection: NSObject, MeteorCollectionType {
public var name:String public var name:String
public let client = Meteor.client public let client = Meteor.client
private var documents = [String:MeteorDocument]()
// 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) -> ())?
/**
Initializes a MeteorCollection object
- parameter name: The string name of the collection (must match the name of the collection on the server)
*/
public init(name:String) { public init(name:String) {
self.name = name self.name = name
super.init() super.init()
@ -313,8 +320,79 @@ public class MeteorCollection: NSObject, MeteorCollectionType {
- parameter fields: an optional NSDictionary with the documents properties - parameter fields: an optional NSDictionary with the documents properties
*/ */
public func documentWasAdded(collection:String, id:String, fields:NSDictionary?) { public func documentWasAdded(collection:String, id:String, fields:NSDictionary?) {}
if let added = onAdded { added(collection: collection, id: id, fields:fields) }
/**
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 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<T:MeteorDocument>: AbstractCollection {
var documents = [String:T]()
var sorted:[T] {
return Array(documents.values).sort({ $0.id > $1.id })
}
/**
Returns the number of documents in the collection
*/
var count:Int {
return documents.count
}
/**
Initializes a MeteorCollection object
- parameter name: The string name of the collection (must match the name of the collection on the server)
*/
private func sorted(property:String) -> [T] {
let values = Array(documents.values)
return values.sort({ $0.id > $1.id })
}
/**
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 override func documentWasAdded(collection:String, id:String, fields:NSDictionary?) {
let document = T(id: id, fields: fields)
documents[id] = document
} }
/** /**
@ -326,8 +404,12 @@ public class MeteorCollection: NSObject, MeteorCollectionType {
- parameter cleared: Optional array of strings (field names to delete) - 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 changed = onChanged { changed(collection:collection, id:id, fields:fields, cleared:cleared) } if let document = documents[id] {
document.update(fields, cleared: cleared)
documents[id] = document
}
} }
/** /**
@ -337,8 +419,10 @@ public class MeteorCollection: NSObject, MeteorCollectionType {
- parameter id: the string unique id that identifies the document on the server - 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 removed = onRemoved { removed(collection:collection, id:id) } if let _ = documents[id] {
documents[id] = nil
}
} }
} }

View File

@ -1,6 +1,6 @@
import Foundation 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 *** // *** methods that are tested against a server are tested against the url below ***
let url = "ws://swiftddp.meteor.com/websocket" let url = "ws://swiftddp.meteor.com/websocket"

View File

@ -8,7 +8,7 @@ class MeteorTest: QuickSpec {
override func spec() { override func spec() {
let client = Meteor.client let client = Meteor.client
let collection = MeteorCollection(name: "test-collection") let collection = MeteorCollection<Document>(name: "test-collection")
describe("Collections") { describe("Collections") {
/* /*
@ -24,40 +24,37 @@ class MeteorTest: QuickSpec {
describe("Document methods send notifications") { describe("Document methods send notifications") {
it("sends a message when a document is added") { 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]) 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") { it("sends a message when a document is removed") {
var _id:String!
collection.onRemoved = {collection, id in try! client.ddpMessageHandler(added[1])
if (id == "2gAMzqvE8K8kBWK8F") { _id = id } expect(collection.documents["ByuwhKPGuLru8h4TT"]).toEventuallyNot(beNil())
} expect(collection.documents["ByuwhKPGuLru8h4TT"]!.city).toEventually(equal("Truro"))
try! client.ddpMessageHandler(removed[0]) try! client.ddpMessageHandler(removed[1])
expect(_id).toEventuallyNot(beNil()) expect(collection.documents["ByuwhKPGuLru8h4TT"]).toEventually(beNil())
expect(_id).toEventually(equal("2gAMzqvE8K8kBWK8F"))
} }
it("sends a message when a document is updated") { it("sends a message when a document is updated") {
var _id:String!
collection.onChanged = {collection, id, fields, cleared in try! client.ddpMessageHandler(added[2])
if (id == "2gAMzqvE8K8kBWK8F") { _id = id } expect(collection.documents["AGX6vyxCJtjqdxbFH"]).toEventuallyNot(beNil())
} expect(collection.documents["AGX6vyxCJtjqdxbFH"]!.city).toEventually(equal("Austin"))
try! client.ddpMessageHandler(changed[2])
expect(collection.documents["AGX6vyxCJtjqdxbFH"]!.city).toEventually(equal("Houston"))
try! client.ddpMessageHandler(changed[0])
expect(_id).toEventuallyNot(beNil())
expect(_id).toEventually(equal("2gAMzqvE8K8kBWK8F"))
} }
} }
} }
} }