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 "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"

View File

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

View File

@ -25,7 +25,11 @@
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 = (); }; };
<<<<<<< HEAD
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 */
/* Begin PBXContainerItemProxy section */
@ -36,6 +40,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 */
@ -80,7 +98,11 @@
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>"; };
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>"; };
=======
D0F6C9191BFE97BC00A6CB70 /* Dollar.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Dollar.xcodeproj; path = Dollar.swift/Dollar/Dollar.xcodeproj; sourceTree = "<group>"; };
>>>>>>> 75ac27cd6b5f36231c0ad7b65baed7b0cf65f997
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -88,6 +110,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 */,
@ -149,6 +172,7 @@
D02A72341BBF02C200940C17 /* Frameworks */ = {
isa = PBXGroup;
children = (
D0F6C9191BFE97BC00A6CB70 /* Dollar.xcodeproj */,
D02A72321BBF02B900940C17 /* XCGLogger.framework.dSYM */,
D02A72301BBF02B400940C17 /* SwiftWebSocket.framework.dSYM */,
D02A722E1BBF02AA00940C17 /* CryptoSwift.framework.dSYM */,
@ -173,6 +197,15 @@
path = SwiftDDPTests;
sourceTree = "<group>";
};
D0F6C91A1BFE97BC00A6CB70 /* Products */ = {
isa = PBXGroup;
children = (
D0F6C91F1BFE97BC00A6CB70 /* Dollar.framework */,
D0F6C9211BFE97BC00A6CB70 /* DollarTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
@ -252,6 +285,12 @@
mainGroup = D02A71D21BBEFBCA00940C17;
productRefGroup = D02A71DD1BBEFBCA00940C17 /* Products */;
projectDirPath = "";
projectReferences = (
{
ProductGroup = D0F6C91A1BFE97BC00A6CB70 /* Products */;
ProjectRef = D0F6C9191BFE97BC00A6CB70 /* Dollar.xcodeproj */;
},
);
projectRoot = "";
targets = (
D02A71DB1BBEFBCA00940C17 /* SwiftDDP */,
@ -260,6 +299,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;

View File

@ -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,31 +266,42 @@ public class Meteor {
}
}
/**
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 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)
}
}
}
}
public class MeteorCollection: NSObject, MeteorCollectionType {
public class AbstractCollection: NSObject, MeteorCollectionType {
public var name:String
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) {
self.name = name
super.init()
@ -304,6 +311,75 @@ public class MeteorCollection: NSObject, MeteorCollectionType {
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 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.
@ -313,8 +389,10 @@ public class MeteorCollection: NSObject, MeteorCollectionType {
- parameter fields: an optional NSDictionary with the documents properties
*/
public func documentWasAdded(collection:String, id:String, fields:NSDictionary?) {
if let added = onAdded { added(collection: collection, id: id, fields:fields) }
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)
*/
public func documentWasChanged(collection:String, id:String, fields:NSDictionary?, cleared:[String]?) {
if let changed = onChanged { changed(collection:collection, id:id, fields:fields, cleared:cleared) }
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
}
}
/**
@ -337,8 +419,10 @@ 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) {
if let removed = onRemoved { removed(collection:collection, id:id) }
public override func documentWasRemoved(collection:String, id:String) {
if let _ = documents[id] {
documents[id] = nil
}
}
}

View File

@ -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"

View File

@ -8,7 +8,7 @@ class MeteorTest: QuickSpec {
override func spec() {
let client = Meteor.client
let collection = MeteorCollection(name: "test-collection")
let collection = MeteorCollection<Document>(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"))
}
}
}
}