live_record 0.2.3 → 0.2.4
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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd7810b1566eda128b77c0d451fedf5056c006b1
|
4
|
+
data.tar.gz: ac200f664f84fb987e563a6dbbed7b186fdcf5f1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 181ef9c74581d40fb9e4cd9f9992f25dc798370467fc93c67b6934e305c02a463390fc505210b04dc68c1a2efd2a92f32e5586d828978af897ae1eaf958ac0f5
|
7
|
+
data.tar.gz: d6c549d59bad9e84abc80d734abc39c093dd62fd673886abe8c98953ade0c5d66d327c8dc75aecd9b371573bb3f81ae0d1321232d86176f14912678d8e5fbc32
|
data/README.md
CHANGED
@@ -32,10 +32,13 @@
|
|
32
32
|
|
33
33
|
### Subscribing to Record Creation
|
34
34
|
```js
|
35
|
-
// subscribe
|
35
|
+
// subscribe and auto-receive newly created Book records from the Rails server
|
36
36
|
LiveRecord.Model.all.Book.subscribe()
|
37
37
|
|
38
|
-
// ...or
|
38
|
+
// ... or also load all Book records as well (not just the new ones)
|
39
|
+
// LiveRecord.Model.all.Book.subscribe({reload: true})
|
40
|
+
|
41
|
+
// ...or only those which are enabled (you can also combine this with `reload: true`)
|
39
42
|
// LiveRecord.Model.all.Book.subscribe({where: {is_enabled_eq: true}})
|
40
43
|
|
41
44
|
// now, we can just simply add a "create" callback, to apply our own logic whenever a new Book record is streamed from the backend
|
@@ -114,7 +117,7 @@
|
|
114
117
|
1. Add the following to your `Gemfile`:
|
115
118
|
|
116
119
|
```ruby
|
117
|
-
gem 'live_record', '~> 0.2.
|
120
|
+
gem 'live_record', '~> 0.2.4'
|
118
121
|
```
|
119
122
|
|
120
123
|
2. Run:
|
@@ -345,16 +348,22 @@
|
|
345
348
|
})
|
346
349
|
```
|
347
350
|
|
348
|
-
9. To automatically receive new Book records, you may subscribe:
|
351
|
+
9. To automatically receive new Book records, and/or also load the old ones, you may subscribe:
|
349
352
|
|
350
353
|
```js
|
351
|
-
// subscribe
|
354
|
+
// subscribe and auto-fetches newly created Book records from the backend
|
352
355
|
var subscription = LiveRecord.Model.all.Book.subscribe();
|
353
356
|
|
357
|
+
// ...or also load all Book records (not just the new ones).
|
358
|
+
// useful for populating records at the start, and therefore you may skip using `LiveRecord.helpers.loadRecords()` already
|
359
|
+
// subscription = LiveRecord.Model.all.Book.subscribe({reload: true});
|
360
|
+
|
354
361
|
// ...or subscribe only to certain conditions (i.e. when `is_enabled` attribute value is `true`)
|
355
362
|
// For the list of supported operators (like `..._eq`), see JS API `MODEL.subscribe(CONFIG)` below
|
356
363
|
// subscription = LiveRecord.Model.all.Book.subscribe({where: {is_enabled_eq: true}});
|
357
364
|
|
365
|
+
// you may choose to combine both `where` and `reload` arguments described above
|
366
|
+
|
358
367
|
// now, we can just simply add a "create" callback, to apply our own logic whenever a new Book record is streamed from the backend
|
359
368
|
LiveRecord.Model.all.Book.addCallback('after:create', function() {
|
360
369
|
// let's say you have a code here that adds this new Book on the page
|
@@ -490,6 +499,7 @@
|
|
490
499
|
|
491
500
|
### `MODEL.subscribe(CONFIG)`
|
492
501
|
* `CONFIG` (Object, Optional)
|
502
|
+
* `reload`: (Boolean, Default: false)
|
493
503
|
* `where`: (Object)
|
494
504
|
* `ATTRIBUTENAME_OPERATOR`: (Any Type)
|
495
505
|
* `callbacks`: (Object)
|
@@ -498,6 +508,7 @@
|
|
498
508
|
* `before:create`: (function Object)
|
499
509
|
* `after:create`: (function Object)
|
500
510
|
* subscribes to the `LiveRecord::PublicationsChannel`, which then automatically receives new records from the backend.
|
511
|
+
* when `reload: true`, all records (subject to `where` condition above) are immediately loaded, and not just the new ones.
|
501
512
|
* you can also pass in `callbacks` (see above). These callbacks are only applicable to this subscription, and is independent of the Model and Instance callbacks.
|
502
513
|
* `ATTRIBUTENAME_OPERATOR` means something like (for example): `is_enabled_eq`, where `is_enabled` is the `ATTRIBUTENAME` and `eq` is the `OPERATOR`.
|
503
514
|
* you can have as many `ATTRIBUTENAME_OPERATOR` as you like, but keep in mind that the logic applied to them is "AND", and not "OR". For "OR" conditions, use `ransack`
|
@@ -536,8 +547,11 @@
|
|
536
547
|
* if association is "belongs to", then returns the record (if exists in current store)
|
537
548
|
* (i.e. `bookInstance.user()`, `bookInstance.reviews()`)
|
538
549
|
|
539
|
-
### `MODELINSTANCE.subscribe()`
|
550
|
+
### `MODELINSTANCE.subscribe(config)`
|
551
|
+
* `CONFIG` (Object, Optional)
|
552
|
+
* `reload`: (Boolean, Default: false)
|
540
553
|
* subscribes to the `LiveRecord::ChangesChannel`. This instance should already be subscribed by default after being stored, unless there is a `on:response_error` or manually `unsubscribed()` which then you should manually call this `subscribe()` function after correctly handling the response error, or whenever desired.
|
554
|
+
* when `reload: true`, the record is forced reloaded to make sure all attributes are in-sync
|
541
555
|
* returns the `subscription` object (the ActionCable subscription object itself)
|
542
556
|
|
543
557
|
### `MODELINSTANCE.unsubscribe()`
|
@@ -550,7 +564,7 @@
|
|
550
564
|
* the `subscription` object (the ActionCable subscription object itself)
|
551
565
|
|
552
566
|
### `MODELINSTANCE.create()`
|
553
|
-
* stores the instance to the store, and then `subscribe()` to the `LiveRecord::ChangesChannel` for syncing
|
567
|
+
* stores the instance to the store, and then `subscribe({reload: true})` to the `LiveRecord::ChangesChannel` for syncing
|
554
568
|
* returns the instance
|
555
569
|
|
556
570
|
### `MODELINSTANCE.update(ATTRIBUTES)`
|
@@ -596,6 +610,10 @@
|
|
596
610
|
* MIT
|
597
611
|
|
598
612
|
## Changelog
|
613
|
+
* 0.2.4
|
614
|
+
* you can now pass in `{reload: true}` to `subscribe()` like the folowing:
|
615
|
+
* `MODEL.subscribe({reload: true})` to immediately load all records from backend, and not just the new ones
|
616
|
+
* `MODELINSTANCE.subscribe({reload: true})` to immediately reload the record and make sure it's in-sync
|
599
617
|
* 0.2.3
|
600
618
|
* IMPORTANT! renamed callback from `on:response_error` to `on:responseError` for conformity. So please update your code accordingly.
|
601
619
|
* added [associations](#example-2---model--callbacks--associations):
|
@@ -5,12 +5,12 @@ LiveRecord.Model.create = (config) ->
|
|
5
5
|
|
6
6
|
# NEW
|
7
7
|
Model = (attributes) ->
|
8
|
-
|
8
|
+
@attributes = attributes
|
9
9
|
|
10
|
-
Object.keys(
|
10
|
+
Object.keys(@attributes).forEach (attribute_key) ->
|
11
11
|
if Model.prototype[attribute_key] == undefined
|
12
12
|
Model.prototype[attribute_key] = ->
|
13
|
-
|
13
|
+
@attributes[attribute_key]
|
14
14
|
this
|
15
15
|
|
16
16
|
Model.modelName = config.modelName
|
@@ -49,9 +49,9 @@ LiveRecord.Model.create = (config) ->
|
|
49
49
|
|
50
50
|
Model.subscriptions = []
|
51
51
|
|
52
|
-
Model.subscribe = (config) ->
|
53
|
-
config ||= {}
|
52
|
+
Model.subscribe = (config = {}) ->
|
54
53
|
config.callbacks ||= {}
|
54
|
+
config.reload ||= false
|
55
55
|
|
56
56
|
subscription = App.cable.subscriptions.create(
|
57
57
|
{
|
@@ -61,13 +61,18 @@ LiveRecord.Model.create = (config) ->
|
|
61
61
|
},
|
62
62
|
|
63
63
|
connected: ->
|
64
|
-
if
|
64
|
+
# if forced reload of all records after subscribing, reload only once at the very start of connection, and no longer when reconnecting
|
65
|
+
if config.reload
|
66
|
+
config.reload = false
|
67
|
+
@syncRecords()
|
68
|
+
|
69
|
+
if @liveRecord._staleSince != undefined
|
65
70
|
@syncRecords()
|
66
71
|
|
67
72
|
config.callbacks['on:connect'].call(this) if config.callbacks['on:connect']
|
68
73
|
|
69
74
|
disconnected: ->
|
70
|
-
|
75
|
+
@liveRecord._staleSince = (new Date()).toISOString() unless @liveRecord._staleSince
|
71
76
|
config.callbacks['on:disconnect'].call(this) if config.callbacks['on:disconnect']
|
72
77
|
|
73
78
|
received: (data) ->
|
@@ -85,9 +90,9 @@ LiveRecord.Model.create = (config) ->
|
|
85
90
|
'sync_records',
|
86
91
|
model_name: Model.modelName,
|
87
92
|
where: config.where,
|
88
|
-
stale_since:
|
93
|
+
stale_since: @liveRecord._staleSince
|
89
94
|
)
|
90
|
-
|
95
|
+
@liveRecord._staleSince = undefined
|
91
96
|
)
|
92
97
|
|
93
98
|
subscription.liveRecord = {}
|
@@ -95,36 +100,43 @@ LiveRecord.Model.create = (config) ->
|
|
95
100
|
subscription.liveRecord.where = config.where
|
96
101
|
subscription.liveRecord.callbacks = config.callbacks
|
97
102
|
|
98
|
-
|
103
|
+
@subscriptions.push(subscription)
|
99
104
|
subscription
|
100
105
|
|
101
106
|
Model.unsubscribe = (subscription) ->
|
102
|
-
index =
|
103
|
-
throw new Error('`subscription` argument does not exist in ' +
|
107
|
+
index = @subscriptions.indexOf(subscription)
|
108
|
+
throw new Error('`subscription` argument does not exist in ' + @modelName + ' subscriptions list') if index == -1
|
104
109
|
|
105
110
|
App.cable.subscriptions.remove(subscription)
|
106
111
|
|
107
|
-
|
112
|
+
@subscriptions.splice(index, 1)
|
108
113
|
subscription
|
109
114
|
|
110
|
-
Model.prototype.subscribe = ->
|
111
|
-
return
|
115
|
+
Model.prototype.subscribe = (config = {}) ->
|
116
|
+
return @subscription if @subscription != undefined
|
117
|
+
|
118
|
+
config.reload ||= false
|
112
119
|
|
113
120
|
# listen for record changes (update / destroy)
|
114
|
-
subscription = App['live_record_' +
|
121
|
+
subscription = App['live_record_' + @modelName() + '_' + @id()] = App.cable.subscriptions.create(
|
115
122
|
{
|
116
123
|
channel: 'LiveRecord::ChangesChannel'
|
117
|
-
model_name:
|
118
|
-
record_id:
|
124
|
+
model_name: @modelName()
|
125
|
+
record_id: @id()
|
119
126
|
},
|
120
127
|
|
121
128
|
record: ->
|
122
129
|
return @_record if @_record
|
123
|
-
identifier = JSON.parse(
|
130
|
+
identifier = JSON.parse(@identifier)
|
124
131
|
@_record = Model.all[identifier.record_id]
|
125
132
|
|
126
133
|
# on: connect
|
127
134
|
connected: ->
|
135
|
+
# if forced reload of this record after subscribing, reload only once at the very start of connection, and no longer when reconnecting
|
136
|
+
if config.reload
|
137
|
+
config.reload = false
|
138
|
+
@syncRecord(@record())
|
139
|
+
|
128
140
|
if @record()._staleSince != undefined
|
129
141
|
@syncRecord(@record())
|
130
142
|
|
@@ -173,7 +185,7 @@ LiveRecord.Model.create = (config) ->
|
|
173
185
|
@record()._staleSince = undefined
|
174
186
|
)
|
175
187
|
|
176
|
-
|
188
|
+
@subscription = subscription
|
177
189
|
|
178
190
|
Model.prototype.model = ->
|
179
191
|
Model
|
@@ -182,45 +194,44 @@ LiveRecord.Model.create = (config) ->
|
|
182
194
|
Model.modelName
|
183
195
|
|
184
196
|
Model.prototype.unsubscribe = ->
|
185
|
-
return if
|
186
|
-
App.cable.subscriptions.remove(
|
197
|
+
return if @subscription == undefined
|
198
|
+
App.cable.subscriptions.remove(@subscription)
|
187
199
|
delete this['subscription']
|
188
200
|
|
189
201
|
Model.prototype.isSubscribed = ->
|
190
|
-
|
202
|
+
@subscription != undefined
|
191
203
|
|
192
204
|
# CREATE
|
193
205
|
Model.prototype.create = (options) ->
|
194
|
-
throw new Error(Model.modelName+'('
|
195
|
-
|
206
|
+
throw new Error(Model.modelName+'('+@id()+') is already in the store') if Model.all[@attributes.id]
|
207
|
+
@_callCallbacks('before:create', undefined)
|
196
208
|
|
197
|
-
Model.all[
|
198
|
-
# because we do not know if this newly created object is
|
199
|
-
|
200
|
-
this.subscribe()
|
209
|
+
Model.all[@attributes.id] = this
|
210
|
+
# because we do not know if this newly created object is stale upon creation, then we force reload it
|
211
|
+
@subscribe({reload: true})
|
201
212
|
|
202
|
-
|
213
|
+
@_callCallbacks('after:create', undefined)
|
203
214
|
this
|
204
215
|
|
205
216
|
# UPDATE
|
206
217
|
Model.prototype.update = (attributes) ->
|
207
|
-
|
218
|
+
@_callCallbacks('before:update', undefined)
|
208
219
|
|
209
220
|
self = this
|
210
221
|
Object.keys(attributes).forEach (attribute_key) ->
|
211
222
|
self.attributes[attribute_key] = attributes[attribute_key]
|
212
223
|
|
213
|
-
|
224
|
+
@_callCallbacks('after:update', undefined)
|
214
225
|
true
|
215
226
|
|
216
227
|
# DESTROY
|
217
228
|
Model.prototype.destroy = ->
|
218
|
-
|
229
|
+
@_callCallbacks('before:destroy', undefined)
|
219
230
|
|
220
|
-
|
221
|
-
delete Model.all[
|
231
|
+
@unsubscribe()
|
232
|
+
delete Model.all[@attributes.id]
|
222
233
|
|
223
|
-
|
234
|
+
@_callCallbacks('after:destroy', undefined)
|
224
235
|
this
|
225
236
|
|
226
237
|
# CALLBACKS
|
@@ -251,16 +262,16 @@ LiveRecord.Model.create = (config) ->
|
|
251
262
|
|
252
263
|
# adding new callbackd to the list
|
253
264
|
Model.prototype.addCallback = Model.addCallback = (callbackKey, callbackFunction) ->
|
254
|
-
index =
|
265
|
+
index = @_callbacks[callbackKey].indexOf(callbackFunction)
|
255
266
|
if index == -1
|
256
|
-
|
267
|
+
@_callbacks[callbackKey].push(callbackFunction)
|
257
268
|
return callbackFunction
|
258
269
|
|
259
270
|
# removing a callback from the list
|
260
271
|
Model.prototype.removeCallback = Model.removeCallback = (callbackKey, callbackFunction) ->
|
261
|
-
index =
|
272
|
+
index = @_callbacks[callbackKey].indexOf(callbackFunction)
|
262
273
|
if index != -1
|
263
|
-
|
274
|
+
@_callbacks[callbackKey].splice(index, 1)
|
264
275
|
return callbackFunction
|
265
276
|
|
266
277
|
Model.prototype._callCallbacks = (callbackKey, args) ->
|
@@ -269,15 +280,15 @@ LiveRecord.Model.create = (config) ->
|
|
269
280
|
callback.apply(this, args)
|
270
281
|
|
271
282
|
# call instance callbacks
|
272
|
-
for callback in
|
283
|
+
for callback in @_callbacks[callbackKey]
|
273
284
|
callback.apply(this, args)
|
274
285
|
|
275
286
|
Model.prototype._setChangesFrom = (attributes) ->
|
276
|
-
|
287
|
+
@changes = {}
|
277
288
|
|
278
289
|
for attributeName, attributeValue of attributes
|
279
|
-
unless
|
280
|
-
|
290
|
+
unless @attributes[attributeName] && @attributes[attributeName] == attributeValue
|
291
|
+
@changes[attributeName] = [@attributes[attributeName], attributeValue]
|
281
292
|
|
282
293
|
Model.prototype._unsetChanges = () ->
|
283
294
|
delete this['changes']
|
@@ -13,7 +13,7 @@ class LiveRecord::ChangesChannel < LiveRecord::BaseChannel
|
|
13
13
|
record.reload
|
14
14
|
rescue ActiveRecord::RecordNotFound
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
authorised_attributes = authorised_attributes(record, current_user)
|
18
18
|
|
19
19
|
# if not just :id
|
@@ -33,19 +33,27 @@ class LiveRecord::ChangesChannel < LiveRecord::BaseChannel
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def sync_record(data)
|
36
|
-
|
36
|
+
params = data.symbolize_keys
|
37
|
+
|
38
|
+
find_record_from_params(params) do |record|
|
37
39
|
authorised_attributes = authorised_attributes(record, current_user)
|
38
40
|
|
39
41
|
# if not just :id
|
40
42
|
if authorised_attributes.size > 1
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
43
|
+
live_record_updates = nil
|
44
|
+
|
45
|
+
if params[:stale_since].present?
|
46
|
+
live_record_updates = LiveRecordUpdate.where(
|
47
|
+
recordable_type: record.class.name,
|
48
|
+
recordable_id: record.id
|
49
|
+
).where(
|
50
|
+
'created_at >= ?', DateTime.parse(params[:stale_since]) - LiveRecord.configuration.sync_record_buffer_time
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
# if stale_since is unknown, or there is a live_record_update that has happened while disconnected,
|
55
|
+
# then we update the record in the client-side
|
56
|
+
if params[:stale_since].blank? || live_record_updates.exists?
|
49
57
|
message = { 'action' => 'update', 'attributes' => record.attributes }
|
50
58
|
response = filtered_message(message, authorised_attributes)
|
51
59
|
transmit response if response.present?
|
@@ -56,4 +64,4 @@ class LiveRecord::ChangesChannel < LiveRecord::BaseChannel
|
|
56
64
|
end
|
57
65
|
end
|
58
66
|
end
|
59
|
-
end
|
67
|
+
end
|
@@ -18,7 +18,7 @@ class LiveRecord::PublicationsChannel < LiveRecord::BaseChannel
|
|
18
18
|
newly_created_record = model_class.find(message['attributes']['id'])
|
19
19
|
|
20
20
|
@authorised_attributes ||= authorised_attributes(newly_created_record, current_user)
|
21
|
-
|
21
|
+
|
22
22
|
active_record_relation = SearchAdapters.mapped_active_record_relation(
|
23
23
|
model_class: model_class,
|
24
24
|
conditions_hash: params[:where].to_h,
|
@@ -42,10 +42,13 @@ class LiveRecord::PublicationsChannel < LiveRecord::BaseChannel
|
|
42
42
|
model_class = params[:model_name].safe_constantize
|
43
43
|
|
44
44
|
if model_class && model_class < ApplicationRecord
|
45
|
+
records = model_class.all
|
45
46
|
|
46
|
-
|
47
|
-
|
48
|
-
|
47
|
+
if params[:stale_since].present?
|
48
|
+
records = records.where(
|
49
|
+
'created_at >= ?', DateTime.parse(params[:stale_since]) - LiveRecord.configuration.sync_record_buffer_time
|
50
|
+
)
|
51
|
+
end
|
49
52
|
|
50
53
|
# we `transmmit` a message back to client for each matching record
|
51
54
|
records.find_each do |record|
|
@@ -131,4 +134,4 @@ class LiveRecord::PublicationsChannel < LiveRecord::BaseChannel
|
|
131
134
|
end
|
132
135
|
end
|
133
136
|
end
|
134
|
-
end
|
137
|
+
end
|
data/lib/live_record/version.rb
CHANGED