entangled 0.0.25 → 0.0.26
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +45 -190
- data/lib/entangled/model.rb +30 -25
- data/lib/entangled/version.rb +1 -1
- data/spec/dummy/app/models/child.rb +6 -0
- data/spec/dummy/app/models/grandfather.rb +6 -0
- data/spec/dummy/app/models/grandmother.rb +6 -0
- data/spec/dummy/app/models/item.rb +2 -0
- data/spec/dummy/app/models/parent.rb +8 -0
- data/spec/dummy/db/migrate/20150329034759_create_children.rb +9 -0
- data/spec/dummy/db/migrate/20150329034900_create_parents.rb +10 -0
- data/spec/dummy/db/migrate/20150329034915_create_grandmothers.rb +8 -0
- data/spec/dummy/db/migrate/20150329034923_create_grandfathers.rb +8 -0
- data/spec/dummy/db/schema.rb +24 -1
- data/spec/dummy/public/app/controllers/list.js +0 -1
- data/spec/dummy/public/app/entangled/entangled.js +5 -6
- data/spec/dummy/public/test/services/entangled_test.js +3 -3
- data/spec/models/channels_spec.rb +112 -0
- data/spec/models/child_spec.rb +19 -0
- data/spec/models/grandfather_spec.rb +7 -0
- data/spec/models/grandmother_spec.rb +7 -0
- data/spec/models/item_spec.rb +1 -27
- data/spec/models/list_spec.rb +83 -102
- data/spec/models/parent_spec.rb +24 -0
- metadata +28 -4
- data/bower.json +0 -20
- data/entangled.js +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7687c95a8fad6d954ca7b9754d6ad17a40de5919
|
4
|
+
data.tar.gz: 37a20749306f1c7dac1a6f86bc92e1974b54306f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc6c25b58eb6e18956a9571e88f3933ee96a1b191f440ef3a895ac8bd4bf27cc0fd2bf81bde259584bb7079ce8ffa24b0d58e38c8e63fdb9b49a6bbb6e0a380b
|
7
|
+
data.tar.gz: ded5776561605f68d9a20b839645a4b1388b3a6209812db939c35287787f0208942a2fc09f19599840b8c00a235140edffb8ff5fb530bc9b759fc585f63a0d91
|
data/README.md
CHANGED
@@ -6,7 +6,7 @@ Real time is important. Users have come to expect real time behavior from every
|
|
6
6
|
|
7
7
|
Entangled stores and syncs data from ActiveRecord instantly across every device. It is a layer behind your models and controllers that pushes updates to all connected clients in real time. It is cross-browser compatible and offers real time validations.
|
8
8
|
|
9
|
-
Currently, Entangled runs on Rails 4.2 and Ruby 2.0
|
9
|
+
Currently, Entangled runs on Rails 4.2 and Ruby 2.0. In the front end, libraries are available in [plain JavaScript](https://github.com/dchacke/entangled-js) and for [Angular](https://github.com/dchacke/entangled-angular).
|
10
10
|
|
11
11
|
## Installation
|
12
12
|
Add this line to your application's Gemfile:
|
@@ -100,6 +100,18 @@ entangle only: :create
|
|
100
100
|
entangle only: [:create, :update]
|
101
101
|
```
|
102
102
|
|
103
|
+
Calling `entangled` creates the following channels (sticking with the example of a `Message` model):
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
# For the collection
|
107
|
+
"/messages"
|
108
|
+
|
109
|
+
# For a member, e.g. /messages/1
|
110
|
+
"/messages/:id"
|
111
|
+
```
|
112
|
+
|
113
|
+
`:id` being the record's id, just as with routes.
|
114
|
+
|
103
115
|
### Controllers
|
104
116
|
Your controllers will be a little more lightweight than in a standard restful Rails app. A restful-style controller is expected and should look like this:
|
105
117
|
|
@@ -152,6 +164,7 @@ Note the following:
|
|
152
164
|
- The `show`, `create`, `update`, and `destroy` actions will expect an instance variable with the singular name of your controller (e.g. `@message` in a `MessagesController`)
|
153
165
|
- The instance variables are sent to clients as stringified JSON
|
154
166
|
- Strong parameters are expected
|
167
|
+
- The path to your controllers' index action has to match the model's channel for the collection, and the path to your controller's show action has to match the model's channel for a single member (which it will automatically if you stay RESTful)
|
155
168
|
|
156
169
|
### Server
|
157
170
|
|
@@ -161,160 +174,14 @@ Remember to run Redis whenever you run your server:
|
|
161
174
|
$ redis-server
|
162
175
|
```
|
163
176
|
|
164
|
-
|
177
|
+
Redis is needed to subscribe and publish to the channels that are created by Entangled internally to communicate over websockets.
|
165
178
|
|
166
179
|
If you store your Redis instance in `$redis` or `REDIS` (e.g. in an initializer), Entangled will use that assigned instance so that you can configure Redis just like you're used to. Otherwise, Entangled will instantiate Redis itself and use its default settings.
|
167
180
|
|
168
|
-
|
169
|
-
You will need to configure your client to create Websockets and understand incoming requests on those sockets. In order to use the helper methods for the front end provided by the Entangled Angular library, you must use Angular in your front end. The use of Angular as counterpart of this gem is highly recommended, since its inherent two way data binding complements the real time functionality of this gem nicely.
|
170
|
-
|
171
|
-
### Installation
|
172
|
-
You can either download or reference the file `entangled.js` from this repository, or simply install it with Bower:
|
173
|
-
|
174
|
-
```shell
|
175
|
-
$ bower install entangled
|
176
|
-
```
|
177
|
-
|
178
|
-
Then include it in your HTML.
|
179
|
-
|
180
|
-
Lastly, add the Entangled module as a dependency to your Angular app:
|
181
|
-
|
182
|
-
```javascript
|
183
|
-
angular.module('appName', ['entangled']);
|
184
|
-
```
|
185
|
-
|
186
|
-
### Usage
|
187
|
-
Entangled is best used within Angular services. For example, consider a `Message` service for a chat app:
|
188
|
-
|
189
|
-
```javascript
|
190
|
-
app.factory('Message', function(Entangled) {
|
191
|
-
return new Entangled('ws://localhost:3000/messages');
|
192
|
-
});
|
193
|
-
```
|
194
|
-
|
195
|
-
In the above example, first we inject Entangled into our service, then instantiate a new Entangled object and return it. The Entangled object takes one argument when instantiated: the URL of your resource's index action (in this case, `/messages`). Note that the socket URL looks just like a standard restful URL with http, except that the protocol part has been switched with `ws` to use the websocket protocol. Also note that you need to use `wss` instead if you want to use SSL.
|
196
|
-
|
197
|
-
The Entangled service comes with these functions:
|
198
|
-
|
199
|
-
- `new(params)`
|
200
|
-
- `create(params, callback)`
|
201
|
-
- `find(id, callback)`
|
202
|
-
- `all(callback)`
|
203
|
-
|
204
|
-
...and the following functions on returned objects:
|
205
|
-
|
206
|
-
- `$save(callback)`
|
207
|
-
- `$update(params, callback)`
|
208
|
-
- `$destroy(callback)`
|
209
|
-
|
210
|
-
They're just like class and instance methods in Active Record.
|
211
|
-
|
212
|
-
In your controller, you could then inject that `Message` service and use it like so:
|
213
|
-
|
214
|
-
```javascript
|
215
|
-
// To instantiate a blank message, e.g. for a form;
|
216
|
-
// You can optionally pass in an object to new() to
|
217
|
-
// set some default values
|
218
|
-
$scope.message = Message.new();
|
219
|
-
|
220
|
-
// To instantiate and save a message in one go
|
221
|
-
Message.create({ body: 'text' }, function(message) {
|
222
|
-
$scope.$apply(function() {
|
223
|
-
$scope.message = message;
|
224
|
-
});
|
225
|
-
});
|
226
|
-
|
227
|
-
// To retrieve a specific message from the server
|
228
|
-
// with id 1 and subscribe to its channel
|
229
|
-
Message.find(1, function(message) {
|
230
|
-
$scope.$apply(function() {
|
231
|
-
$scope.message = message;
|
232
|
-
});
|
233
|
-
});
|
234
|
-
|
235
|
-
// To retrieve all messages from the server and
|
236
|
-
// subscribe to the collection's channel
|
237
|
-
Message.all(function(messages) {
|
238
|
-
$scope.$apply(function() {
|
239
|
-
$scope.messages = messages;
|
240
|
-
});
|
241
|
-
});
|
242
|
-
|
243
|
-
// To store a newly instantiated or update an existing message.
|
244
|
-
// If saved successfully, $scope.message is updated in place
|
245
|
-
// with the attributes id, created_at and updated_at
|
246
|
-
$scope.message.body = 'new body';
|
247
|
-
$scope.message.$save(function() {
|
248
|
-
// Do stuff after save
|
249
|
-
});
|
250
|
-
|
251
|
-
// To update a newly instantiated or existing message in place.
|
252
|
-
// If updated successfully, $scope.message is updated in place
|
253
|
-
// with the attributes id, created_at and updated_at
|
254
|
-
$scope.message.$update({ body: 'new body' }, function() {
|
255
|
-
// Do stuff after update
|
256
|
-
});
|
257
|
-
|
258
|
-
// To destroy a message
|
259
|
-
$scope.message.$destroy(function() {
|
260
|
-
// Do stuff after destroy
|
261
|
-
});
|
262
|
-
```
|
263
|
-
|
264
|
-
All functions above will interact with your server's controllers in real time. Your scope variables will always reflect your server's most current data.
|
265
|
-
|
266
|
-
#### Validations
|
267
|
-
Objects from the Entangled service automatically receive ActiveRecord's error messages from your model when you `$save()`. An additional property called `errors` containing the error messages is available, formatted the same way you're used to from calling `.errors` on a model in Rails.
|
268
|
-
|
269
|
-
For example, consider the following scenario:
|
270
|
-
|
271
|
-
```ruby
|
272
|
-
# Message model (Rails)
|
273
|
-
validates :body, presence: true
|
274
|
-
```
|
275
|
-
|
276
|
-
```javascript
|
277
|
-
// Controller (Angular)
|
278
|
-
$scope.message.$save(function() {
|
279
|
-
console.log($scope.message.errors);
|
280
|
-
// => { body: ["can't be blank"] }
|
281
|
-
});
|
282
|
-
```
|
283
|
-
|
284
|
-
You could then display these error messages to your users.
|
285
|
-
|
286
|
-
To check if a resource is valid, you can use `$valid()` and `$invalid()`. Both functions return booleans. For example:
|
287
|
-
|
288
|
-
```javascript
|
289
|
-
$scope.message.$save(function() {
|
290
|
-
// Check if record has no errors
|
291
|
-
if ($scope.message.$valid()) { // similar to ActiveRecord's .valid?
|
292
|
-
alert('Yay!');
|
293
|
-
}
|
294
|
-
|
295
|
-
// Check if record errors
|
296
|
-
if ($scope.message.$invalid()) { // similar to ActiveRecord's .invalid?
|
297
|
-
alert('Nay!');
|
298
|
-
}
|
299
|
-
});
|
300
|
-
```
|
301
|
-
|
302
|
-
Note that `$valid()` and `$invalid()` should only be used after $saving a resource, i.e. in the callback of `$save`, since they don't actually invoke server side validations. They only check if a resource contains errors.
|
303
|
-
|
304
|
-
#### Persistence
|
305
|
-
Just as with ActiveRecord's `persisted?` method, you can use `$persisted()` on an object to check if it was successfully stored in the database.
|
306
|
-
|
307
|
-
```javascript
|
308
|
-
$scope.message.$persisted();
|
309
|
-
// => true or false
|
310
|
-
```
|
311
|
-
|
312
|
-
#### Associations
|
181
|
+
### Associations
|
313
182
|
What if you want to only fetch and subscribe to children that belong to a specific parent? Or maybe you want to create a child in your front end and assign it to a specific parent?
|
314
183
|
|
315
|
-
|
316
|
-
|
317
|
-
For example, imagine the following Parent > Children relationship in your models:
|
184
|
+
Imagine the following Parent > Children relationship in your models:
|
318
185
|
|
319
186
|
```ruby
|
320
187
|
class Parent < ActiveRecord::Base
|
@@ -332,7 +199,25 @@ class Child < ActiveRecord::Base
|
|
332
199
|
end
|
333
200
|
```
|
334
201
|
|
335
|
-
|
202
|
+
Entangled takes note of every `belongs_to` association and creates two additional channels for each `belongs_to` association in the child model:
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
"/parents/:parent_id/children"
|
206
|
+
"/parents/:parent_id/children/:id"
|
207
|
+
```
|
208
|
+
|
209
|
+
So in total, the `Child` model will have all of the following channels:
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
"/children"
|
213
|
+
"/children/:id"
|
214
|
+
"/parents/:parent_id/children"
|
215
|
+
"/parents/:parent_id/children/:id"
|
216
|
+
```
|
217
|
+
|
218
|
+
Channels are deeply nested for child/parent/grandparent etc associations. There is no limit. To get a list of all available channels on a record, you can call the method `channels` on any entangled instance.
|
219
|
+
|
220
|
+
To reflect associations in your front end, you just need to add three things to your app:
|
336
221
|
|
337
222
|
- Nest your routes so that they resemble the parent/child relationship:
|
338
223
|
|
@@ -358,7 +243,9 @@ class ChildrenController < ApplicationController
|
|
358
243
|
# Create child of specific parent
|
359
244
|
def create
|
360
245
|
broadcast do
|
361
|
-
@child =
|
246
|
+
@child = Child.new(child_params)
|
247
|
+
@child.parent_id = params[:parent_id]
|
248
|
+
@child.save
|
362
249
|
end
|
363
250
|
end
|
364
251
|
|
@@ -366,56 +253,24 @@ class ChildrenController < ApplicationController
|
|
366
253
|
end
|
367
254
|
```
|
368
255
|
|
369
|
-
|
370
|
-
|
371
|
-
```javascript
|
372
|
-
app.factory('Parent', function(Entangled) {
|
373
|
-
// Instantiate Entangled service
|
374
|
-
var Parent = new Entangled('ws://localhost:3000/parents');
|
375
|
-
|
376
|
-
// Set up association
|
377
|
-
Parent.hasMany('children');
|
378
|
-
|
379
|
-
return Parent;
|
380
|
-
});
|
381
|
-
```
|
382
|
-
|
383
|
-
This makes a `children()` function available on your parent records on which you can chain all other functions to fetch/manipulate data:
|
384
|
-
|
385
|
-
```javascript
|
386
|
-
Parent.find(1, function(parent) {
|
387
|
-
parent.children().all(function(children) {
|
388
|
-
// children here all belong to parent with id 1
|
389
|
-
});
|
390
|
-
|
391
|
-
parent.children().find(1, function(child) {
|
392
|
-
// child has id 1 and belongs to parent with id 1
|
393
|
-
});
|
394
|
-
|
395
|
-
parent.children().create({ foo: 'bar' }, function(child) {
|
396
|
-
// child has been persisted and associated with parent
|
397
|
-
});
|
256
|
+
Check out the JavaScript guides to implement associations on the client.
|
398
257
|
|
399
|
-
|
400
|
-
|
401
|
-
```
|
402
|
-
|
403
|
-
This is the way to go if you want to fetch records that only belong to a certain record, or create records that should belong to a parent record. In short, it is ideal to scope records to parent records.
|
258
|
+
## The Client
|
259
|
+
Pick if you want to use Entangled with plain JavaScript or with Angular:
|
404
260
|
|
405
|
-
|
261
|
+
- [entangled-js](https://github.com/dchacke/entangled-js)
|
262
|
+
- [entangled-angular](https://github.com/dchacke/entangled-angular)
|
406
263
|
|
407
264
|
## Planning Your Infrastructure
|
408
265
|
This gem is best used for Rails apps that serve as APIs only and are not concerned with rendering views, since Entangled controllers cannot render views. A front end separate from your Rails app is recommended, either in your Rails app's public directory, or a separate front end app altogether.
|
409
266
|
|
410
267
|
## Limitations
|
411
|
-
The gem relies heavily on convention over configuration and currently only works with restful style controllers as shown above. More features will be available soon
|
268
|
+
The gem relies heavily on convention over configuration and currently only works with restful style controllers as shown above. More features will be available soon. See the list of development priorities below.
|
412
269
|
|
413
270
|
## Development Priorities
|
414
271
|
The following features are to be implemented next:
|
415
272
|
|
416
|
-
- Allow for more than one level of nesting of `#channels` in `Entangled::Model`
|
417
273
|
- Support `belongsTo` in front end
|
418
|
-
- Support deeply nested `belongs_to`, e.g. `Parent > Child > Grandchild`
|
419
274
|
- Support `has_one` association in back end and front end
|
420
275
|
- Add offline capabilities
|
421
276
|
- Add authentication - with JWT?
|
data/lib/entangled/model.rb
CHANGED
@@ -77,40 +77,38 @@ module Entangled
|
|
77
77
|
# a collection channel, i.e. /tacos, and a member
|
78
78
|
# channel, i.e. /tacos/1, for direct access.
|
79
79
|
#
|
80
|
-
# If the model belongs_to other models,
|
81
|
-
#
|
82
|
-
#
|
83
|
-
|
84
|
-
# parents/1/children/1, leaving a total of four channels
|
85
|
-
def channels
|
80
|
+
# If the model belongs_to other models, nested channels
|
81
|
+
# are created for all parents, grand parents, etc
|
82
|
+
# recursively
|
83
|
+
def channels(tail = '')
|
86
84
|
channels = []
|
87
85
|
plural_name = self.class.name.underscore.pluralize
|
88
86
|
|
89
|
-
# Add collection
|
90
|
-
|
91
|
-
|
92
|
-
#
|
93
|
-
|
87
|
+
# Add collection channel for child only. If the tails
|
88
|
+
# is not empty, the function is being called recursively
|
89
|
+
# for one of the parents, for which only member channels
|
90
|
+
# are needed
|
91
|
+
if tail.empty?
|
92
|
+
collection_channel = "/#{plural_name}" + tail
|
93
|
+
channels << collection_channel
|
94
|
+
end
|
94
95
|
|
95
|
-
#
|
96
|
-
|
96
|
+
# Add member channel
|
97
|
+
member_channel = "/#{plural_name}/#{to_param}" + tail
|
98
|
+
channels << member_channel
|
97
99
|
|
98
100
|
# Add nested channels for each parent
|
99
|
-
parents.
|
100
|
-
#
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
# Add collection's channel nested under parent's member channel
|
107
|
-
channels << "/#{parent_plural_name}/#{parent.to_param}/#{plural_name}"
|
101
|
+
parents.each do |parent|
|
102
|
+
# Only recusively add collection channel
|
103
|
+
# for child
|
104
|
+
if tail.empty?
|
105
|
+
channels << parent.channels(collection_channel)
|
106
|
+
end
|
108
107
|
|
109
|
-
|
110
|
-
channels << "/#{parent_plural_name}/#{parent.to_param}/#{plural_name}/#{to_param}"
|
108
|
+
channels << parent.channels(member_channel)
|
111
109
|
end
|
112
110
|
|
113
|
-
channels
|
111
|
+
channels.flatten
|
114
112
|
end
|
115
113
|
|
116
114
|
private
|
@@ -132,6 +130,13 @@ module Entangled
|
|
132
130
|
resource: self
|
133
131
|
}.to_json
|
134
132
|
end
|
133
|
+
|
134
|
+
# Find parent classes from belongs_to associations
|
135
|
+
def parents
|
136
|
+
self.class.
|
137
|
+
reflect_on_all_associations(:belongs_to).
|
138
|
+
map{ |a| send(a.name) }
|
139
|
+
end
|
135
140
|
end
|
136
141
|
|
137
142
|
def self.included(receiver)
|
data/lib/entangled/version.rb
CHANGED
data/spec/dummy/db/schema.rb
CHANGED
@@ -11,7 +11,7 @@
|
|
11
11
|
#
|
12
12
|
# It's strongly recommended that you check this file into your version control system.
|
13
13
|
|
14
|
-
ActiveRecord::Schema.define(version:
|
14
|
+
ActiveRecord::Schema.define(version: 20150329034923) do
|
15
15
|
|
16
16
|
create_table "barfoos", force: :cascade do |t|
|
17
17
|
t.text "body"
|
@@ -25,6 +25,12 @@ ActiveRecord::Schema.define(version: 20150316034305) do
|
|
25
25
|
t.datetime "updated_at", null: false
|
26
26
|
end
|
27
27
|
|
28
|
+
create_table "children", force: :cascade do |t|
|
29
|
+
t.integer "parent_id"
|
30
|
+
t.datetime "created_at", null: false
|
31
|
+
t.datetime "updated_at", null: false
|
32
|
+
end
|
33
|
+
|
28
34
|
create_table "foobars", force: :cascade do |t|
|
29
35
|
t.text "body"
|
30
36
|
t.datetime "created_at", null: false
|
@@ -37,6 +43,16 @@ ActiveRecord::Schema.define(version: 20150316034305) do
|
|
37
43
|
t.datetime "updated_at", null: false
|
38
44
|
end
|
39
45
|
|
46
|
+
create_table "grandfathers", force: :cascade do |t|
|
47
|
+
t.datetime "created_at", null: false
|
48
|
+
t.datetime "updated_at", null: false
|
49
|
+
end
|
50
|
+
|
51
|
+
create_table "grandmothers", force: :cascade do |t|
|
52
|
+
t.datetime "created_at", null: false
|
53
|
+
t.datetime "updated_at", null: false
|
54
|
+
end
|
55
|
+
|
40
56
|
create_table "items", force: :cascade do |t|
|
41
57
|
t.string "name"
|
42
58
|
t.boolean "complete", default: false
|
@@ -51,4 +67,11 @@ ActiveRecord::Schema.define(version: 20150316034305) do
|
|
51
67
|
t.datetime "updated_at", null: false
|
52
68
|
end
|
53
69
|
|
70
|
+
create_table "parents", force: :cascade do |t|
|
71
|
+
t.integer "grandmother_id"
|
72
|
+
t.integer "grandfather_id"
|
73
|
+
t.datetime "created_at", null: false
|
74
|
+
t.datetime "updated_at", null: false
|
75
|
+
end
|
76
|
+
|
54
77
|
end
|
@@ -9,8 +9,8 @@ angular.module('entangled', [])
|
|
9
9
|
// methods $save(), $destroy, and others. A Resource also
|
10
10
|
// stores the socket's URL it was retrieved from so it
|
11
11
|
// can be reused for other requests.
|
12
|
-
|
13
|
-
// Assign
|
12
|
+
function Resource(params, webSocketUrl, hasMany) {
|
13
|
+
// Assign properties
|
14
14
|
for (var key in params) {
|
15
15
|
// Skip inherent object properties
|
16
16
|
if (params.hasOwnProperty(key)) {
|
@@ -160,7 +160,7 @@ angular.module('entangled', [])
|
|
160
160
|
|
161
161
|
// Resources wraps all individual Resource objects
|
162
162
|
// in a collection.
|
163
|
-
|
163
|
+
function Resources(resources, webSocketUrl, hasMany) {
|
164
164
|
this.all = [];
|
165
165
|
|
166
166
|
for (var i = 0; i < resources.length; i++) {
|
@@ -178,9 +178,7 @@ angular.module('entangled', [])
|
|
178
178
|
// Entangled is a constructor that takes the URL
|
179
179
|
// of the index action on the server where the
|
180
180
|
// Resources can be retrieved.
|
181
|
-
|
182
|
-
this.className = 'Entangled';
|
183
|
-
|
181
|
+
function Entangled(webSocketUrl) {
|
184
182
|
// Store the root URL that sockets
|
185
183
|
// will connect to
|
186
184
|
this.webSocketUrl = webSocketUrl;
|
@@ -297,5 +295,6 @@ angular.module('entangled', [])
|
|
297
295
|
}.bind(this);
|
298
296
|
};
|
299
297
|
|
298
|
+
// Return Entangled object as Angular service
|
300
299
|
return Entangled;
|
301
300
|
});
|
@@ -15,9 +15,9 @@ describe('Entangled', function() {
|
|
15
15
|
List.hasMany('items');
|
16
16
|
}));
|
17
17
|
|
18
|
-
describe('
|
18
|
+
describe('constructor', function() {
|
19
19
|
it('is "Entangled"', function() {
|
20
|
-
expect(List.
|
20
|
+
expect(List.constructor.name).toBe('Entangled');
|
21
21
|
});
|
22
22
|
});
|
23
23
|
|
@@ -255,7 +255,7 @@ describe('Entangled', function() {
|
|
255
255
|
// an instance of Entangled, meaning
|
256
256
|
// in turn that all class and instance
|
257
257
|
// methods are available on it
|
258
|
-
expect(list.items().
|
258
|
+
expect(list.items().constructor.name).toBe('Entangled');
|
259
259
|
done();
|
260
260
|
});
|
261
261
|
});
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# Test channels inferred from relationships
|
4
|
+
RSpec.describe 'Channels', type: :model do
|
5
|
+
let!(:grandmother) { Grandmother.create }
|
6
|
+
let!(:grandfather) { Grandfather.create }
|
7
|
+
|
8
|
+
let!(:parent) do
|
9
|
+
Parent.create(
|
10
|
+
grandmother_id: grandmother.id,
|
11
|
+
grandfather_id: grandfather.id
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
let!(:child) { Child.create(parent_id: parent.id) }
|
16
|
+
|
17
|
+
describe "grandmother's channels" do
|
18
|
+
it 'has two channels' do
|
19
|
+
expect(grandmother.channels.size).to eq 2
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'has a collection channel' do
|
23
|
+
expect(grandmother.channels).to include '/grandmothers'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'has a member channel' do
|
27
|
+
expect(grandmother.channels).to include "/grandmothers/#{grandmother.to_param}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "grandfather's channels" do
|
32
|
+
it 'has two channels' do
|
33
|
+
expect(grandfather.channels.size).to eq 2
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'has a collection channel' do
|
37
|
+
expect(grandfather.channels).to include '/grandfathers'
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'has a member channel' do
|
41
|
+
expect(grandfather.channels).to include "/grandfathers/#{grandfather.to_param}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "parent's channels" do
|
46
|
+
it 'has six channels' do
|
47
|
+
expect(parent.channels.size).to eq 6
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'has a collection channel' do
|
51
|
+
expect(parent.channels).to include '/parents'
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'has a member channel' do
|
55
|
+
expect(parent.channels).to include "/parents/#{parent.to_param}"
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'has a collection channel nested under its grandmother' do
|
59
|
+
expect(parent.channels).to include "/grandmothers/#{grandmother.to_param}/parents"
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'has a member channel nested under its grandmother' do
|
63
|
+
expect(parent.channels).to include "/grandmothers/#{grandmother.to_param}/parents/#{parent.to_param}"
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'has a collection channel nested under its grandfather' do
|
67
|
+
expect(parent.channels).to include "/grandfathers/#{grandfather.to_param}/parents"
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'has a member channel nested under its grandfather' do
|
71
|
+
expect(parent.channels).to include "/grandfathers/#{grandfather.to_param}/parents/#{parent.to_param}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "child's channels" do
|
76
|
+
it 'has eight channels' do
|
77
|
+
expect(child.channels.size).to eq 8
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'has a collection channel' do
|
81
|
+
expect(child.channels).to include '/children'
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'has a member channel' do
|
85
|
+
expect(child.channels).to include "/children/#{child.to_param}"
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'has a collection channel nested under its parent' do
|
89
|
+
expect(child.channels).to include "/parents/#{parent.to_param}/children"
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'has a member channel nested under its parent' do
|
93
|
+
expect(child.channels).to include "/parents/#{parent.to_param}/children/#{child.to_param}"
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'has a collection channel nested under its parent and grandmother' do
|
97
|
+
expect(child.channels).to include "/grandmothers/#{grandmother.to_param}/parents/#{parent.to_param}/children"
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'has a member channel nested under its parent and grandmother' do
|
101
|
+
expect(child.channels).to include "/grandmothers/#{grandmother.to_param}/parents/#{parent.to_param}/children/#{child.to_param}"
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'has a collection channel nested under its parent and grandfather' do
|
105
|
+
expect(child.channels).to include "/grandfathers/#{grandfather.to_param}/parents/#{parent.to_param}/children"
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'has a member channel nested under its parent and grandfather' do
|
109
|
+
expect(child.channels).to include "/grandfathers/#{grandfather.to_param}/parents/#{parent.to_param}/children/#{child.to_param}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Child, type: :model do
|
4
|
+
describe 'Associations' do
|
5
|
+
it { is_expected.to belong_to :parent }
|
6
|
+
end
|
7
|
+
|
8
|
+
describe 'Attributes' do
|
9
|
+
it { is_expected.to respond_to :parent_id }
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'Database' do
|
13
|
+
it { is_expected.to have_db_column(:parent_id).of_type(:integer) }
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'Validations' do
|
17
|
+
it { is_expected.to validate_presence_of :parent }
|
18
|
+
end
|
19
|
+
end
|
data/spec/models/item_spec.rb
CHANGED
@@ -17,34 +17,8 @@ RSpec.describe Item, type: :model do
|
|
17
17
|
it { is_expected.to belong_to :list }
|
18
18
|
end
|
19
19
|
|
20
|
-
describe 'Methods' do
|
21
|
-
let(:list) { List.create(name: 'foo') }
|
22
|
-
let(:item) { list.items.create(name: 'bar') }
|
23
|
-
|
24
|
-
describe '#channels' do
|
25
|
-
it 'is an array of channels' do
|
26
|
-
expect(item.channels).to be_an Array
|
27
|
-
end
|
28
|
-
|
29
|
-
it "includes the collection's channel" do
|
30
|
-
expect(item.channels).to include '/items'
|
31
|
-
end
|
32
|
-
|
33
|
-
it "includes the item's direct channel" do
|
34
|
-
expect(item.channels).to include "/items/#{item.to_param}"
|
35
|
-
end
|
36
|
-
|
37
|
-
it "includes the collection's nested channel" do
|
38
|
-
expect(item.channels).to include "/lists/#{list.to_param}/items"
|
39
|
-
end
|
40
|
-
|
41
|
-
it "includes the item's nested channel" do
|
42
|
-
expect(item.channels).to include "/lists/#{list.to_param}/items/#{item.to_param}"
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
20
|
describe 'Validations' do
|
48
21
|
it { is_expected.to validate_presence_of :list }
|
22
|
+
it { is_expected.to validate_presence_of :name }
|
49
23
|
end
|
50
24
|
end
|
data/spec/models/list_spec.rb
CHANGED
@@ -16,132 +16,113 @@ RSpec.describe List, type: :model do
|
|
16
16
|
describe 'Methods' do
|
17
17
|
let(:list) { List.create(name: 'foo') }
|
18
18
|
|
19
|
-
describe '#
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
it "includes the collection's channel" do
|
25
|
-
expect(list.channels).to include '/lists'
|
26
|
-
end
|
27
|
-
|
28
|
-
it "includes the member's channel" do
|
29
|
-
expect(list.channels).to include "/lists/#{list.to_param}"
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
describe '#entangle' do
|
35
|
-
let(:stub_redis) do
|
36
|
-
mock("redis").tap do |redis|
|
37
|
-
redis.stubs(:publish)
|
38
|
-
Redis.stubs(:new).returns(redis)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
describe 'creation' do
|
43
|
-
let(:list) { List.create(name: 'foo') }
|
44
|
-
|
45
|
-
it 'broadcasts the creation to the collection channel' do
|
46
|
-
redis = stub_redis
|
47
|
-
|
48
|
-
list.channels.each do |channel|
|
49
|
-
expect(redis).to have_received(:publish).with(
|
50
|
-
channel, {
|
51
|
-
action: :create,
|
52
|
-
resource: list
|
53
|
-
}.to_json
|
54
|
-
)
|
19
|
+
describe '#entangle' do
|
20
|
+
let(:stub_redis) do
|
21
|
+
mock("redis").tap do |redis|
|
22
|
+
redis.stubs(:publish)
|
23
|
+
Redis.stubs(:new).returns(redis)
|
55
24
|
end
|
56
25
|
end
|
57
26
|
|
58
|
-
|
59
|
-
|
27
|
+
describe 'creation' do
|
28
|
+
it 'broadcasts the creation to the collection channel' do
|
29
|
+
redis = stub_redis
|
30
|
+
|
31
|
+
list.channels.each do |channel|
|
32
|
+
expect(redis).to have_received(:publish).with(
|
33
|
+
channel, {
|
34
|
+
action: :create,
|
35
|
+
resource: list
|
36
|
+
}.to_json
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
60
40
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
41
|
+
it 'broadcasts the creation to the member channel' do
|
42
|
+
redis = stub_redis
|
43
|
+
|
44
|
+
list.channels.each do |channel|
|
45
|
+
expect(redis).to have_received(:publish).with(
|
46
|
+
channel, {
|
47
|
+
action: :create,
|
48
|
+
resource: list
|
49
|
+
}.to_json
|
50
|
+
)
|
51
|
+
end
|
68
52
|
end
|
69
53
|
end
|
70
|
-
end
|
71
54
|
|
72
|
-
|
73
|
-
|
55
|
+
describe 'update' do
|
56
|
+
it 'broadcasts the update to the collection channel' do
|
57
|
+
redis = stub_redis
|
74
58
|
|
75
|
-
|
76
|
-
redis = stub_redis
|
59
|
+
list.update(name: 'bar')
|
77
60
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
)
|
61
|
+
list.channels.each do |channel|
|
62
|
+
expect(redis).to have_received(:publish).with(
|
63
|
+
channel, {
|
64
|
+
action: :update,
|
65
|
+
resource: list
|
66
|
+
}.to_json
|
67
|
+
)
|
68
|
+
end
|
87
69
|
end
|
88
|
-
end
|
89
70
|
|
90
|
-
|
91
|
-
|
71
|
+
it 'broadcasts the update to the member channel' do
|
72
|
+
redis = stub_redis
|
92
73
|
|
93
|
-
|
74
|
+
list.update(name: 'bar')
|
94
75
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
76
|
+
list.channels.each do |channel|
|
77
|
+
expect(redis).to have_received(:publish).with(
|
78
|
+
channel, {
|
79
|
+
action: :update,
|
80
|
+
resource: list
|
81
|
+
}.to_json
|
82
|
+
)
|
83
|
+
end
|
102
84
|
end
|
103
85
|
end
|
104
|
-
end
|
105
86
|
|
106
|
-
|
107
|
-
|
87
|
+
describe 'destruction' do
|
88
|
+
it 'broadcasts the destruction to the collection channel' do
|
89
|
+
redis = stub_redis
|
108
90
|
|
109
|
-
|
110
|
-
redis = stub_redis
|
91
|
+
list.destroy
|
111
92
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
)
|
93
|
+
list.channels.each do |channel|
|
94
|
+
expect(redis).to have_received(:publish).with(
|
95
|
+
channel, {
|
96
|
+
action: :destroy,
|
97
|
+
resource: list
|
98
|
+
}.to_json
|
99
|
+
)
|
100
|
+
end
|
121
101
|
end
|
122
|
-
end
|
123
102
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
103
|
+
it 'broadcasts the destruction to the member channel' do
|
104
|
+
redis = stub_redis
|
105
|
+
|
106
|
+
list.destroy
|
107
|
+
|
108
|
+
list.channels.each do |channel|
|
109
|
+
expect(redis).to have_received(:publish).with(
|
110
|
+
channel, {
|
111
|
+
action: :destroy,
|
112
|
+
resource: list
|
113
|
+
}.to_json
|
114
|
+
)
|
115
|
+
end
|
136
116
|
end
|
137
117
|
end
|
138
118
|
end
|
139
|
-
end
|
140
119
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
120
|
+
describe '#as_json' do
|
121
|
+
let(:list) { List.create }
|
122
|
+
|
123
|
+
it 'includes errors' do
|
124
|
+
expect(list.as_json["errors"][:name]).to include "can't be blank"
|
125
|
+
end
|
145
126
|
end
|
146
127
|
end
|
147
128
|
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Parent, type: :model do
|
4
|
+
describe 'Associations' do
|
5
|
+
it { is_expected.to have_many(:children).dependent(:destroy) }
|
6
|
+
it { is_expected.to belong_to :grandmother }
|
7
|
+
it { is_expected.to belong_to :grandfather }
|
8
|
+
end
|
9
|
+
|
10
|
+
describe 'Attributes' do
|
11
|
+
it { is_expected.to respond_to :grandmother_id }
|
12
|
+
it { is_expected.to respond_to :grandfather_id }
|
13
|
+
end
|
14
|
+
|
15
|
+
describe 'Database' do
|
16
|
+
it { is_expected.to have_db_column(:grandmother_id).of_type(:integer) }
|
17
|
+
it { is_expected.to have_db_column(:grandfather_id).of_type(:integer) }
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'Validations' do
|
21
|
+
it { is_expected.to validate_presence_of :grandmother }
|
22
|
+
it { is_expected.to validate_presence_of :grandfather }
|
23
|
+
end
|
24
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: entangled
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.26
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dennis Charles Hackethal
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-03-
|
11
|
+
date: 2015-03-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -177,9 +177,7 @@ files:
|
|
177
177
|
- LICENSE.txt
|
178
178
|
- README.md
|
179
179
|
- Rakefile
|
180
|
-
- bower.json
|
181
180
|
- entangled.gemspec
|
182
|
-
- entangled.js
|
183
181
|
- lib/entangled.rb
|
184
182
|
- lib/entangled/controller.rb
|
185
183
|
- lib/entangled/helpers.rb
|
@@ -195,12 +193,16 @@ files:
|
|
195
193
|
- spec/dummy/app/models/.keep
|
196
194
|
- spec/dummy/app/models/bar.rb
|
197
195
|
- spec/dummy/app/models/barfoo.rb
|
196
|
+
- spec/dummy/app/models/child.rb
|
198
197
|
- spec/dummy/app/models/concerns/.keep
|
199
198
|
- spec/dummy/app/models/foo.rb
|
200
199
|
- spec/dummy/app/models/foobar.rb
|
200
|
+
- spec/dummy/app/models/grandfather.rb
|
201
|
+
- spec/dummy/app/models/grandmother.rb
|
201
202
|
- spec/dummy/app/models/item.rb
|
202
203
|
- spec/dummy/app/models/list.rb
|
203
204
|
- spec/dummy/app/models/message.rb
|
205
|
+
- spec/dummy/app/models/parent.rb
|
204
206
|
- spec/dummy/bin/bundle
|
205
207
|
- spec/dummy/bin/rails
|
206
208
|
- spec/dummy/bin/rake
|
@@ -232,6 +234,10 @@ files:
|
|
232
234
|
- spec/dummy/db/migrate/20150314054548_create_lists.rb
|
233
235
|
- spec/dummy/db/migrate/20150314055847_create_items.rb
|
234
236
|
- spec/dummy/db/migrate/20150316034305_drop_messages.rb
|
237
|
+
- spec/dummy/db/migrate/20150329034759_create_children.rb
|
238
|
+
- spec/dummy/db/migrate/20150329034900_create_parents.rb
|
239
|
+
- spec/dummy/db/migrate/20150329034915_create_grandmothers.rb
|
240
|
+
- spec/dummy/db/migrate/20150329034923_create_grandfathers.rb
|
235
241
|
- spec/dummy/db/schema.rb
|
236
242
|
- spec/dummy/log/.keep
|
237
243
|
- spec/dummy/public/Gruntfile.js
|
@@ -248,9 +254,14 @@ files:
|
|
248
254
|
- spec/dummy/public/views/lists/index.html
|
249
255
|
- spec/dummy/public/views/lists/show.html
|
250
256
|
- spec/helpers/redis_spec.rb
|
257
|
+
- spec/models/channels_spec.rb
|
258
|
+
- spec/models/child_spec.rb
|
259
|
+
- spec/models/grandfather_spec.rb
|
260
|
+
- spec/models/grandmother_spec.rb
|
251
261
|
- spec/models/inclusion_exclusion_spec.rb
|
252
262
|
- spec/models/item_spec.rb
|
253
263
|
- spec/models/list_spec.rb
|
264
|
+
- spec/models/parent_spec.rb
|
254
265
|
- spec/routing/inclusion_exclusion_routing_spec.rb
|
255
266
|
- spec/routing/nested_routing_spec.rb
|
256
267
|
- spec/spec_helper.rb
|
@@ -288,12 +299,16 @@ test_files:
|
|
288
299
|
- spec/dummy/app/models/.keep
|
289
300
|
- spec/dummy/app/models/bar.rb
|
290
301
|
- spec/dummy/app/models/barfoo.rb
|
302
|
+
- spec/dummy/app/models/child.rb
|
291
303
|
- spec/dummy/app/models/concerns/.keep
|
292
304
|
- spec/dummy/app/models/foo.rb
|
293
305
|
- spec/dummy/app/models/foobar.rb
|
306
|
+
- spec/dummy/app/models/grandfather.rb
|
307
|
+
- spec/dummy/app/models/grandmother.rb
|
294
308
|
- spec/dummy/app/models/item.rb
|
295
309
|
- spec/dummy/app/models/list.rb
|
296
310
|
- spec/dummy/app/models/message.rb
|
311
|
+
- spec/dummy/app/models/parent.rb
|
297
312
|
- spec/dummy/bin/bundle
|
298
313
|
- spec/dummy/bin/rails
|
299
314
|
- spec/dummy/bin/rake
|
@@ -325,6 +340,10 @@ test_files:
|
|
325
340
|
- spec/dummy/db/migrate/20150314054548_create_lists.rb
|
326
341
|
- spec/dummy/db/migrate/20150314055847_create_items.rb
|
327
342
|
- spec/dummy/db/migrate/20150316034305_drop_messages.rb
|
343
|
+
- spec/dummy/db/migrate/20150329034759_create_children.rb
|
344
|
+
- spec/dummy/db/migrate/20150329034900_create_parents.rb
|
345
|
+
- spec/dummy/db/migrate/20150329034915_create_grandmothers.rb
|
346
|
+
- spec/dummy/db/migrate/20150329034923_create_grandfathers.rb
|
328
347
|
- spec/dummy/db/schema.rb
|
329
348
|
- spec/dummy/log/.keep
|
330
349
|
- spec/dummy/public/Gruntfile.js
|
@@ -341,9 +360,14 @@ test_files:
|
|
341
360
|
- spec/dummy/public/views/lists/index.html
|
342
361
|
- spec/dummy/public/views/lists/show.html
|
343
362
|
- spec/helpers/redis_spec.rb
|
363
|
+
- spec/models/channels_spec.rb
|
364
|
+
- spec/models/child_spec.rb
|
365
|
+
- spec/models/grandfather_spec.rb
|
366
|
+
- spec/models/grandmother_spec.rb
|
344
367
|
- spec/models/inclusion_exclusion_spec.rb
|
345
368
|
- spec/models/item_spec.rb
|
346
369
|
- spec/models/list_spec.rb
|
370
|
+
- spec/models/parent_spec.rb
|
347
371
|
- spec/routing/inclusion_exclusion_routing_spec.rb
|
348
372
|
- spec/routing/nested_routing_spec.rb
|
349
373
|
- spec/spec_helper.rb
|
data/bower.json
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
{
|
2
|
-
"name": "entangled",
|
3
|
-
"version": "0.0.8",
|
4
|
-
"authors": [
|
5
|
-
"Dennis Charles Hackethal <dennis.hackethal@gmail.com>"
|
6
|
-
],
|
7
|
-
"description": "Angular library that communicates with a real time backend to enable three way data binding",
|
8
|
-
"license": "MIT",
|
9
|
-
"homepage": "https://github.com/dchacke/entangled",
|
10
|
-
"ignore": [
|
11
|
-
"lib",
|
12
|
-
"spec",
|
13
|
-
".gitignore",
|
14
|
-
"entangled.gemspec",
|
15
|
-
"Gemfile",
|
16
|
-
"Gemfile.lock",
|
17
|
-
"Rakefile"
|
18
|
-
],
|
19
|
-
"main": "entangled.js"
|
20
|
-
}
|
data/entangled.js
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
angular.module("entangled",[]).factory("Entangled",function(){var e=function(e,t,r){for(var i in e)e.hasOwnProperty(i)&&(this[i]=e[i]);this.webSocketUrl=t,r&&(this[r]=function(){return new s(this.webSocketUrl+"/"+this.id+"/"+r)})};e.prototype.$save=function(e){if(this.id){var t=new WebSocket(this.webSocketUrl+"/"+this.id+"/update");t.onopen=function(){t.send(JSON.stringify(this))}.bind(this),t.onmessage=function(t){if(t.data){var r=JSON.parse(t.data);if(r.resource)for(key in r.resource)this[key]=r.resource[key]}this[this.hasMany]=new s(this.webSocketUrl+"/"+this.id+"/"+this.hasMany),e&&e(this)}.bind(this)}else{var t=new WebSocket(this.webSocketUrl+"/create");t.onopen=function(){t.send(JSON.stringify(this))}.bind(this),t.onmessage=function(t){if(t.data){var s=JSON.parse(t.data);if(s.resource)for(key in s.resource)this[key]=s.resource[key]}e&&e(this)}.bind(this)}},e.prototype.$update=function(e,t){for(var s in e)e.hasOwnProperty(s)&&(this[s]=e[s]);this.$save(t)},e.prototype.$destroy=function(e){var t=new WebSocket(this.webSocketUrl+"/"+this.id+"/destroy");t.onopen=function(){t.send(null)},t.onmessage=function(t){if(t.data){var s=JSON.parse(t.data);if(s.resource)for(key in s.resource)this[key]=s.resource[key]}e&&e(this)}.bind(this)},e.prototype.$valid=function(){return!(this.errors&&Object.keys(this.errors).length)},e.prototype.$invalid=function(){return!this.$valid()},e.prototype.$persisted=function(){return!!this.id};var t=function(t,s,r){this.all=[];for(var i=0;i<t.length;i++){var o=new e(t[i],s,r);this.all.push(o)}},s=function(e){this.className="Entangled",this.webSocketUrl=e};return s.prototype.hasMany=function(e){this.hasMany=e},s.prototype["new"]=function(t){return new e(t,this.webSocketUrl,this.hasMany)},s.prototype.all=function(s){var r=new WebSocket(this.webSocketUrl);r.onmessage=function(i){if(i.data.length){var o=JSON.parse(i.data);if(o.resources)this.resources=new t(o.resources,r.url,this.hasMany);else if(o.action)if("create"===o.action)this.resources.all.push(new e(o.resource,r.url,this.hasMany));else if("update"===o.action){for(var n,a=0;a<this.resources.all.length;a++)this.resources.all[a].id===o.resource.id&&(n=a);this.resources.all[n]=new e(o.resource,r.url,this.hasMany)}else if("destroy"===o.action){for(var n,a=0;a<this.resources.all.length;a++)this.resources.all[a].id===o.resource.id&&(n=a);this.resources.all.splice(n,1)}else console.log("Something else other than CRUD happened..."),console.log(o)}s(this.resources.all)}.bind(this)},s.prototype.create=function(e,t){var s=this["new"](e);s.$save(t)},s.prototype.find=function(t,s){var r=this.webSocketUrl,i=new WebSocket(r+"/"+t);i.onmessage=function(t){if(t.data.length){var i=JSON.parse(t.data);i.resource&&!i.action?this.resource=new e(i.resource,r,this.hasMany):i.action?"update"===i.action?this.resource=new e(i.resource,r,this.hasMany):"destroy"===i.action&&(this.resource=void 0):(console.log("Something else other than CRUD happened..."),console.log(i))}s(this.resource)}.bind(this)},s});
|