entangled 0.0.21 → 0.0.22
Sign up to get free protection for your applications and to get access to all the features.
- 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>
|