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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a57bb2d361ca6fc01d21e41081913491ec6d3f96
4
- data.tar.gz: 8e76dffbad36c6d4f5ce3189852c054d919bbe36
3
+ metadata.gz: 0a2db62264a96d90447e75a73bed98ef29fd1a86
4
+ data.tar.gz: 5e2a82bd5418bb9911b54febe96721e285cee439
5
5
  SHA512:
6
- metadata.gz: 313a25571895c0076d87989f4efae4cda3d8642255fa930844b02306b12f2dca7d6d5d5236ca8778d64588a5f55e8f555fc2c44a9d9155e1276e0f7b57339a99
7
- data.tar.gz: 0fd5d35de6ee95bdb9f77b1a21df7959b17e19048dcd19e6bea3794ebb9e1980cf4c22ff589a51320fca923c08bf01d55a471531083b51a0f60a59a8f7a2d0c1
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
- entangled only: [:create, :update]
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` method 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` and `update` methods will expect an instance variable with the singular name of your controller (e.g. `@message` in a `MessagesController`)
153
- - Data sent to clients arrives as stringified JSON
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 entangled = new Entangled('ws://localhost:3000/parents');
372
+ var Parent = new Entangled('ws://localhost:3000/parents');
365
373
 
366
374
  // Set up association
367
- entangled.hasMany('children');
375
+ Parent.hasMany('children');
368
376
 
369
- return entangled;
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
- - Offline capabilities - when client is disconnected, put websocket interactions in a queue and dequeue all once connected again
415
- - Support for authentication
416
- - Support for associations
417
- - On Heroku (maybe in production in general), objects are always in different order depending on their attributes
418
- - Add $onChange listener to objects
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "entangled",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "authors": [
5
5
  "Dennis Charles Hackethal <dennis.hackethal@gmail.com>"
6
6
  ],
data/entangled.js CHANGED
@@ -1 +1 @@
1
- angular.module("entangled",[]).factory("Entangled",function(){var e=function(e,t,r){for(var o in e)e.hasOwnProperty(o)&&(this[o]=e[o]);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),e&&e()}},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 o=0;o<t.length;o++){var i=new e(t[o],s,r);this.all.push(i)}},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(o){if(o.data.length){var i=JSON.parse(o.data);if(i.resources)this.resources=new t(i.resources,r.url,this.hasMany);else if(i.action)if("create"===i.action)this.resources.all.push(new e(i.resource,r.url,this.hasMany));else if("update"===i.action){for(var n,a=0;a<this.resources.all.length;a++)this.resources.all[a].id===i.resource.id&&(n=a);this.resources.all[n]=new e(i.resource,r.url,this.hasMany)}else if("destroy"===i.action){for(var n,a=0;a<this.resources.all.length;a++)this.resources.all[a].id===i.resource.id&&(n=a);this.resources.all.splice(n,1)}else console.log("Something else other than CRUD happened..."),console.log(i)}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,o=new WebSocket(r+"/"+t);o.onmessage=function(t){if(t.data.length){var o=JSON.parse(t.data);o.resource&&!o.action?this.resource=new e(o.resource,r,this.hasMany):o.action?"update"===o.action?this.resource=new e(o.resource,r,this.hasMany):"destroy"===o.action&&(this.resource=void 0):(console.log("Something else other than CRUD happened..."),console.log(o))}s(this.resource)}.bind(this)},s});
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});
@@ -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. The delete action is automatically covered
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
@@ -1,3 +1,3 @@
1
1
  module Entangled
2
- VERSION = "0.0.21"
2
+ VERSION = "0.0.22"
3
3
  end
@@ -28,7 +28,7 @@ class ItemsController < ApplicationController
28
28
 
29
29
  def destroy
30
30
  broadcast do
31
- Item.find(params[:id]).destroy
31
+ @item = Item.find(params[:id]).destroy
32
32
  end
33
33
  end
34
34
 
@@ -21,7 +21,7 @@ class ListsController < ApplicationController
21
21
 
22
22
  def destroy
23
23
  broadcast do
24
- List.find(params[:id]).destroy
24
+ @list = List.find(params[:id]).destroy
25
25
  end
26
26
  end
27
27
 
@@ -30,7 +30,7 @@ class MessagesController < ApplicationController
30
30
 
31
31
  def destroy
32
32
  broadcast do
33
- Message.find(params[:id]).destroy
33
+ @message = Message.find(params[:id]).destroy
34
34
  end
35
35
  end
36
36
 
@@ -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 $destroy. A Resource also
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 validity with $invalid()', function() {
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
- <ul>
6
- <li ng-repeat="item in items">
7
- {{ item.name }}, {{ item.complete }}
8
- </li>
9
- </ul>
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>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: entangled
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.21
4
+ version: 0.0.22
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dennis Charles Hackethal