entangled 0.0.21 → 0.0.22
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.
- checksums.yaml +4 -4
- data/README.md +30 -21
- data/bower.json +1 -1
- data/entangled.js +1 -1
- data/lib/entangled/controller.rb +15 -2
- data/lib/entangled/version.rb +1 -1
- data/spec/dummy/app/controllers/items_controller.rb +1 -1
- data/spec/dummy/app/controllers/lists_controller.rb +1 -1
- data/spec/dummy/app/controllers/messages_controller.rb +1 -1
- data/spec/dummy/public/app/controllers/list.js +15 -0
- data/spec/dummy/public/app/entangled/entangled.js +17 -4
- data/spec/dummy/public/index.html +1 -0
- data/spec/dummy/public/test/controllers/messages_test.js +12 -1
- data/spec/dummy/public/views/lists/show.html +12 -5
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a2db62264a96d90447e75a73bed98ef29fd1a86
|
4
|
+
data.tar.gz: 5e2a82bd5418bb9911b54febe96721e285cee439
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a4ce644d5222adcf7a7631da195c64ea1f35c9e70419451ece36a3a46325adca6c7386d015b70e10610f3d0d484c95fca19648f624f7baf9f4d02283ade87147
|
7
|
+
data.tar.gz: 31dfde1c1a38423af73029c115b38580030d2c268104b4ce5b90ea305c059458d1622e024267a81476f74880d9c7a07af2e0420e2ced2b4b33a55064b8aafe35
|
data/README.md
CHANGED
@@ -97,7 +97,7 @@ You can limit this behavior by specifying `:only` or `:except` options. For exam
|
|
97
97
|
|
98
98
|
```ruby
|
99
99
|
entangle only: :create
|
100
|
-
|
100
|
+
entangle only: [:create, :update]
|
101
101
|
```
|
102
102
|
|
103
103
|
### Controllers
|
@@ -134,7 +134,7 @@ class MessagesController < ApplicationController
|
|
134
134
|
|
135
135
|
def destroy
|
136
136
|
broadcast do
|
137
|
-
Message.find(params[:id]).destroy
|
137
|
+
@message = Message.find(params[:id]).destroy
|
138
138
|
end
|
139
139
|
end
|
140
140
|
|
@@ -148,9 +148,9 @@ end
|
|
148
148
|
Note the following:
|
149
149
|
|
150
150
|
- All methods are wrapped in a new `broadcast` block needed to receive and send data to connected clients
|
151
|
-
- The `index`
|
152
|
-
- The `show`, `create` and `
|
153
|
-
-
|
151
|
+
- The `index` action will expect an instance variable with the same name as your controller in the plural form (e.g. `@messages` in a `MessagesController`)
|
152
|
+
- 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
|
+
- The instance variables are sent to clients as stringified JSON
|
154
154
|
- Strong parameters are expected
|
155
155
|
|
156
156
|
### Server
|
@@ -299,6 +299,14 @@ $scope.message.$save(function() {
|
|
299
299
|
|
300
300
|
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.
|
301
301
|
|
302
|
+
#### Persistence
|
303
|
+
Just as with ActiveRecord's `persisted?` method, you can use `$persisted()` on an object to check if it was successfully stored in the database.
|
304
|
+
|
305
|
+
```javascript
|
306
|
+
$scope.message.$persisted();
|
307
|
+
// => true or false
|
308
|
+
```
|
309
|
+
|
302
310
|
#### Associations
|
303
311
|
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?
|
304
312
|
|
@@ -361,12 +369,12 @@ end
|
|
361
369
|
```javascript
|
362
370
|
app.factory('Parent', function(Entangled) {
|
363
371
|
// Instantiate Entangled service
|
364
|
-
var
|
372
|
+
var Parent = new Entangled('ws://localhost:3000/parents');
|
365
373
|
|
366
374
|
// Set up association
|
367
|
-
|
375
|
+
Parent.hasMany('children');
|
368
376
|
|
369
|
-
return
|
377
|
+
return Parent;
|
370
378
|
});
|
371
379
|
```
|
372
380
|
|
@@ -394,14 +402,6 @@ This is the way to go if you want to fetch records that only belong to a certain
|
|
394
402
|
|
395
403
|
Naturally, all nested records are also synced in real time across all connected clients.
|
396
404
|
|
397
|
-
#### Persistence
|
398
|
-
Just as with ActiveRecord's `persisted?` method, you can use `$persisted()` on an object to check if it was successfully stored in the database.
|
399
|
-
|
400
|
-
```javascript
|
401
|
-
$scope.message.$persisted();
|
402
|
-
// => true or false
|
403
|
-
```
|
404
|
-
|
405
405
|
## Planning Your Infrastructure
|
406
406
|
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.
|
407
407
|
|
@@ -411,13 +411,22 @@ The gem relies heavily on convention over configuration and currently only works
|
|
411
411
|
## Development Priorities
|
412
412
|
The following features are to be implemented next:
|
413
413
|
|
414
|
-
-
|
415
|
-
- Support
|
416
|
-
- Support
|
417
|
-
-
|
418
|
-
- Add
|
414
|
+
- Support more than one belongs_to association in back end
|
415
|
+
- Support belongs_to in front end
|
416
|
+
- Support deeply nested belongs_to, e.g. Parent > Child > Grandchild
|
417
|
+
- Support has_one association in back end and front end
|
418
|
+
- Add offline capabilities
|
419
|
+
- Add authentication - with JWT?
|
420
|
+
- On Heroku, tasks are always in different order depending on which ones are checked off and not
|
421
|
+
- Add $onChange function to objects - or could a simple $watch and $watchCollection suffice?
|
419
422
|
- Add diagram on how it works to Readme
|
420
423
|
- Check if Rails 4.0.0 supported too
|
424
|
+
- GNU instead of MIT? Or something else? How to switch?
|
425
|
+
- Contact Jessy to tweet about it!
|
426
|
+
- Handle errors gracefully (e.g. finding a non-existent id, etc, authorization error in the back end, timeouts, etc)
|
427
|
+
- Test controllers (see https://github.com/ngauthier/tubesock/issues/41)
|
428
|
+
- Freeze destroyed object
|
429
|
+
- Set $persisted() to false on a destroyed object
|
421
430
|
|
422
431
|
## Contributing
|
423
432
|
1. [Fork it](https://github.com/dchacke/entangled/fork) - you will notice that the repo comes with a back end and a front end part to test both parts of the gem
|
data/bower.json
CHANGED
data/entangled.js
CHANGED
@@ -1 +1 @@
|
|
1
|
-
angular.module("entangled",[]).factory("Entangled",function(){var e=function(e,t,r){for(var
|
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.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});
|
data/lib/entangled/controller.rb
CHANGED
@@ -193,10 +193,23 @@ module Entangled
|
|
193
193
|
close_db_connection
|
194
194
|
end
|
195
195
|
|
196
|
+
when 'destroy'
|
197
|
+
tubesock.onmessage do |m|
|
198
|
+
yield
|
199
|
+
|
200
|
+
# Send resource that was just destroyed back to client
|
201
|
+
if member
|
202
|
+
tubesock.send_data({
|
203
|
+
resource: member
|
204
|
+
}.to_json)
|
205
|
+
end
|
206
|
+
|
207
|
+
close_db_connection
|
208
|
+
end
|
209
|
+
|
196
210
|
# For every other controller action, simply wrap whatever is
|
197
211
|
# yielded in the tubesock block to execute it in the context
|
198
|
-
# of the socket.
|
199
|
-
# by this, and other custom action can be added through this.
|
212
|
+
# of the socket. Other custom actions can be added through this
|
200
213
|
else
|
201
214
|
tubesock.onmessage do |m|
|
202
215
|
yield
|
data/lib/entangled/version.rb
CHANGED
@@ -11,6 +11,21 @@ angular.module('entangledTest')
|
|
11
11
|
$scope.items = items;
|
12
12
|
});
|
13
13
|
});
|
14
|
+
|
15
|
+
$scope.item = $scope.list.items().new();
|
16
|
+
console.log($scope.item);
|
14
17
|
});
|
15
18
|
});
|
19
|
+
|
20
|
+
$scope.createItem = function() {
|
21
|
+
$scope.item.$save(function() {
|
22
|
+
$scope.$apply(function() {
|
23
|
+
$scope.item = $scope.list.items().new();
|
24
|
+
});
|
25
|
+
});
|
26
|
+
};
|
27
|
+
|
28
|
+
$scope.updateItem = function(event, item) {
|
29
|
+
item.$save();
|
30
|
+
};
|
16
31
|
});
|
@@ -6,7 +6,7 @@ angular.module('entangled', [])
|
|
6
6
|
// Every response coming from the server will be wrapped
|
7
7
|
// in a Resource constructor to represent a CRUD-able
|
8
8
|
// resource that can be saved and destroyed using the
|
9
|
-
// methods $save() and
|
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
12
|
var Resource = function(params, webSocketUrl, hasMany) {
|
@@ -114,14 +114,28 @@ angular.module('entangled', [])
|
|
114
114
|
// destroy an existing record.
|
115
115
|
Resource.prototype.$destroy = function(callback) {
|
116
116
|
var socket = new WebSocket(this.webSocketUrl + '/' + this.id + '/destroy');
|
117
|
+
|
117
118
|
socket.onopen = function() {
|
118
119
|
// It's fine to send an empty message since the
|
119
120
|
// socket's URL contains all the information
|
120
121
|
// needed to destroy the record (the id).
|
121
122
|
socket.send(null);
|
122
|
-
|
123
|
-
if (callback) callback();
|
124
123
|
};
|
124
|
+
|
125
|
+
socket.onmessage = function(event) {
|
126
|
+
if (event.data) {
|
127
|
+
var data = JSON.parse(event.data);
|
128
|
+
|
129
|
+
// Assign/override new data
|
130
|
+
if (data.resource) {
|
131
|
+
for (key in data.resource) {
|
132
|
+
this[key] = data.resource[key];
|
133
|
+
}
|
134
|
+
}
|
135
|
+
}
|
136
|
+
|
137
|
+
if (callback) callback(this);
|
138
|
+
}.bind(this);
|
125
139
|
};
|
126
140
|
|
127
141
|
// $valid() checks if any errors are attached to the object
|
@@ -174,7 +188,6 @@ angular.module('entangled', [])
|
|
174
188
|
// sets up websockets accordingly
|
175
189
|
Entangled.prototype.hasMany = function(resources) {
|
176
190
|
this.hasMany = resources;
|
177
|
-
// this[resources] = new Entangled(this.webSocketUrl + '/' + this.id + '/' + resources);
|
178
191
|
};
|
179
192
|
|
180
193
|
// Function to instantiate a new Resource, optionally
|
@@ -5,6 +5,7 @@
|
|
5
5
|
<body>
|
6
6
|
<h1>Hello, young programmer!</h1>
|
7
7
|
<div ng-view=""></div>
|
8
|
+
<a href="#/lists">Lists</a>
|
8
9
|
<script src="bower_components/angular/angular.js"></script>
|
9
10
|
<script src="bower_components/angular-route/angular-route.js"></script>
|
10
11
|
<script src="app/entangled/entangled.js"></script>
|
@@ -161,7 +161,7 @@ describe('MessagesCtrl', function () {
|
|
161
161
|
expect(message.$valid()).toBeTruthy();
|
162
162
|
});
|
163
163
|
|
164
|
-
it('checks for
|
164
|
+
it('checks for invalidity with $invalid()', function() {
|
165
165
|
var message = Message.new();
|
166
166
|
|
167
167
|
// Message should not be invalid without errors
|
@@ -201,6 +201,17 @@ describe('MessagesCtrl', function () {
|
|
201
201
|
}, 100);
|
202
202
|
});
|
203
203
|
|
204
|
+
it('can destroy a message and receives it in the callback', function(done) {
|
205
|
+
Message.create({ body: 'foo' }, function(message) {
|
206
|
+
expect(message.$persisted()).toBeTruthy();
|
207
|
+
|
208
|
+
message.$destroy(function(message) {
|
209
|
+
expect(message).toBeDefined();
|
210
|
+
done();
|
211
|
+
});
|
212
|
+
});
|
213
|
+
});
|
214
|
+
|
204
215
|
it('can check for persistence', function() {
|
205
216
|
// Instantiate record and mimic persistence
|
206
217
|
var message = Message.new({ id: 1 });
|
@@ -2,8 +2,15 @@
|
|
2
2
|
|
3
3
|
<h2>{{ list.name }}</h2>
|
4
4
|
|
5
|
-
<
|
6
|
-
<
|
7
|
-
{{ item.name }}
|
8
|
-
|
9
|
-
</
|
5
|
+
<table>
|
6
|
+
<tr ng-repeat="item in items">
|
7
|
+
<td>{{ item.name }}</td>
|
8
|
+
<td><input ng-model="item.complete" type="checkbox" ng-checked="item.complete" ng-change="updateItem($event, item)"></td>
|
9
|
+
</tr>
|
10
|
+
</table>
|
11
|
+
|
12
|
+
<h4>Add item to this list</h4>
|
13
|
+
<form ng-submit="createItem()">
|
14
|
+
<input ng-model="item.name">
|
15
|
+
<input type="submit">
|
16
|
+
</form>
|