ruby_ddp_client 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/Gemfile +8 -0
- data/README.md +0 -0
- data/lib/client.rb +181 -0
- data/lib/ddp.md +264 -0
- data/lib/ruby_ddp.rb +198 -0
- metadata +98 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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: []
|