entangled 0.0.17 → 0.0.18
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +25 -18
- data/bower.json +0 -1
- data/entangled.js +1 -269
- data/lib/entangled/controller.rb +7 -3
- data/lib/entangled/model.rb +12 -12
- data/lib/entangled/version.rb +1 -1
- data/spec/models/message_spec.rb +16 -8
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c26b49537871a591ca346338518c72959721f39a
|
4
|
+
data.tar.gz: b1edc3cd5f06358c30ce85ef8146da53db890704
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 54880c5779d9979ac8f97728b5e939fa3af5ffff4e76a5f330314cf3eb9b1fd7d609cd15e2be8ba6273019c5441db8d59788013763f2c4c15758024bb70c4e31
|
7
|
+
data.tar.gz: 98ab954df3d7f04be3bd6a8f0662a33f0b88626e5ab06c6b5351d3721a3c7de63350af215da5ead09d6e29c2c61434b2aefe1ff9bc5eb11d2f12de68281da86d
|
data/README.md
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
[![Codeship Status for dchacke/entangled](https://codeship.com/projects/9fe9a790-9df7-0132-5fb8-6e77ea26735b/status?branch=master)](https://codeship.com/projects/64679)
|
4
4
|
|
5
|
-
|
5
|
+
Real time is important. Users have come to expect real time behavior from every website, because they want to see the latest data without having to reload the page. Real time increases their engagement, provides better context for the data they're seeing, and makes collaboration easier.
|
6
6
|
|
7
|
-
Entangled stores and syncs data instantly across every device. It is a layer behind your
|
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
|
-
|
9
|
+
Currently, Entangled runs on Rails 4.2 in the back end and Angular in the front end.
|
10
10
|
|
11
11
|
## Installation
|
12
12
|
Add this line to your application's Gemfile:
|
@@ -130,7 +130,7 @@ end
|
|
130
130
|
|
131
131
|
Note the following:
|
132
132
|
|
133
|
-
- All methods are wrapped in a new `broadcast` block needed to send
|
133
|
+
- All methods are wrapped in a new `broadcast` block needed to receive and send data to connected clients
|
134
134
|
- 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`)
|
135
135
|
- The `show`, `create` and `update` methods will expect an instance variable with the singular name of your controller (e.g. `@message` in a `MessagesController`)
|
136
136
|
- Data sent to clients arrives as stringified JSON
|
@@ -152,7 +152,7 @@ If you store your Redis instance in `$redis` or `REDIS` (e.g. in an initializer)
|
|
152
152
|
Depending on your app's settings, you might have to increase the pool size in your database.yml configuration file, since every new socket will open a new connection to your database.
|
153
153
|
|
154
154
|
## The Client
|
155
|
-
You will need to configure your client to create Websockets and understand incoming requests on those sockets.
|
155
|
+
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.
|
156
156
|
|
157
157
|
### Installation
|
158
158
|
You can either download or reference the file `entangled.js` from this repository, or simply install it with Bower:
|
@@ -180,7 +180,7 @@ app.factory('Message', function(Entangled) {
|
|
180
180
|
|
181
181
|
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.
|
182
182
|
|
183
|
-
The Entangled service
|
183
|
+
The Entangled service comes with these functions:
|
184
184
|
|
185
185
|
- `new(params)`
|
186
186
|
- `create(params, callback)`
|
@@ -245,12 +245,7 @@ $scope.message.$destroy(function() {
|
|
245
245
|
});
|
246
246
|
```
|
247
247
|
|
248
|
-
All functions above will interact with your server's controllers in real time.
|
249
|
-
|
250
|
-
If data in your server's database changes, so will your scope variables - in real time, for all connected clients.
|
251
|
-
|
252
|
-
### Available Functions
|
253
|
-
A number of functions is attached to Entangled JavaScript objects. They basically mimic ActiveRecord's behavior in the back end to make the database more accessible in the front end.
|
248
|
+
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.
|
254
249
|
|
255
250
|
#### Validations
|
256
251
|
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.
|
@@ -291,7 +286,7 @@ $scope.message.$save(function() {
|
|
291
286
|
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.
|
292
287
|
|
293
288
|
#### Persistence
|
294
|
-
Just
|
289
|
+
Just as with ActiveRecord's `persisted?` method, you can use `$persisted()` on an object to check if it was successfully stored in the database.
|
295
290
|
|
296
291
|
```javascript
|
297
292
|
$scope.message.$persisted();
|
@@ -302,16 +297,28 @@ $scope.message.$persisted();
|
|
302
297
|
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.
|
303
298
|
|
304
299
|
## Limitations
|
305
|
-
The gem
|
300
|
+
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, such as associations, authentication, and more.
|
301
|
+
|
302
|
+
## Development Priorities
|
303
|
+
The following features are to be implemented next:
|
304
|
+
|
305
|
+
- Offline capabilities - when client is disconnected, put websocket interactions in a queue and dequeue all once connected again
|
306
|
+
- Support for authentication
|
307
|
+
- Remove angular dependencies from bower package (they're currently all being downloaded as well when doing bower install)
|
308
|
+
- On Heroku (maybe in production in general), objects are always in different order depending on their attributes
|
309
|
+
- Add $onChange listener to objects
|
310
|
+
- Add diagram on how it works to Readme
|
311
|
+
- Reuse open DB connections to reduce pool-size - currently, a new db connection is established for every request, which quickly gets out of hand. Only one DB connection should be opened and maintained per client
|
312
|
+
- Check if Rails 4.0.0 supported too
|
306
313
|
|
307
314
|
## Contributing
|
308
315
|
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
|
309
316
|
2. Run `$ bundle install` in the root of the repo
|
310
317
|
3. Run `$ bower install` and `$ npm install` in spec/dummy/public
|
311
|
-
4. The back end example app resides in spec/dummy
|
312
|
-
5. The front end example app resides in spec/dummy/public. To look at it in your browser, cd into spec/dummy/public and run `$ bin/rails s`. Tests for this part of the app can be located under spec/dummy/public/test and are written with Jasmine. To run the tests, first run `$ bin/rails -e test` to start up the server in test mode, and then run `$ grunt test` in a new terminal tab. It's important to remember that changes you make to the server will not take effect until you restart the server since you're running it in the test environment
|
313
|
-
6. The Entangled Angular service resides in spec/dummy/public/app/entangled/entangled.js. This is where you can make changes to the service
|
314
|
-
7. Write your tests
|
318
|
+
4. The back end example app resides in spec/dummy. You can run `rails` and `rake` commands in there if you prefix them with `bin/`, i.e. `$ bin/rails s` or `$ bin/rake db:schema:load`. Run your Rails tests in the root of the repo by running `$ rspec`
|
319
|
+
5. The front end example app resides in spec/dummy/public. To look at it in your browser, cd into spec/dummy/public and run `$ bin/rails s`. Tests for this part of the app can be located under spec/dummy/public/test and are written with Jasmine. To run the tests, first run `$ bin/rails -e test` to start up the server in test mode, and then run `$ grunt test` in a new terminal tab. It's important to remember that changes you make to the server will not take effect until you restart the server since you're running it in the test environment. Also remember to prepare the test database by running `$ bin/rake db:test:prepare`
|
320
|
+
6. The Entangled Angular service resides in spec/dummy/public/app/entangled/entangled.js. This is where you can make changes to the service. A copy of it, living in /entangled.js at the root of the repo, should be kept in sync for it to be available with Bower. Once you're done editing spec/dummy/public/app/entangled/entangled.js, copy it over to /entangled.js
|
321
|
+
7. Write your tests. Test coverage is required
|
315
322
|
8. Write your feature to make the tests pass
|
316
323
|
9. Stage and commit your changes
|
317
324
|
10. Push to a new feature branch in your repo
|
data/bower.json
CHANGED
data/entangled.js
CHANGED
@@ -1,269 +1 @@
|
|
1
|
-
|
2
|
-
angular.module('entangled', [])
|
3
|
-
|
4
|
-
// Register service and call it 'Entangled'
|
5
|
-
.factory('Entangled', function() {
|
6
|
-
// Every response coming from the server will be wrapped
|
7
|
-
// in a Resource constructor to represent a CRUD-able
|
8
|
-
// resource that can be saved and destroyed using the
|
9
|
-
// methods $save() and $destroy. A Resource also
|
10
|
-
// stores the socket's URL it was retrieved from so it
|
11
|
-
// can be reused for other requests.
|
12
|
-
var Resource = function(params, webSocketUrl) {
|
13
|
-
// Assign proerties
|
14
|
-
for (var key in params) {
|
15
|
-
// Skip inherent object properties
|
16
|
-
if (params.hasOwnProperty(key)) {
|
17
|
-
this[key] = params[key];
|
18
|
-
}
|
19
|
-
}
|
20
|
-
|
21
|
-
this.webSocketUrl = webSocketUrl;
|
22
|
-
};
|
23
|
-
|
24
|
-
// $save() will send a request to the server
|
25
|
-
// to either create a new record or update
|
26
|
-
// an existing, depending on whether or not
|
27
|
-
// an id is present.
|
28
|
-
Resource.prototype.$save = function(callback) {
|
29
|
-
var that = this;
|
30
|
-
|
31
|
-
if (this.id) {
|
32
|
-
// Update
|
33
|
-
var socket = new WebSocket(that.webSocketUrl + '/' + that.id + '/update');
|
34
|
-
socket.onopen = function() {
|
35
|
-
socket.send(JSON.stringify(that));
|
36
|
-
};
|
37
|
-
|
38
|
-
// Receive updated resource from server
|
39
|
-
socket.onmessage = function(event) {
|
40
|
-
if (event.data) {
|
41
|
-
var data = JSON.parse(event.data);
|
42
|
-
|
43
|
-
// Assign/override new data (such as updated_at, etc)
|
44
|
-
if (data.resource) {
|
45
|
-
for (key in data.resource) {
|
46
|
-
that[key] = data.resource[key];
|
47
|
-
}
|
48
|
-
}
|
49
|
-
}
|
50
|
-
|
51
|
-
// Pass 'that' to callback for create
|
52
|
-
// function so that the create function
|
53
|
-
// can pass the created resource to its
|
54
|
-
// own callback; not needed for $save per se
|
55
|
-
if (callback) callback(that);
|
56
|
-
};
|
57
|
-
} else {
|
58
|
-
// Create
|
59
|
-
var socket = new WebSocket(that.webSocketUrl + '/create');
|
60
|
-
|
61
|
-
// Send attributes to server
|
62
|
-
socket.onopen = function() {
|
63
|
-
socket.send(JSON.stringify(that));
|
64
|
-
};
|
65
|
-
|
66
|
-
// Receive saved resource from server
|
67
|
-
socket.onmessage = function(event) {
|
68
|
-
if (event.data) {
|
69
|
-
var data = JSON.parse(event.data);
|
70
|
-
|
71
|
-
// Assign/override new data (such as id, created_at,
|
72
|
-
// updated_at, etc)
|
73
|
-
if (data.resource) {
|
74
|
-
for (key in data.resource) {
|
75
|
-
that[key] = data.resource[key];
|
76
|
-
}
|
77
|
-
}
|
78
|
-
}
|
79
|
-
|
80
|
-
// Pass 'that' to callback for create
|
81
|
-
// function so that the create function
|
82
|
-
// can pass the created resource to its
|
83
|
-
// own callback; not needed for $save per se
|
84
|
-
if (callback) callback(that);
|
85
|
-
};
|
86
|
-
}
|
87
|
-
};
|
88
|
-
|
89
|
-
// $update() updates a record in place
|
90
|
-
Resource.prototype.$update = function(params, callback) {
|
91
|
-
// Update object in memory
|
92
|
-
for (var key in params) {
|
93
|
-
// Skip inherent object properties
|
94
|
-
if (params.hasOwnProperty(key)) {
|
95
|
-
this[key] = params[key];
|
96
|
-
}
|
97
|
-
}
|
98
|
-
|
99
|
-
// Save object
|
100
|
-
this.$save(callback);
|
101
|
-
};
|
102
|
-
|
103
|
-
// $destroy() will send a request to the server to
|
104
|
-
// destroy an existing record.
|
105
|
-
Resource.prototype.$destroy = function(callback) {
|
106
|
-
var socket = new WebSocket(this.webSocketUrl + '/' + this.id + '/destroy');
|
107
|
-
socket.onopen = function() {
|
108
|
-
// It's fine to send an empty message since the
|
109
|
-
// socket's URL contains all the information
|
110
|
-
// needed to destroy the record (the id).
|
111
|
-
socket.send(null);
|
112
|
-
|
113
|
-
if (callback) callback();
|
114
|
-
};
|
115
|
-
};
|
116
|
-
|
117
|
-
// $valid() checks if any errors are attached to the object
|
118
|
-
// and return false if so, false otherwise. This doesn't actually
|
119
|
-
// invoke server side validations, so it should only be used
|
120
|
-
// after calling $save() to check if the record was successfully
|
121
|
-
// stored in the database
|
122
|
-
Resource.prototype.$valid = function() {
|
123
|
-
return !(this.errors && Object.keys(this.errors).length);
|
124
|
-
};
|
125
|
-
|
126
|
-
// $invalid() returns the opposite of $valid()
|
127
|
-
Resource.prototype.$invalid = function() {
|
128
|
-
return !this.$valid();
|
129
|
-
};
|
130
|
-
|
131
|
-
// $persisted() checks if the record was successfully stored
|
132
|
-
// in the back end's database
|
133
|
-
Resource.prototype.$persisted = function() {
|
134
|
-
return !!this.id;
|
135
|
-
};
|
136
|
-
|
137
|
-
// Resources wraps all individual Resource objects
|
138
|
-
// in a collection.
|
139
|
-
var Resources = function(resources, webSocketUrl) {
|
140
|
-
this.all = [];
|
141
|
-
|
142
|
-
for (var i = 0; i < resources.length; i++) {
|
143
|
-
var resource = new Resource(resources[i], webSocketUrl);
|
144
|
-
this.all.push(resource);
|
145
|
-
}
|
146
|
-
};
|
147
|
-
|
148
|
-
// Entangled is the heart of this service. It contains
|
149
|
-
// several methods to instantiate a new Resource,
|
150
|
-
// retrieve an existing Resource from the server,
|
151
|
-
// and retrieve a collection of existing Resources
|
152
|
-
// from the server.
|
153
|
-
//
|
154
|
-
// Entangled is a constructor that takes the URL
|
155
|
-
// of the index action on the server where the
|
156
|
-
// Resources can be retrieved.
|
157
|
-
var Entangled = function(webSocketUrl) {
|
158
|
-
// Store the root URL that sockets
|
159
|
-
// will connect to
|
160
|
-
this.webSocketUrl = webSocketUrl;
|
161
|
-
};
|
162
|
-
|
163
|
-
// Function to instantiate a new Resource, optionally
|
164
|
-
// with given parameters
|
165
|
-
Entangled.prototype.new = function(params) {
|
166
|
-
return new Resource(params, this.webSocketUrl);
|
167
|
-
};
|
168
|
-
|
169
|
-
// Retrieve all Resources from the root of the socket's
|
170
|
-
// URL
|
171
|
-
Entangled.prototype.all = function(callback) {
|
172
|
-
var socket = new WebSocket(this.webSocketUrl);
|
173
|
-
|
174
|
-
socket.onmessage = function(event) {
|
175
|
-
// If the message from the server isn't empty
|
176
|
-
if (event.data.length) {
|
177
|
-
// Convert message to JSON
|
178
|
-
var data = JSON.parse(event.data);
|
179
|
-
|
180
|
-
// If the collection of Resources was sent
|
181
|
-
if (data.resources) {
|
182
|
-
// Store retrieved Resources in property
|
183
|
-
this.resources = new Resources(data.resources, socket.url);
|
184
|
-
} else if (data.action) {
|
185
|
-
if (data.action === 'create') {
|
186
|
-
// If new Resource was created, add it to the
|
187
|
-
// collection
|
188
|
-
this.resources.all.push(new Resource(data.resource, socket.url));
|
189
|
-
} else if (data.action === 'update') {
|
190
|
-
// If an existing Resource was updated,
|
191
|
-
// update it in the collection as well
|
192
|
-
var index;
|
193
|
-
|
194
|
-
for (var i = 0; i < this.resources.all.length; i++) {
|
195
|
-
if (this.resources.all[i].id === data.resource.id) {
|
196
|
-
index = i;
|
197
|
-
}
|
198
|
-
}
|
199
|
-
|
200
|
-
this.resources.all[index] = new Resource(data.resource, socket.url);
|
201
|
-
} else if (data.action === 'destroy') {
|
202
|
-
// If a Resource was destroyed, remove it
|
203
|
-
// from Resources as well
|
204
|
-
var index;
|
205
|
-
|
206
|
-
for (var i = 0; i < this.resources.all.length; i++) {
|
207
|
-
if (this.resources.all[i].id === data.resource.id) {
|
208
|
-
index = i;
|
209
|
-
}
|
210
|
-
}
|
211
|
-
|
212
|
-
this.resources.all.splice(index, 1);
|
213
|
-
} else {
|
214
|
-
console.log('Something else other than CRUD happened...');
|
215
|
-
console.log(data);
|
216
|
-
}
|
217
|
-
}
|
218
|
-
}
|
219
|
-
|
220
|
-
// Run the callback and pass in the
|
221
|
-
// resulting collection
|
222
|
-
callback(this.resources.all);
|
223
|
-
};
|
224
|
-
};
|
225
|
-
|
226
|
-
// Instantiate and persist a record in one go
|
227
|
-
Entangled.prototype.create = function(params, callback) {
|
228
|
-
var resource = this.new(params);
|
229
|
-
resource.$save(callback);
|
230
|
-
};
|
231
|
-
|
232
|
-
// Find an individual Resource on the server
|
233
|
-
Entangled.prototype.find = function(id, callback) {
|
234
|
-
var webSocketUrl = this.webSocketUrl;
|
235
|
-
var socket = new WebSocket(webSocketUrl + '/' + id);
|
236
|
-
|
237
|
-
socket.onmessage = function(event) {
|
238
|
-
// If the message from the server isn't empty
|
239
|
-
if (event.data.length) {
|
240
|
-
// Parse message and convert to JSON
|
241
|
-
var data = JSON.parse(event.data);
|
242
|
-
|
243
|
-
if (data.resource && !data.action) {
|
244
|
-
// If the Resource was sent from the server,
|
245
|
-
// store it
|
246
|
-
this.resource = new Resource(data.resource, webSocketUrl);
|
247
|
-
} else if (data.action) {
|
248
|
-
if (data.action === 'update') {
|
249
|
-
// If the stored Resource was updated,
|
250
|
-
// update it here as well
|
251
|
-
this.resource = new Resource(data.resource, webSocketUrl);
|
252
|
-
} else if (data.action === 'destroy') {
|
253
|
-
// If the stored Resource was destroyed,
|
254
|
-
// remove it from here as well
|
255
|
-
this.resource = undefined;
|
256
|
-
}
|
257
|
-
} else {
|
258
|
-
console.log('Something else other than CRUD happened...');
|
259
|
-
console.log(data);
|
260
|
-
}
|
261
|
-
}
|
262
|
-
|
263
|
-
// Run callback with retrieved Resource
|
264
|
-
callback(this.resource);
|
265
|
-
};
|
266
|
-
};
|
267
|
-
|
268
|
-
return Entangled;
|
269
|
-
});
|
1
|
+
angular.module("entangled",[]).factory("Entangled",function(){var e=function(e,r){for(var t in e)e.hasOwnProperty(t)&&(this[t]=e[t]);this.webSocketUrl=r};e.prototype.$save=function(e){var r=this;if(this.id){var t=new WebSocket(r.webSocketUrl+"/"+r.id+"/update");t.onopen=function(){t.send(JSON.stringify(r))},t.onmessage=function(t){if(t.data){var o=JSON.parse(t.data);if(o.resource)for(key in o.resource)r[key]=o.resource[key]}e&&e(r)}}else{var t=new WebSocket(r.webSocketUrl+"/create");t.onopen=function(){t.send(JSON.stringify(r))},t.onmessage=function(t){if(t.data){var o=JSON.parse(t.data);if(o.resource)for(key in o.resource)r[key]=o.resource[key]}e&&e(r)}}},e.prototype.$update=function(e,r){for(var t in e)e.hasOwnProperty(t)&&(this[t]=e[t]);this.$save(r)},e.prototype.$destroy=function(e){var r=new WebSocket(this.webSocketUrl+"/"+this.id+"/destroy");r.onopen=function(){r.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 r=function(r,t){this.all=[];for(var o=0;o<r.length;o++){var s=new e(r[o],t);this.all.push(s)}},t=function(e){this.webSocketUrl=e};return t.prototype["new"]=function(r){return new e(r,this.webSocketUrl)},t.prototype.all=function(t){var o=new WebSocket(this.webSocketUrl);o.onmessage=function(s){if(s.data.length){var n=JSON.parse(s.data);if(n.resources)this.resources=new r(n.resources,o.url);else if(n.action)if("create"===n.action)this.resources.all.push(new e(n.resource,o.url));else if("update"===n.action){for(var i,a=0;a<this.resources.all.length;a++)this.resources.all[a].id===n.resource.id&&(i=a);this.resources.all[i]=new e(n.resource,o.url)}else if("destroy"===n.action){for(var i,a=0;a<this.resources.all.length;a++)this.resources.all[a].id===n.resource.id&&(i=a);this.resources.all.splice(i,1)}else console.log("Something else other than CRUD happened..."),console.log(n)}t(this.resources.all)}},t.prototype.create=function(e,r){var t=this["new"](e);t.$save(r)},t.prototype.find=function(r,t){var o=this.webSocketUrl,s=new WebSocket(o+"/"+r);s.onmessage=function(r){if(r.data.length){var s=JSON.parse(r.data);s.resource&&!s.action?this.resource=new e(s.resource,o):s.action?"update"===s.action?this.resource=new e(s.resource,o):"destroy"===s.action&&(this.resource=void 0):(console.log("Something else other than CRUD happened..."),console.log(s))}t(this.resource)}},t});
|
data/lib/entangled/controller.rb
CHANGED
@@ -30,13 +30,17 @@ module Entangled
|
|
30
30
|
# Channel name for collection of resources, used in index
|
31
31
|
# action
|
32
32
|
def collection_channel
|
33
|
-
|
33
|
+
model.channel
|
34
|
+
end
|
35
|
+
|
36
|
+
# The model for this controller. E.g. Taco for a TacosController
|
37
|
+
def model
|
38
|
+
controller_name.classify.constantize
|
34
39
|
end
|
35
40
|
|
36
41
|
# Channel name for single resource, used in show action
|
37
42
|
def member_channel
|
38
|
-
|
39
|
-
"#{resources_name}/#{instance.to_param}"
|
43
|
+
member.channel
|
40
44
|
end
|
41
45
|
|
42
46
|
def collection
|
data/lib/entangled/model.rb
CHANGED
@@ -57,7 +57,7 @@ module Entangled
|
|
57
57
|
|
58
58
|
# The inferred channel name. For example, if the class name
|
59
59
|
# is DeliciousTaco, the inferred channel name is "delicious_tacos"
|
60
|
-
def
|
60
|
+
def channel
|
61
61
|
name.underscore.pluralize
|
62
62
|
end
|
63
63
|
|
@@ -79,6 +79,15 @@ module Entangled
|
|
79
79
|
attributes.merge(errors: errors).as_json
|
80
80
|
end
|
81
81
|
|
82
|
+
# The inferred channel name for a single record
|
83
|
+
# containing the inferred channel name from the class
|
84
|
+
# and the record's id. For example, if it's a
|
85
|
+
# DeliciousTaco with the id 1, the inferred channel
|
86
|
+
# name for the single record is "delicious_tacos/1"
|
87
|
+
def channel
|
88
|
+
"#{self.class.channel}/#{self.to_param}"
|
89
|
+
end
|
90
|
+
|
82
91
|
private
|
83
92
|
|
84
93
|
# Publishes to client. Whoever is subscribed
|
@@ -86,25 +95,16 @@ module Entangled
|
|
86
95
|
# gets the message
|
87
96
|
def publish(action)
|
88
97
|
redis.publish(
|
89
|
-
self.class.
|
98
|
+
self.class.channel,
|
90
99
|
json(action)
|
91
100
|
)
|
92
101
|
|
93
102
|
redis.publish(
|
94
|
-
|
103
|
+
channel,
|
95
104
|
json(action)
|
96
105
|
)
|
97
106
|
end
|
98
107
|
|
99
|
-
# The inferred channel name for a single record
|
100
|
-
# containing the inferred channel name from the class
|
101
|
-
# and the record's id. For example, if it's a
|
102
|
-
# DeliciousTaco with the id 1, the inferred channel
|
103
|
-
# name for the single record is "delicious_tacos/1"
|
104
|
-
def inferred_channel_name_for_single_record
|
105
|
-
"#{self.class.inferred_channel_name}/#{to_param}"
|
106
|
-
end
|
107
|
-
|
108
108
|
# JSON containing the type of action (:create, :update
|
109
109
|
# or :destroy) and the record itself. This is eventually
|
110
110
|
# broadcast to the client
|
data/lib/entangled/version.rb
CHANGED
data/spec/models/message_spec.rb
CHANGED
@@ -6,9 +6,17 @@ RSpec.describe Message, type: :model do
|
|
6
6
|
end
|
7
7
|
|
8
8
|
describe 'Methods' do
|
9
|
-
describe '.
|
9
|
+
describe '.channel' do
|
10
10
|
it 'is the underscore, pluralized model name' do
|
11
|
-
expect(Message.
|
11
|
+
expect(Message.channel).to eq 'messages'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#channel' do
|
16
|
+
let(:message) { Message.create(body: 'foo') }
|
17
|
+
|
18
|
+
it "is the class's channel name plus the member as param" do
|
19
|
+
expect(message.channel).to eq "messages/#{message.to_param}"
|
12
20
|
end
|
13
21
|
end
|
14
22
|
|
@@ -27,7 +35,7 @@ RSpec.describe Message, type: :model do
|
|
27
35
|
redis = stub_redis
|
28
36
|
|
29
37
|
expect(redis).to have_received(:publish).with(
|
30
|
-
|
38
|
+
Message.channel, {
|
31
39
|
action: :create,
|
32
40
|
resource: message
|
33
41
|
}.to_json
|
@@ -38,7 +46,7 @@ RSpec.describe Message, type: :model do
|
|
38
46
|
redis = stub_redis
|
39
47
|
|
40
48
|
expect(redis).to have_received(:publish).with(
|
41
|
-
|
49
|
+
message.channel, {
|
42
50
|
action: :create,
|
43
51
|
resource: message
|
44
52
|
}.to_json
|
@@ -55,7 +63,7 @@ RSpec.describe Message, type: :model do
|
|
55
63
|
message.update(body: 'bar')
|
56
64
|
|
57
65
|
expect(redis).to have_received(:publish).with(
|
58
|
-
|
66
|
+
Message.channel, {
|
59
67
|
action: :update,
|
60
68
|
resource: message
|
61
69
|
}.to_json
|
@@ -68,7 +76,7 @@ RSpec.describe Message, type: :model do
|
|
68
76
|
message.update(body: 'bar')
|
69
77
|
|
70
78
|
expect(redis).to have_received(:publish).with(
|
71
|
-
|
79
|
+
message.channel, {
|
72
80
|
action: :update,
|
73
81
|
resource: message
|
74
82
|
}.to_json
|
@@ -85,7 +93,7 @@ RSpec.describe Message, type: :model do
|
|
85
93
|
message.destroy
|
86
94
|
|
87
95
|
expect(redis).to have_received(:publish).with(
|
88
|
-
|
96
|
+
Message.channel, {
|
89
97
|
action: :destroy,
|
90
98
|
resource: message
|
91
99
|
}.to_json
|
@@ -98,7 +106,7 @@ RSpec.describe Message, type: :model do
|
|
98
106
|
message.destroy
|
99
107
|
|
100
108
|
expect(redis).to have_received(:publish).with(
|
101
|
-
|
109
|
+
message.channel, {
|
102
110
|
action: :destroy,
|
103
111
|
resource: message
|
104
112
|
}.to_json
|
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.18
|
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-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|