ruby_ddp_client 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +8 -0
  3. data/README.md +0 -0
  4. data/lib/client.rb +181 -0
  5. data/lib/ddp.md +264 -0
  6. data/lib/ruby_ddp.rb +198 -0
  7. metadata +98 -0
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ lib/server.rb
2
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "faye-websocket"
4
+ gem "json"
5
+ gem "mongo"
6
+ gem "bson"
7
+ gem "bson_ext"
8
+
data/README.md ADDED
File without changes
data/lib/client.rb ADDED
@@ -0,0 +1,181 @@
1
+ #!usr/bin/env/ruby
2
+
3
+ #import gems
4
+ require "bundler"
5
+ Bundler.require
6
+
7
+ #ddp is just an extension of a websocket protocol
8
+ class DDP::Client < Faye::WebSocket::Client
9
+ attr_accessor :collections, :connect_m
10
+
11
+ #sets up basic server for ddp connections
12
+ def initialize( host, port = 8888, path = "websocket",
13
+ version, support )
14
+ super("http://#{host}:#{port}/#{path}") #setup websocket connection
15
+
16
+ #handler for incoming respones from the server
17
+ self.init_handlers()
18
+
19
+ @sub_callbacks = {}
20
+ @collections = {}
21
+ @sub_ids = {}
22
+ @version = version
23
+ @last_suc_version = nil
24
+ @session = nil
25
+ @curr_id = 0
26
+ @support = support
27
+ end
28
+
29
+ #sends a method call
30
+ def call_method(method, params = [], &blk)
31
+ id = self.next_id()
32
+ self.send(msg: 'method', id: id, method: method, params: params)
33
+ @sub_callbacks[id] = blk
34
+ end
35
+
36
+ #subscripe to the data 'name' on the server
37
+ #this function will assign an arbitrary id to the subscription so that
38
+ #it may be tracked by the client
39
+ def subscribe(name, params, &blk)
40
+ id = self.next_id()
41
+ self.send(msg: 'sub', id: id, name: name, params: params)
42
+
43
+ @sub_ids[name] = id
44
+ @sub_callbacks[id] = blk
45
+ end
46
+
47
+ #client unsibscribes from the specified subscription
48
+ def unsubscribe(name)
49
+ id = @sub_ids[name]
50
+ self.send(msg: 'unsub', id: id)
51
+ end
52
+
53
+ private
54
+
55
+ #sends connection message to server
56
+ def connect(version = @version)
57
+
58
+ #send initial connection request
59
+ self.send(msg: :connect, version: @version, support: @support)
60
+
61
+ #handle incoming response form the server
62
+ self.onmessage = lambda do |event|
63
+
64
+ res = JSON.parse(event.data)
65
+
66
+ if res.has_key? 'session'
67
+ #connection is successful - update session and record version
68
+ @session = res['session'].to_s
69
+ @last_suc_version = @version
70
+
71
+ #if the connection fails the suggested version should be retrieved
72
+ #from the response and the connection should be made
73
+ else #there was a failed connection
74
+ sug_version = res['version']
75
+ #retry the send request with the version specified by the server
76
+ self.connect(sug_version)
77
+
78
+ end
79
+ end
80
+ end
81
+
82
+ #updates and returns the next available ID number
83
+ def next_id
84
+ (@curr_id +=1).to_s
85
+ end
86
+
87
+ def send data
88
+ self.send(data.to_json)
89
+ end
90
+
91
+ #handlers for server sents
92
+ def init_handlers
93
+ #begin the client session by attempting to connect to the server
94
+ self.onopen = lamda { self.connect() }
95
+
96
+ self.onmessage = lambda do |event|
97
+
98
+ data = JSON.parse(event.data)
99
+
100
+ if data.has_key? 'msg'
101
+
102
+ case(data['msg'])
103
+ when 'connected'
104
+ self.connect_m.send_method event
105
+
106
+ #inserts the given data into the client datastore
107
+ when 'added'
108
+ if data.has_key? 'collection'
109
+ c_name = data['collection']
110
+ c_id = data['id']
111
+ @collections[c_name] ||= {}
112
+ data['params'].each { |k,v| @collections[c_name][c_id][k] = v }
113
+ end
114
+
115
+ #update the given data in the clients datastore
116
+ when 'changed'
117
+ if data.has_key 'collection'
118
+ c_name = data['collection']
119
+ c_id = data['id']
120
+ c_fields = data['fields']
121
+ to_clear = data['cleared']
122
+
123
+ #clear out fields specified at 'cleared'
124
+ to_clear.each { |f| @collections[c_name][c_id][f].delete f }
125
+ #insert/udpdate new data
126
+ c_fields.each { |k,v| @collections[c_name][c_id][k] = v }
127
+ end
128
+
129
+ #remove the given data from the client's datastore
130
+ when 'removed'
131
+ if data.has_key 'collection'
132
+ c_name = data['collection']
133
+ c_id = data['id']
134
+ @collections[c_name][c_id] = nil
135
+ end
136
+
137
+ #adds the document *before* that whose id is specified in the id
138
+ #field of addedBefore
139
+ when 'addedBefore'
140
+ if data.has_key? 'collection'
141
+ c_name = data['collection']
142
+ c_id = data['id']
143
+ before = data['before']
144
+
145
+ @collections[c_name] ||= {}
146
+ #adds before if non-null
147
+ if before
148
+ data['params'].each { |k,v| @collections[c_name][c_id-1][k] = v }
149
+ else
150
+ data['params'].each { |k,v| @collections[c_name][c_id][k] = v }
151
+ end
152
+ end
153
+
154
+ when 'movedBefore'
155
+ if data.has_key? 'collection'
156
+ c_name = data['collection']
157
+ c_id = data['id']
158
+ before = data['before']
159
+
160
+ @collections[c_name] ||= {}
161
+ #adds before if non-null
162
+ if before
163
+ data['params'].each { |k,v| @collections[c_name][c_id-1][k] = v }
164
+ else
165
+ data['params'].each { |k,v| @collections[c_name][c_id][k] = v }
166
+ end
167
+ end
168
+
169
+ when 'ready'
170
+ puts "Server acks READY"
171
+
172
+ end #case
173
+ end # msg?
174
+
175
+ self.on(:close) = lambda {|e| puts "Connection Closed"}
176
+ end #message handler
177
+ end #init_handlrers
178
+ end
179
+
180
+
181
+
data/lib/ddp.md ADDED
@@ -0,0 +1,264 @@
1
+ # DDP Specification (As provided by meteor)
2
+
3
+ DDP is a protocol between a client and a server that supports two operations:
4
+
5
+ * Remote procedure calls by the client to the server.
6
+ * The client subscribing to a set of documents, and the server keeping the
7
+ client informed about the contents of those documents as they change over
8
+ time.
9
+
10
+ This document specifies the version "pre1" of DDP. It's a rough description of
11
+ the protocol and not intended to be entirely definitive.
12
+
13
+ ## General Message Structure:
14
+
15
+ DDP may use either SockJS or WebSockets as a lower-level message transport. (For
16
+ now, you connect via SockJS at the URL `/sockjs` and via WebSockets at the URL
17
+ `/websocket`. The latter is likely to change to be the main app URL specifying a
18
+ WebSocket subprotocol.)
19
+
20
+ DDP messages are JSON objects, with some fields specified to be EJSON. Each one
21
+ has a `msg` field that specifies the message type, as well as other fields
22
+ depending on message type.
23
+
24
+ ## Establishing a DDP Connection:
25
+
26
+ ### Messages:
27
+
28
+ * `connect` (client -> server)
29
+ - `session`: string (if trying to reconnect to an existing DDP session)
30
+ - `version`: string (the proposed protocol version)
31
+ - `support`: array of strings (protocol versions supported by the client,
32
+ in order of preference)
33
+
34
+ * `connected` (server->client)
35
+ - `session`: string (an identifier for the DDP session)
36
+
37
+ * `failed` (server->client)
38
+ - `version`: string (a suggested protocol version to connect with)
39
+
40
+ ### Procedure:
41
+
42
+ The server may send an initial message which is a JSON object lacking a `msg`
43
+ key. If so, the client should ignore it. The client does not have to wait for
44
+ this message. (This message is used to help implement hot code reload over our
45
+ SockJS transport. It is currently sent over websockets as well, but probably
46
+ should not be.)
47
+
48
+ * The client sends a `connect` message.
49
+ * If the server is willing to speak the `version` of the protocol specified in
50
+ the `connect` message, it sends back a `connected` message.
51
+ * Otherwise the server sends back a `failed` message with a version of DDP it
52
+ would rather speak, informed by the `connect` message's `support` field, and
53
+ closes the underlying transport.
54
+ * The client is then free to attempt to connect again speaking a different
55
+ version of DDP. It can do that by sending another `connect` message on a new
56
+ connection. The client may optimistically send more messages after the
57
+ `connect` message, assuming that the server will support the proposed
58
+ protocol version. If the server does not support that version, it must ignore
59
+ those additional messages.
60
+
61
+ The versions in the `support` field of the client's `connect` message
62
+ are ordered according to the client's preference, most preferred
63
+ first. If, according to this ordering, the `version` proposed by the
64
+ client is not the best version that the server supports, the server
65
+ must force the client to switch to the better version by sending a
66
+ `failed` message.
67
+
68
+ When a client is connecting to a server for the first time it will
69
+ typically set `version` equal to its most preferred version. If
70
+ desired, the client can then remember the version that is ultimately
71
+ negotiated with the server and begin with that version in future
72
+ connections. The client can rely on the server sending a `failed`
73
+ message if a better version is possible as a result of the client or
74
+ the server having been upgraded.
75
+
76
+ ## Managing Data:
77
+
78
+ ### Messages:
79
+
80
+ * `sub` (client -> server):
81
+ - `id`: string (an arbitrary client-determined identifier for this subscription)
82
+ - `name`: string (the name of the subscription)
83
+ - `params`: optional array of EJSON items (parameters to the subscription)
84
+ * `unsub` (client -> server):
85
+ - `id`: string (the id passed to 'sub')
86
+ * `nosub` (server -> client):
87
+ - `id`: string (the id passed to 'sub')
88
+ * `error`: optional Error (an error raised by the subscription as it
89
+ concludes, or sub-not-found)
90
+ * `added` (server -> client):
91
+ - `collection`: string (collection name)
92
+ - `id`: string (document ID)
93
+ - `fields`: optional object with EJSON values
94
+ * `changed` (server -> client):
95
+ - `collection`: string (collection name)
96
+ - `id`: string (document ID)
97
+ - `fields`: optional object with EJSON values
98
+ - `cleared`: optional array of strings (field names to delete)
99
+ * `removed` (server -> client):
100
+ - `collection`: string (collection name)
101
+ - `id`: string (document ID)
102
+ * `ready` (server -> client):
103
+ - `subs`: array of strings (ids passed to 'sub' which have sent their
104
+ initial batch of data)
105
+ * `addedBefore` (server -> client):
106
+ - `collection`: string (collection name)
107
+ - `id`: string (document ID)
108
+ - `fields`: optional object with EJSON values
109
+ - `before`: string or null (the document ID to add the document before,
110
+ or null to add at the end)
111
+ * `movedBefore` (server -> client):
112
+ - `collection`: string
113
+ - `id`: string (the document ID)
114
+ - `before`: string or null (the document ID to move the document before, or
115
+ null to move to the end)
116
+
117
+ ### Procedure:
118
+
119
+ * The client specifies sets of information it is interested in by sending
120
+ `sub` messages to the server.
121
+
122
+ * At any time, but generally informed by the `sub` messages, the server can
123
+ send data messages to the client. Data consist of `added`, `changed`, and
124
+ `removed` messages. These messages model a local set of data the client
125
+ should keep track of.
126
+
127
+ - An `added` message indicates a document was added to the local set. The ID
128
+ of the document is specified in the `id` field, and the fields of the
129
+ document are specified in the `fields` field. Minimongo interperets the
130
+ string id field in a special way that transforms it to the _id field of
131
+ Mongo documents.
132
+
133
+ - A `changed` message indicates a document in the local set has new values
134
+ for some fields or has had some fields removed. The `id` field is the ID of
135
+ the document that has changed. The `fields` object, if present, indicates
136
+ fields in the document that should be replaced with new values. The
137
+ `cleared` field contains an array of fields that are no longer in the
138
+ document.
139
+
140
+ - A `removed` message indicates a document was removed from the local
141
+ set. The `id` field is the ID of the document.
142
+
143
+ * A collection is either ordered, or not. If a collection is ordered,
144
+ the `added` message is replaced by `addedBefore`, which
145
+ additionally contains the ID of the document after the one being
146
+ added in the `before` field. If the document is being added at the
147
+ end, `before` is set to null. For a given collection, the server
148
+ should only send `added` messages or `addedBefore` messages, not a
149
+ mixture of both, and should only send `movedBefore` messages for a
150
+ collection with `addedBefore` messages.
151
+
152
+ NOTE: The ordered collection DDP messages are not currently used by Meteor.
153
+ They will likely be used by Meteor in the future.
154
+
155
+ * The client maintains one set of data per collection. Each subscription does
156
+ not get its own datastore, but rather overlapping subscriptions cause the
157
+ server to send the union of facts about the one collection's data. For
158
+ example, if subscription A says document `x` has fields `{foo: 1, bar: 2}`
159
+ and subscription B says document `x` has fields `{foo: 1, baz:3}`, then the
160
+ client will be informed that document `x` has fields `{foo: 1, bar: 2, baz:
161
+ 3}`. If field values from different subscriptions conflict with each other,
162
+ the server should send one of the possible field values.
163
+
164
+ * When one or more subscriptions have finished sending their initial batch of
165
+ data, the server will send a `ready` message with their IDs.
166
+
167
+ ## Remote Procedure Calls:
168
+
169
+ ### Messages:
170
+
171
+ * `method` (client -> server):
172
+ - `method`: string (method name)
173
+ - `params`: optional array of EJSON items (parameters to the method)
174
+ - `id`: string (an arbitrary client-determined identifier for this method call)
175
+ * `result` (server -> client):
176
+ - `id`: string (the id passed to 'method')
177
+ - `error`: optional Error (an error thrown by the method (or method-not-found)
178
+ - `result`: optional EJSON item (the return value of the method, if any)
179
+ * `updated` (server -> client):
180
+ - `methods`: array of strings (ids passed to 'method', all of whose writes
181
+ have been reflected in data messages)
182
+
183
+ ### Procedure:
184
+
185
+ * The client sends a `method` message to the server
186
+
187
+ * The server responds with a `result` message to the client, carrying either
188
+ the result of the method call, or an appropriate error.
189
+
190
+ * Method calls can affect data that the client is subscribed to. Once the
191
+ server has finished sending the client all the relevant data messages based
192
+ on this procedure call, the server should send an `updated` message to the
193
+ client with this method's ID.
194
+
195
+ * There is no particular required ordering between `result` and `updated`
196
+ messages for a method call.
197
+
198
+ ## Errors:
199
+
200
+ Errors appear in `result` and `nosub` messages in an optional error field. An
201
+ error is an Object with the following fields:
202
+
203
+ * `error`: number
204
+ * `reason`: optional string
205
+ * `details`: optional string
206
+
207
+ Such an Error is used to represent errors raised by the method or subscription,
208
+ as well as an attempt to subscribe to an unknown subscription or call an unknown
209
+ method.
210
+
211
+ Other erroneous messages sent from the client to the server can result in
212
+ receiving a top-level `msg: 'error'` message in response. These conditions
213
+ include:
214
+
215
+ * sending messages which are not valid JSON objects
216
+ * unknown `msg` type
217
+ * other malformed client requests (not including required fields)
218
+ * sending anything other than `connect` as the first message, or sending
219
+ `connect` as a non-initial message
220
+
221
+ The error message contains the following fields:
222
+
223
+ * `reason`: string describing the error
224
+ * `offendingMessage`: if the original message parsed properly, it is included
225
+ here
226
+
227
+ ## Appendix: EJSON
228
+
229
+ EJSON is a way of embedding more than the built-in JSON types in JSON. It
230
+ supports all types built into JSON as plain JSON, plus the following:
231
+
232
+ **Dates:**
233
+
234
+ {"$date": MILLISECONDS_SINCE_EPOCH}
235
+
236
+ **Binary data:**
237
+
238
+ {"$binary": BASE_64_STRING}
239
+
240
+ (The base 64 string has `+` and `/` as characters 62 and 63, and has no maximum line length)
241
+
242
+ **Escaped things** that might otherwise look like EJSON types:
243
+
244
+ {"$escape": THING}
245
+
246
+ For example, here is the JSON value `{$date: 10000}` stored in EJSON:
247
+
248
+ {"$escape": {"$date": 10000}}
249
+
250
+ Note that escaping only causes keys to be literal for one level down; you can
251
+ have further EJSON inside. For example, the following is the key `$date` mapped
252
+ to a Date object:
253
+
254
+ {"$escape": {"$date": {"$date": 32491}}}
255
+
256
+ **User-specified types:**
257
+
258
+ {"$type": TYPENAME, "$value": VALUE}
259
+
260
+ Implementations of EJSON should try to preserve key order where they can. Users
261
+ of EJSON should not rely on key order, if possible.
262
+
263
+ > MongoDB relies on key order. When using EJSON with MongoDB, the
264
+ > implementation of EJSON must preserve key order.
data/lib/ruby_ddp.rb ADDED
@@ -0,0 +1,198 @@
1
+ #!usr/bin/env/ruby
2
+
3
+ #import gems
4
+ require "bundler"
5
+ Bundler.require
6
+
7
+ #ddp is just an extension of a websocket protocol
8
+ class DDP::Client < Faye::WebSocket::Client
9
+ attr_accessor :collections, :onconnect
10
+
11
+ #sets up basic server for ddp connections
12
+ def initialize( host, port = 8888, path = "websocket",
13
+ version = self.version, support )
14
+ super("http://#{host}:#{port}/#{path}") #setup websocket connection
15
+
16
+ self.init_event_handlers()
17
+
18
+ @callbacks = {}
19
+ @collections = {}
20
+ @subs = {}
21
+ @version = version
22
+ @session = nil
23
+ @curr_id = 0
24
+ @support = support
25
+
26
+ end
27
+
28
+ #sends connection message to server
29
+ def connect
30
+ self.send({
31
+ msg: :connect,
32
+ version: @version,
33
+ support: @support
34
+ })
35
+
36
+ #handle incoming response form the server
37
+ self.onmessage = lambda do |event|
38
+
39
+ res = JSON.parse(event.data)
40
+
41
+ if res.has_key? 'session'
42
+ #connection is successful - update session and record version
43
+ @session = res['session'].to_s
44
+ @@last_suc_version = @version
45
+
46
+ else #there was a failed connection
47
+ @version = res['version']
48
+ #retry the send request with the version specified by the server
49
+ self.send({
50
+ msg: :connect
51
+ version: @version,
52
+ support: @support
53
+ })
54
+ end
55
+
56
+ end
57
+ end
58
+
59
+ #sends a method call
60
+ def call(method, params = [], &blk)
61
+ id = self.next_id()
62
+ self.send(msg: 'method', id: id, method: method, params: params)
63
+ @callbacks[id] = blk
64
+ end
65
+
66
+ #subscripe to the data 'name' on the server
67
+ #this function will assign an arbitrary id to the subscription so that
68
+ #it may be tracked by the client
69
+ def subscribe(name, params, &blk)
70
+ id = self.next_id()
71
+ self.send({msg: 'sub', id: id, name: name, params: params})
72
+
73
+ @subs[name] = id
74
+ @callbacks[id] = blk
75
+ end
76
+
77
+ #client unsibscribes from the specified subscription
78
+ def unsubscribe(name)
79
+ id = @subs[name]
80
+
81
+ self.send(msg: 'unsub', id: id)
82
+ end
83
+
84
+ private
85
+ #updates and returns the next available ID number
86
+ def next_id
87
+ (@curr_id +=1).to_s
88
+ end
89
+
90
+ def send data
91
+ self.send(data.to_json)
92
+ end
93
+
94
+ #handlers for sever sents
95
+ def init_event_handlers
96
+ #begin the client session by attempting to connect to the server
97
+ self.onopen = lamda {self.connect()}
98
+
99
+ self.onmessage = lambda do |event|
100
+
101
+ data = JSON.parse(event.data)
102
+
103
+ if data.has_key? 'msg'
104
+
105
+ case(data['msg'])
106
+
107
+ #successfull connection!
108
+ when 'connected'
109
+ self.connect.call event
110
+
111
+ #inserts the given data into the client datastore
112
+ when 'addad'
113
+ if data.has_key? 'collection'
114
+ c_name = data['collection']
115
+ c_id = data['id']
116
+ @collections[c_name] ||= {}
117
+ data['params'].each { |k,v| @collections[c_name][c_id][k] = v }
118
+ end
119
+
120
+ #update the given data in the clients datastore
121
+ when 'changed'
122
+ if data.has_key 'collection'
123
+ c_name = data['collection']
124
+ c_id = data['id']
125
+ c_fields = data['fields']
126
+ to_clear = data['cleared']
127
+
128
+ #clear out fields specified at 'cleared'
129
+ to_clear.each do |f|
130
+ @collections[c_name][c_id][f].delete f
131
+ end
132
+ #insert/udpdate new data
133
+ c_fields.each do |k,v|
134
+ @collections[c_name][c_id][k] = v
135
+ end
136
+
137
+ end
138
+
139
+ #remove the given data from the client's datastore
140
+ when 'removed'
141
+ if data.has_key 'collection'
142
+ c_name = data['collection']
143
+ c_id = data['id']
144
+ @collections[c_name][c_id] = nil
145
+ end
146
+
147
+ when 'ready'
148
+ #adds the document *before* that whose id is specified in the id
149
+ #field of addedBefore
150
+ when 'addedBefore'
151
+
152
+ if data.has_key? 'collection'
153
+ c_name = data['collection']
154
+ c_id = data['id']
155
+ before = data['before']
156
+
157
+ @collections[c_name] ||= {}
158
+ #adds before if non-null
159
+ if before
160
+ data['params'].each { |k,v| @collections[c_name][c_id-1][k] = v }
161
+ else
162
+ data['params'].each { |k,v| @collections[c_name][c_id][k] = v }
163
+ end
164
+ end
165
+
166
+ when 'movedBefore'
167
+ if data.has_key? 'collection'
168
+ c_name = data['collection']
169
+ c_id = data['id']
170
+ before = data['before']
171
+
172
+ @collections[c_name] ||= {}
173
+ #adds before if non-null
174
+ if before
175
+ data['params'].each { |k,v| @collections[c_name][c_id-1][k] = v }
176
+ else
177
+ data['params'].each { |k,v| @collections[c_name][c_id][k] = v }
178
+ end
179
+ end
180
+
181
+ when 'ready'
182
+ puts "Server acks READY"
183
+
184
+ end #case
185
+ end # msg?
186
+
187
+ #there is no message ???
188
+
189
+ self.on(:close) = lambda {|e| puts "Connection Closed"}
190
+ end #message handler
191
+ end #init_event_handlrers
192
+
193
+ end #class
194
+
195
+
196
+
197
+
198
+
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby_ddp_client
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Brendan Ryan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: faye-websocket
16
+ requirement: &70316560 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70316560
25
+ - !ruby/object:Gem::Dependency
26
+ name: json
27
+ requirement: &70316320 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70316320
36
+ - !ruby/object:Gem::Dependency
37
+ name: bundler
38
+ requirement: &70316060 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: '1.3'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70316060
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: &70315850 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70315850
58
+ description: A simple ddp client (a la Meteor) written in Ruby
59
+ email:
60
+ - ryan.brendanjohn@gmail.com
61
+ executables: []
62
+ extensions: []
63
+ extra_rdoc_files: []
64
+ files:
65
+ - .gitignore
66
+ - Gemfile
67
+ - Gemfile.lock
68
+ - README.md
69
+ - lib/client.rb
70
+ - lib/ddp.md
71
+ - lib/ruby_ddp.rb
72
+ - lib/server.rb
73
+ homepage: http://github.com/bjryan2/ruby_ddp_client
74
+ licenses:
75
+ - MIT
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ! '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubyforge_project:
94
+ rubygems_version: 1.8.11
95
+ signing_key:
96
+ specification_version: 3
97
+ summary: ''
98
+ test_files: []