live_record 0.3.2 → 0.3.3
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 +4 -4
- data/README.md +29 -4
- data/app/assets/javascripts/live_record/model/create.coffee +61 -21
- data/app/channels/live_record/autoloads_channel.rb +10 -1
- data/app/channels/live_record/publications_channel.rb +10 -1
- data/lib/live_record/generators/templates/javascript.coffee.rb +8 -0
- data/lib/live_record/generators/templates/javascript.js.rb +22 -5
- data/lib/live_record/version.rb +1 -1
- data/spec/factories/categories.rb +5 -0
- data/spec/factories/posts.rb +1 -0
- data/spec/features/live_record_syncing_spec.rb +88 -1
- data/spec/internal/app/assets/javascripts/categories.coffee +15 -0
- data/spec/internal/app/assets/javascripts/posts.coffee +6 -1
- data/spec/internal/app/assets/javascripts/users.coffee +4 -0
- data/spec/internal/app/models/category.rb +14 -0
- data/spec/internal/app/models/post.rb +3 -2
- data/spec/internal/app/views/posts/_post.json.jbuilder +1 -1
- data/spec/internal/db/schema.rb +10 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de7dc62d17d18d42b77e88503e9a1dce8bdc1993
|
4
|
+
data.tar.gz: 99d005e2f048f497a2dbd152d832ab574b77b05e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d5c2c960ba7d0f48b15b275344edf4dfc134a7d28cbd8ae5ec814ce96b7c70aadb2e37913b9ce6230a9b4fd72201982aa0a608f16d5099aa2f106430d0f3051
|
7
|
+
data.tar.gz: 28c6ab4599f7742a4fc8cfd0554a6194e9b8ab0bb65f181a9789ef1ebb2c0367e33f144ba1ea856728f1ec7eb0a7f0bf1aee1a73418875f03f8d1b806797f0d9
|
data/README.md
CHANGED
@@ -150,7 +150,7 @@
|
|
150
150
|
1. Add the following to your `Gemfile`:
|
151
151
|
|
152
152
|
```ruby
|
153
|
-
gem 'live_record', '~> 0.3.
|
153
|
+
gem 'live_record', '~> 0.3.3'
|
154
154
|
```
|
155
155
|
|
156
156
|
2. Run:
|
@@ -261,7 +261,7 @@
|
|
261
261
|
)
|
262
262
|
```
|
263
263
|
|
264
|
-
### Example 2 - Model + Callbacks +
|
264
|
+
### Example 2 - Model + Associations + Callbacks + Methods
|
265
265
|
|
266
266
|
```js
|
267
267
|
// app/assets/javascripts/books.js
|
@@ -289,6 +289,20 @@
|
|
289
289
|
console.log(this); // `this` refers to the current `Book` record that has just been updated with changes synced from the backend
|
290
290
|
}
|
291
291
|
]
|
292
|
+
},
|
293
|
+
// allows you to do LiveRecord.Model.all.Book.yourOwnNamedClassMethod('arg1', 'arg2')
|
294
|
+
classMethods: {
|
295
|
+
yourOwnNamedClassMethod: function(arg1, arg2) {
|
296
|
+
console.log(this); // `this` refers to `Book` Model
|
297
|
+
return 'somevalue'
|
298
|
+
}
|
299
|
+
},
|
300
|
+
// allows you to do LiveRecord.Model.all.Book.all[1].yourOwnNamedInstanceMethod('arg1', 'arg2')
|
301
|
+
instanceMethods: {
|
302
|
+
yourOwnNamedInstanceMethod: function(arg1, arg2) {
|
303
|
+
console.log(this); // `this` refers to a `Book` record
|
304
|
+
return 'somevalue'
|
305
|
+
}
|
292
306
|
}
|
293
307
|
}
|
294
308
|
)
|
@@ -605,10 +619,15 @@ end
|
|
605
619
|
* `after:update`: (Array of functions)
|
606
620
|
* `before:destroy`: (Array of functions)
|
607
621
|
* `after:destroy`: (Array of functions)
|
622
|
+
* `classMethods`: (Object)
|
623
|
+
* `CLASSMETHODNAME`: (function)
|
624
|
+
* `instanceMethods`: (Object)
|
625
|
+
* `INSTANCEMETHODNAME`: (function)
|
608
626
|
* `plugins`: (Object)
|
609
627
|
* `LiveDOM`: (Boolean)
|
610
628
|
* creates a `MODEL` and stores it into `LiveRecord.Model.all` array
|
611
629
|
* `hasMany` and `belongsTo` `modelName` above should be a valid defined `LiveRecord.Model`
|
630
|
+
* `CLASSMETHODNAME` and `INSTANCEMETHODNAME` can be whatever name you wish, only except some reserved names used by LiveRecord (will throw an error if reserved name is used)
|
612
631
|
* returns the newly created `MODEL`
|
613
632
|
|
614
633
|
### `MODEL.subscribe(CONFIG)`
|
@@ -621,6 +640,7 @@ end
|
|
621
640
|
* `on:disconnect`: (function Object)
|
622
641
|
* `before:create`: (function Object; function argument = record)
|
623
642
|
* `after:create`: (function Object; function argument = record)
|
643
|
+
* `after:reload`: (function Object; function argument = recordIds) **Only works with `reload: true`**
|
624
644
|
* returns an ActionCable subscription object
|
625
645
|
* subscribes to the `LiveRecord::PublicationsChannel`, which then automatically receives new records from the backend.
|
626
646
|
* when `reload: true`, all records (subject to `where` condition above) are immediately loaded, and not just the new ones.
|
@@ -653,6 +673,7 @@ end
|
|
653
673
|
* `on:disconnect`: (function Object)
|
654
674
|
* `before:createOrUpdate`: (function Object; function argument = record)
|
655
675
|
* `after:createOrUpdate`: (function Object; function argument = record)
|
676
|
+
* `after:reload`: (function Object; function argument = recordIds) **Only works with `reload: true`**
|
656
677
|
* returns an ActionCable subscription object
|
657
678
|
* subscribes to the `LiveRecord::AutoloadsChannel`, which then automatically receives new/updated records from the backend.
|
658
679
|
* when `reload: true`, all records (subject to `where` condition above) are immediately loaded, and not just the future new/updated ones.
|
@@ -667,7 +688,7 @@ end
|
|
667
688
|
* unsubscribes to the `LiveRecord::PublicationsChannel`, thereby will not be receiving new records anymore.
|
668
689
|
|
669
690
|
### `MODEL.all`
|
670
|
-
|
691
|
+
* Object of which properties are IDs of the records
|
671
692
|
|
672
693
|
### `new LiveRecord.Model.all.MODELNAME(ATTRIBUTES)`
|
673
694
|
* `ATTRIBUTES` (Object)
|
@@ -752,6 +773,10 @@ end
|
|
752
773
|
* MIT
|
753
774
|
|
754
775
|
## Changelog
|
776
|
+
* 0.3.3
|
777
|
+
* now allows creating [`class` and `instance` methods](#example-2---model--associations--callbacks--methods) when creating a LiveRecord Model
|
778
|
+
* added `after:reload` callback to `autoload()` and `subscribe()` when `reload: true` is passed, which triggers once after reloading has finished transmitting all records
|
779
|
+
* fixed nasty weird bug where defined `hasMany` or `belongsTo` associations share the last value, instead of independently being calculated. (i.e. if `Post` `belongsTo` `user` and `category`, `postInstance.user()` returns a value of the returned value of `postInstance.category()`). Bug is caused by `for ... in` loop and now fixed by using `Object.keys().forEach(...)`, where a variable outside a function is being shared in each loop (as it is referenced inside the function) instead of independently being referenced. [Similar problem here](https://stackoverflow.com/questions/1451009/javascript-infamous-loop-issue)
|
755
780
|
* 0.3.2
|
756
781
|
* fixed `autoload()` `before:createOrUpdate` and `after:createOrUpdate` callbacks not triggering`
|
757
782
|
* 0.3.1
|
@@ -778,7 +803,7 @@ end
|
|
778
803
|
* `MODELINSTANCE.subscribe({reload: true})` to immediately reload the record and make sure it's in-sync
|
779
804
|
* 0.2.3
|
780
805
|
* IMPORTANT! renamed callback from `on:response_error` to `on:responseError` for conformity. So please update your code accordingly.
|
781
|
-
* added [associations](#example-2---model--callbacks--
|
806
|
+
* added [associations](#example-2---model--associations--callbacks--methods):
|
782
807
|
* `hasMany` which allows you to do `bookInstance.reviews()`
|
783
808
|
* `belongsTo` which allows you to do `bookInstance.user()`
|
784
809
|
* fixed `loadRecords()` throwing an error when there is no response
|
@@ -32,31 +32,41 @@ LiveRecord.Model.create = (config) ->
|
|
32
32
|
hasMany: config.hasMany
|
33
33
|
belongsTo: config.belongsTo
|
34
34
|
|
35
|
-
# getting has_many association
|
36
|
-
|
37
|
-
Model.
|
38
|
-
|
39
|
-
|
40
|
-
throw new Error('No defined model for "' + associationConfig.modelName + '"') unless associatedModel
|
35
|
+
# getting has_many association record
|
36
|
+
if Model.associations.hasMany
|
37
|
+
Object.keys(Model.associations.hasMany).forEach((key, index) ->
|
38
|
+
associationName = key
|
39
|
+
associationConfig = Model.associations.hasMany[associationName]
|
41
40
|
|
42
|
-
|
43
|
-
|
41
|
+
Model.prototype[associationName] = () ->
|
42
|
+
self = this
|
43
|
+
associatedModel = LiveRecord.Model.all[associationConfig.modelName]
|
44
|
+
throw new Error('No defined model for "' + associationConfig.modelName + '"') unless associatedModel
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
associatedRecords.push(record) if isAssociated
|
46
|
+
# TODO: speed up searching for associated records, or use cache-maps
|
47
|
+
associatedRecords = []
|
48
48
|
|
49
|
-
|
49
|
+
for id, record of associatedModel.all
|
50
|
+
isAssociated = record[associationConfig.foreignKey]() == self.id()
|
51
|
+
associatedRecords.push(record) if isAssociated
|
50
52
|
|
51
|
-
|
52
|
-
|
53
|
-
Model.prototype[associationName] = ->
|
54
|
-
self = this
|
55
|
-
associatedModel = LiveRecord.Model.all[associationConfig.modelName]
|
56
|
-
throw new Error('No defined model for "' + associationConfig.modelName + '"') unless associatedModel
|
53
|
+
associatedRecords
|
54
|
+
)
|
57
55
|
|
58
|
-
|
59
|
-
|
56
|
+
# getting belongs_to association record
|
57
|
+
if Model.associations.belongsTo
|
58
|
+
Object.keys(Model.associations.belongsTo).forEach((key, index) ->
|
59
|
+
associationName = key
|
60
|
+
associationConfig = Model.associations.belongsTo[associationName]
|
61
|
+
|
62
|
+
Model.prototype[associationName] = () ->
|
63
|
+
self = this
|
64
|
+
associatedModel = LiveRecord.Model.all[associationConfig.modelName]
|
65
|
+
throw new Error('No defined model for "' + associationConfig.modelName + '"') unless associatedModel
|
66
|
+
|
67
|
+
belongsToID = self[associationConfig.foreignKey]()
|
68
|
+
associatedModel.all[belongsToID]
|
69
|
+
)
|
60
70
|
|
61
71
|
Model.all = {}
|
62
72
|
|
@@ -66,6 +76,9 @@ LiveRecord.Model.create = (config) ->
|
|
66
76
|
config.callbacks ||= {}
|
67
77
|
config.reload ||= false
|
68
78
|
|
79
|
+
if config.callbacks.afterReload && !config.reload
|
80
|
+
throw new Error('`afterReload` callback only works with `reload: true`')
|
81
|
+
|
69
82
|
subscription = App.cable.subscriptions.create(
|
70
83
|
{
|
71
84
|
channel: 'LiveRecord::AutoloadsChannel'
|
@@ -114,6 +127,9 @@ LiveRecord.Model.create = (config) ->
|
|
114
127
|
record.create()
|
115
128
|
config.callbacks['after:createOrUpdate'].call(this, record) if config.callbacks['after:createOrUpdate']
|
116
129
|
|
130
|
+
afterReload: (data) ->
|
131
|
+
config.callbacks['after:reload'].call(this, data.recordIds) if config.callbacks['after:reload']
|
132
|
+
|
117
133
|
# handler for received() callback above
|
118
134
|
onError:
|
119
135
|
forbidden: (data) ->
|
@@ -143,6 +159,9 @@ LiveRecord.Model.create = (config) ->
|
|
143
159
|
config.callbacks ||= {}
|
144
160
|
config.reload ||= false
|
145
161
|
|
162
|
+
if config.callbacks.afterReload && !config.reload
|
163
|
+
throw new Error('`afterReload` callback only works with `reload: true`')
|
164
|
+
|
146
165
|
subscription = App.cable.subscriptions.create(
|
147
166
|
{
|
148
167
|
channel: 'LiveRecord::PublicationsChannel'
|
@@ -179,6 +198,9 @@ LiveRecord.Model.create = (config) ->
|
|
179
198
|
record.create()
|
180
199
|
config.callbacks['after:create'].call(this, record) if config.callbacks['after:create']
|
181
200
|
|
201
|
+
afterReload: (data) ->
|
202
|
+
config.callbacks['after:reload'].call(this, data.recordIds) if config.callbacks['after:reload']
|
203
|
+
|
182
204
|
# handler for received() callback above
|
183
205
|
onError:
|
184
206
|
forbidden: (data) ->
|
@@ -213,6 +235,11 @@ LiveRecord.Model.create = (config) ->
|
|
213
235
|
@subscriptions.splice(index, 1)
|
214
236
|
subscription
|
215
237
|
|
238
|
+
# create class methods
|
239
|
+
for methodKey, methodValue of config.classMethods
|
240
|
+
throw new Error('Cannot use reserved name as class method: ', methodKey) if Model[methodKey] != undefined
|
241
|
+
Model[methodKey] = methodValue
|
242
|
+
|
216
243
|
Model.prototype.subscribe = (config = {}) ->
|
217
244
|
return @subscription if @subscription != undefined
|
218
245
|
|
@@ -316,9 +343,17 @@ LiveRecord.Model.create = (config) ->
|
|
316
343
|
|
317
344
|
# UPDATE
|
318
345
|
Model.prototype.update = (attributes) ->
|
346
|
+
self = this
|
347
|
+
|
348
|
+
# sometimes there are new attributes that were not there yet upon initialization
|
349
|
+
# so when updated() make sure to create helper getter functions for each new attribute as below
|
350
|
+
Object.keys(attributes).forEach (attribute_key) ->
|
351
|
+
if Model.prototype[attribute_key] == undefined
|
352
|
+
Model.prototype[attribute_key] = ->
|
353
|
+
@attributes[attribute_key]
|
354
|
+
|
319
355
|
@_callCallbacks('before:update', undefined)
|
320
356
|
|
321
|
-
self = this
|
322
357
|
Object.keys(attributes).forEach (attribute_key) ->
|
323
358
|
self.attributes[attribute_key] = attributes[attribute_key]
|
324
359
|
|
@@ -382,6 +417,11 @@ LiveRecord.Model.create = (config) ->
|
|
382
417
|
Model.prototype._unsetChanges = () ->
|
383
418
|
delete this['changes']
|
384
419
|
|
420
|
+
# create instance methods
|
421
|
+
for methodKey, methodValue of config.instanceMethods
|
422
|
+
throw new Error('Cannot use reserved name as instance method: ', methodKey) if Model.prototype[methodKey] != undefined
|
423
|
+
Model.prototype[methodKey] = methodValue
|
424
|
+
|
385
425
|
# AFTER MODEL INITIALISATION
|
386
426
|
|
387
427
|
# add callbacks from arguments
|
@@ -55,11 +55,14 @@ class LiveRecord::AutoloadsChannel < LiveRecord::BaseChannel
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
+
# TODO: split up sync_records action because it currently both handles "syncing" and "reloading"
|
58
59
|
def sync_records(data)
|
59
60
|
params = data.symbolize_keys
|
60
61
|
model_class = params[:model_name].safe_constantize
|
61
62
|
|
62
63
|
if model_class && model_class < ApplicationRecord
|
64
|
+
is_being_reloaded = params[:stale_since].blank?
|
65
|
+
is_being_synced = params[:stale_since].present?
|
63
66
|
|
64
67
|
active_record_relation = LiveRecord::BaseChannel::SearchAdapters.mapped_active_record_relation(
|
65
68
|
model_class: model_class,
|
@@ -67,7 +70,7 @@ class LiveRecord::AutoloadsChannel < LiveRecord::BaseChannel
|
|
67
70
|
current_user: current_user,
|
68
71
|
)
|
69
72
|
|
70
|
-
if
|
73
|
+
if is_being_synced
|
71
74
|
active_record_relation = active_record_relation.where(
|
72
75
|
'updated_at >= ?', DateTime.parse(params[:stale_since]) - LiveRecord.configuration.sync_record_buffer_time
|
73
76
|
)
|
@@ -84,6 +87,12 @@ class LiveRecord::AutoloadsChannel < LiveRecord::BaseChannel
|
|
84
87
|
transmit response if response.present?
|
85
88
|
end
|
86
89
|
end
|
90
|
+
|
91
|
+
# if being reloaded, we finally still transmit a "done" action indicating that reloading has just finished
|
92
|
+
if is_being_reloaded
|
93
|
+
response = { 'action' => 'afterReload', 'recordIds' => active_record_relation.pluck(:id) }
|
94
|
+
transmit response
|
95
|
+
end
|
87
96
|
else
|
88
97
|
respond_with_error(:bad_request, 'Not a correct model name')
|
89
98
|
reject_subscription
|
@@ -40,11 +40,14 @@ class LiveRecord::PublicationsChannel < LiveRecord::BaseChannel
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
+
# TODO: split up sync_records action because it currently both handles "syncing" and "reloading"
|
43
44
|
def sync_records(data)
|
44
45
|
params = data.symbolize_keys
|
45
46
|
model_class = params[:model_name].safe_constantize
|
46
47
|
|
47
48
|
if model_class && model_class < ApplicationRecord
|
49
|
+
is_being_reloaded = params[:stale_since].blank?
|
50
|
+
is_being_synced = params[:stale_since].present?
|
48
51
|
|
49
52
|
active_record_relation = LiveRecord::BaseChannel::SearchAdapters.mapped_active_record_relation(
|
50
53
|
model_class: model_class,
|
@@ -52,7 +55,7 @@ class LiveRecord::PublicationsChannel < LiveRecord::BaseChannel
|
|
52
55
|
current_user: current_user,
|
53
56
|
)
|
54
57
|
|
55
|
-
if
|
58
|
+
if is_being_synced
|
56
59
|
active_record_relation = active_record_relation.where(
|
57
60
|
'created_at >= ?', DateTime.parse(params[:stale_since]) - LiveRecord.configuration.sync_record_buffer_time
|
58
61
|
)
|
@@ -69,6 +72,12 @@ class LiveRecord::PublicationsChannel < LiveRecord::BaseChannel
|
|
69
72
|
transmit response if response.present?
|
70
73
|
end
|
71
74
|
end
|
75
|
+
|
76
|
+
# if being reloaded, we finally still transmit a "done" action indicating that reloading has just finished
|
77
|
+
if is_being_reloaded
|
78
|
+
response = { 'action' => 'afterReload', 'recordIds' => active_record_relation.pluck(:id) }
|
79
|
+
transmit response
|
80
|
+
end
|
72
81
|
else
|
73
82
|
respond_with_error(:bad_request, 'Not a correct model name')
|
74
83
|
reject_subscription
|
@@ -17,6 +17,14 @@ LiveRecord.Model.create(
|
|
17
17
|
# callbacks: {
|
18
18
|
# 'on:disconnect': [],
|
19
19
|
# 'after:update': [],
|
20
|
+
# },
|
21
|
+
# classMethods: {
|
22
|
+
# someMethod: (arg1, arg2) ->
|
23
|
+
# return 'somevalue'
|
24
|
+
# },
|
25
|
+
# instanceMethods: {
|
26
|
+
# someMethod: (arg1, arg2) ->
|
27
|
+
# return 'somevalue'
|
20
28
|
# }
|
21
29
|
}
|
22
30
|
)
|
@@ -1,16 +1,33 @@
|
|
1
1
|
<% module_namespacing do -%>
|
2
2
|
LiveRecord.Model.create(
|
3
3
|
{
|
4
|
-
modelName: '<%=
|
4
|
+
modelName: '<%= singular_table_name.camelcase %>',
|
5
5
|
plugins: {
|
6
|
+
// remove this line if you're not using LiveDOM
|
6
7
|
LiveDOM: true
|
7
8
|
},
|
8
|
-
|
9
|
-
//
|
9
|
+
|
10
|
+
// More configurations below. See https://github.com/jrpolidario/live_record#example-1---model
|
11
|
+
// belongsTo: {
|
12
|
+
// user: { foreignKey: 'user_id', modelName: 'User' }
|
13
|
+
// },
|
14
|
+
// hasMany: {
|
15
|
+
// books: { foreignKey: '<%= singular_table_name %>_id', modelName: 'Book' }
|
16
|
+
// },
|
10
17
|
// callbacks: {
|
11
18
|
// 'on:disconnect': [],
|
12
|
-
// 'after:update': []
|
19
|
+
// 'after:update': []
|
20
|
+
// },
|
21
|
+
// classMethods: {
|
22
|
+
// someMethod: function(arg1, arg2) {
|
23
|
+
// return 'somevalue';
|
24
|
+
// }
|
25
|
+
// },
|
26
|
+
// instanceMethods: {
|
27
|
+
// someMethod: function(arg1, arg2) {
|
28
|
+
// return 'somevalue';
|
29
|
+
// }
|
13
30
|
// }
|
14
31
|
}
|
15
32
|
)
|
16
|
-
<% end -%>
|
33
|
+
<% end -%>
|
data/lib/live_record/version.rb
CHANGED
data/spec/factories/posts.rb
CHANGED
@@ -8,7 +8,7 @@ RSpec.feature 'LiveRecord Syncing', type: :feature do
|
|
8
8
|
let(:post3) { create(:post, user: nil) }
|
9
9
|
let!(:users) { [user1, user2] }
|
10
10
|
let!(:posts) { [post1, post2, post3] }
|
11
|
-
|
11
|
+
|
12
12
|
scenario 'User sees live changes (updates) of post records', js: true do
|
13
13
|
visit '/posts'
|
14
14
|
|
@@ -282,6 +282,93 @@ RSpec.feature 'LiveRecord Syncing', type: :feature do
|
|
282
282
|
sleep(5)
|
283
283
|
end
|
284
284
|
|
285
|
+
scenario 'JS-Client can access defined belongsTo() and hasMany() associations', js: true do
|
286
|
+
# prepopulate
|
287
|
+
category_that_do_not_have_posts = create(:category)
|
288
|
+
category_that_has_posts = create(:category)
|
289
|
+
category_that_has_posts.tap do |category|
|
290
|
+
create(:post, category: category)
|
291
|
+
create(:post, category: category)
|
292
|
+
end
|
293
|
+
user_that_do_not_have_posts = create(:user)
|
294
|
+
user_that_have_posts = create(:user)
|
295
|
+
user_that_have_posts.tap do |user|
|
296
|
+
create(:post, user: user)
|
297
|
+
create(:post, user: user)
|
298
|
+
end
|
299
|
+
post_that_do_not_belong_to_user_nor_category = create(:post, user: nil, category: nil)
|
300
|
+
post_that_belongs_to_user_but_not_category = create(:post, user: create(:user), category: nil)
|
301
|
+
post_that_belongs_to_category_but_not_user = create(:post, category: create(:category), user: nil)
|
302
|
+
post_that_belongs_to_both_category_and_user = create(:post, category: create(:category), user: create(:user))
|
303
|
+
|
304
|
+
visit '/posts'
|
305
|
+
|
306
|
+
execute_script(
|
307
|
+
<<-eos
|
308
|
+
LiveRecord.Model.all.Post.autoload({reload: true});
|
309
|
+
LiveRecord.Model.all.User.autoload({reload: true});
|
310
|
+
LiveRecord.Model.all.Category.autoload({reload: true});
|
311
|
+
eos
|
312
|
+
)
|
313
|
+
|
314
|
+
'wait first for all records to be loaded'.tap do
|
315
|
+
wait before: -> { evaluate_script("Object.keys( LiveRecord.Model.all.Post.all ).length") }, becomes: -> (value) { value == Post.all.count }, duration: 10.seconds
|
316
|
+
expect(evaluate_script("Object.keys( LiveRecord.Model.all.Post.all ).length")).to be Post.all.count
|
317
|
+
|
318
|
+
wait before: -> { evaluate_script("Object.keys( LiveRecord.Model.all.User.all ).length") }, becomes: -> (value) { value == Post.all.count }, duration: 10.seconds
|
319
|
+
expect(evaluate_script("Object.keys( LiveRecord.Model.all.User.all ).length")).to be User.all.count
|
320
|
+
|
321
|
+
wait before: -> { evaluate_script("Object.keys( LiveRecord.Model.all.Category.all ).length") }, becomes: -> (value) { value == Post.all.count }, duration: 10.seconds
|
322
|
+
expect(evaluate_script("Object.keys( LiveRecord.Model.all.Category.all ).length")).to be Category.all.count
|
323
|
+
end
|
324
|
+
|
325
|
+
'now check if associations are correct / matching'.tap do
|
326
|
+
expect(evaluate_script(
|
327
|
+
"LiveRecord.Model.all.Category.all[#{category_that_do_not_have_posts.id}].posts().map(function(post) {return post.id()})"
|
328
|
+
)).to eq category_that_do_not_have_posts.posts.pluck(:id)
|
329
|
+
|
330
|
+
expect(evaluate_script(
|
331
|
+
"LiveRecord.Model.all.Category.all[#{category_that_has_posts.id}].posts().map(function(post) {return post.id()})"
|
332
|
+
)).to eq category_that_has_posts.posts.pluck(:id)
|
333
|
+
|
334
|
+
expect(evaluate_script(
|
335
|
+
"LiveRecord.Model.all.User.all[#{user_that_do_not_have_posts.id}].posts().map(function(post) {return post.id()})"
|
336
|
+
)).to eq user_that_do_not_have_posts.posts.pluck(:id)
|
337
|
+
|
338
|
+
expect(evaluate_script(
|
339
|
+
"LiveRecord.Model.all.User.all[#{user_that_have_posts.id}].posts().map(function(post) {return post.id()})"
|
340
|
+
)).to eq user_that_have_posts.posts.pluck(:id)
|
341
|
+
|
342
|
+
expect(evaluate_script(
|
343
|
+
"LiveRecord.Model.all.Post.all[#{post_that_do_not_belong_to_user_nor_category.id}].user()"
|
344
|
+
)).to eq nil
|
345
|
+
expect(evaluate_script(
|
346
|
+
"LiveRecord.Model.all.Post.all[#{post_that_do_not_belong_to_user_nor_category.id}].category()"
|
347
|
+
)).to eq nil
|
348
|
+
|
349
|
+
expect(evaluate_script(
|
350
|
+
"LiveRecord.Model.all.Post.all[#{post_that_belongs_to_user_but_not_category.id}].user().id()"
|
351
|
+
)).to eq post_that_belongs_to_user_but_not_category.user.id
|
352
|
+
expect(evaluate_script(
|
353
|
+
"LiveRecord.Model.all.Post.all[#{post_that_belongs_to_user_but_not_category.id}].category()"
|
354
|
+
)).to eq nil
|
355
|
+
|
356
|
+
expect(evaluate_script(
|
357
|
+
"LiveRecord.Model.all.Post.all[#{post_that_belongs_to_category_but_not_user.id}].category().id()"
|
358
|
+
)).to eq post_that_belongs_to_category_but_not_user.category.id
|
359
|
+
expect(evaluate_script(
|
360
|
+
"LiveRecord.Model.all.Post.all[#{post_that_belongs_to_category_but_not_user.id}].user()"
|
361
|
+
)).to eq nil
|
362
|
+
|
363
|
+
expect(evaluate_script(
|
364
|
+
"LiveRecord.Model.all.Post.all[#{post_that_belongs_to_both_category_and_user.id}].category().id()"
|
365
|
+
)).to eq post_that_belongs_to_both_category_and_user.category.id
|
366
|
+
expect(evaluate_script(
|
367
|
+
"LiveRecord.Model.all.Post.all[#{post_that_belongs_to_both_category_and_user.id}].user().id()"
|
368
|
+
)).to eq post_that_belongs_to_both_category_and_user.user.id
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
285
372
|
# see spec/internal/app/models/post.rb to view specified whitelisted attributes
|
286
373
|
context 'when client got disconnected, and then reconnected' do
|
287
374
|
scenario 'JS-Client resyncs stale records', js: true do
|
@@ -2,10 +2,15 @@ LiveRecord.Model.create(
|
|
2
2
|
{
|
3
3
|
modelName: 'Post',
|
4
4
|
belongsTo: {
|
5
|
-
user: { foreignKey: 'user_id', modelName: 'User' }
|
5
|
+
user: { foreignKey: 'user_id', modelName: 'User' },
|
6
|
+
category: { foreignKey: 'category_id', modelName: 'Category' }
|
6
7
|
},
|
7
8
|
plugins: {
|
8
9
|
LiveDOM: true
|
10
|
+
},
|
11
|
+
classMethods: {
|
12
|
+
},
|
13
|
+
instanceMethods: {
|
9
14
|
}
|
10
15
|
}
|
11
16
|
)
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Category < ApplicationRecord
|
2
|
+
include LiveRecord::Model::Callbacks
|
3
|
+
|
4
|
+
has_many :live_record_updates, as: :recordable, dependent: :destroy
|
5
|
+
has_many :posts
|
6
|
+
|
7
|
+
def self.live_record_whitelisted_attributes(category, current_user)
|
8
|
+
[:id, :name, :created_at, :updated_at]
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.live_record_queryable_attributes(current_user)
|
12
|
+
[:id, :name, :created_at, :updated_at]
|
13
|
+
end
|
14
|
+
end
|
@@ -2,13 +2,14 @@ class Post < ApplicationRecord
|
|
2
2
|
include LiveRecord::Model::Callbacks
|
3
3
|
|
4
4
|
belongs_to :user
|
5
|
+
belongs_to :category
|
5
6
|
has_many :live_record_updates, as: :recordable, dependent: :destroy
|
6
7
|
|
7
8
|
def self.live_record_whitelisted_attributes(post, current_user)
|
8
|
-
[:id, :title, :is_enabled, :user_id, :created_at, :updated_at]
|
9
|
+
[:id, :title, :is_enabled, :category_id, :user_id, :created_at, :updated_at]
|
9
10
|
end
|
10
11
|
|
11
12
|
def self.live_record_queryable_attributes(current_user)
|
12
|
-
[:id, :title, :is_enabled, :user_id, :created_at, :updated_at]
|
13
|
+
[:id, :title, :is_enabled, :category_id, :user_id, :created_at, :updated_at]
|
13
14
|
end
|
14
15
|
end
|
@@ -1,2 +1,2 @@
|
|
1
|
-
json.extract! post, :id, :title, :content, :user_id, :created_at, :updated_at
|
1
|
+
json.extract! post, :id, :title, :content, :user_id, :category_id, :created_at, :updated_at
|
2
2
|
json.url post_url(post, format: :json)
|
data/spec/internal/db/schema.rb
CHANGED
@@ -12,10 +12,12 @@ ActiveRecord::Schema.define do
|
|
12
12
|
t.text "content"
|
13
13
|
t.boolean "is_enabled"
|
14
14
|
t.integer "user_id"
|
15
|
+
t.integer "category_id"
|
15
16
|
t.datetime "created_at", null: false
|
16
17
|
t.datetime "updated_at", null: false
|
17
18
|
t.index ["is_enabled"], name: "index_posts_on_is_enabled"
|
18
19
|
t.index ["user_id"], name: "index_posts_on_user_id"
|
20
|
+
t.index ["category_id"], name: "index_posts_on_category_id"
|
19
21
|
t.index ["created_at"], name: "index_posts_on_created_at"
|
20
22
|
t.index ["updated_at"], name: "index_posts_on_updated_at"
|
21
23
|
end
|
@@ -27,4 +29,12 @@ ActiveRecord::Schema.define do
|
|
27
29
|
t.index ["created_at"], name: "index_users_on_created_at"
|
28
30
|
t.index ["updated_at"], name: "index_users_on_updated_at"
|
29
31
|
end
|
32
|
+
|
33
|
+
create_table "categories", force: :cascade do |t|
|
34
|
+
t.string "name"
|
35
|
+
t.datetime "created_at", null: false
|
36
|
+
t.datetime "updated_at", null: false
|
37
|
+
t.index ["created_at"], name: "index_categories_on_created_at"
|
38
|
+
t.index ["updated_at"], name: "index_categories_on_updated_at"
|
39
|
+
end
|
30
40
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: live_record
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jules Roman B. Polidario
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-11-
|
11
|
+
date: 2017-11-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -319,6 +319,7 @@ files:
|
|
319
319
|
- lib/live_record/model/callbacks.rb
|
320
320
|
- lib/live_record/version.rb
|
321
321
|
- live_record.gemspec
|
322
|
+
- spec/factories/categories.rb
|
322
323
|
- spec/factories/posts.rb
|
323
324
|
- spec/factories/users.rb
|
324
325
|
- spec/features/live_record_syncing_spec.rb
|
@@ -326,6 +327,7 @@ files:
|
|
326
327
|
- spec/internal/app/assets/config/manifest.js
|
327
328
|
- spec/internal/app/assets/javascripts/application.js
|
328
329
|
- spec/internal/app/assets/javascripts/cable.js
|
330
|
+
- spec/internal/app/assets/javascripts/categories.coffee
|
329
331
|
- spec/internal/app/assets/javascripts/posts.coffee
|
330
332
|
- spec/internal/app/assets/javascripts/users.coffee
|
331
333
|
- spec/internal/app/channels/application_cable/channel.rb
|
@@ -334,6 +336,7 @@ files:
|
|
334
336
|
- spec/internal/app/controllers/posts_controller.rb
|
335
337
|
- spec/internal/app/controllers/users_controller.rb
|
336
338
|
- spec/internal/app/models/application_record.rb
|
339
|
+
- spec/internal/app/models/category.rb
|
337
340
|
- spec/internal/app/models/live_record_update.rb
|
338
341
|
- spec/internal/app/models/post.rb
|
339
342
|
- spec/internal/app/models/user.rb
|