live_record 0.2.3 → 0.2.4
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
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