live_record 0.3.2 → 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|