ruby_ddp_client 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|