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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c61bf9ab10a4f26e259ef3750b09850b6327dfc0
4
- data.tar.gz: b6e83b2a424ebf4933e97e10ce3333ed80a45c7d
3
+ metadata.gz: de7dc62d17d18d42b77e88503e9a1dce8bdc1993
4
+ data.tar.gz: 99d005e2f048f497a2dbd152d832ab574b77b05e
5
5
  SHA512:
6
- metadata.gz: 68339d0891798614e1570765aeca0a1069dcfbc76de04ba8c249afb14cd3f3309fdc139ef46c5feaae3257e382d922c1a971862d981bee9693a01bbc4be57d8a
7
- data.tar.gz: aaee410eed406ac43b3f013759e8e45d0489ff08e553b5a2136403a8770d4a3346a19c699aed380e21de6735b22c663db48af48cec0a7f09cb6b35476c2e91be
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.2'
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 + Associations
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
- * Object of which properties are IDs of the records
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--associations):
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 records
36
- for associationName, associationConfig of Model.associations.hasMany
37
- Model.prototype[associationName] = ->
38
- self = this
39
- associatedModel = LiveRecord.Model.all[associationConfig.modelName]
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
- # TODO: speed up searching for associated records, or use cache-maps
43
- associatedRecords = []
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
- for id, record of associatedModel.all
46
- isAssociated = record[associationConfig.foreignKey]() == self.id()
47
- associatedRecords.push(record) if isAssociated
46
+ # TODO: speed up searching for associated records, or use cache-maps
47
+ associatedRecords = []
48
48
 
49
- associatedRecords
49
+ for id, record of associatedModel.all
50
+ isAssociated = record[associationConfig.foreignKey]() == self.id()
51
+ associatedRecords.push(record) if isAssociated
50
52
 
51
- # getting belongs_to association record
52
- for associationName, associationConfig of Model.associations.belongsTo
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
- belongsToID = self[associationConfig.foreignKey]()
59
- associatedModel.all[belongsToID]
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 params[:stale_since].present?
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 params[:stale_since].present?
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: '<%= file_name.singularize.camelcase %>',
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
- // See TODO: URL_TO_DOCUMENTATION for supported callbacks
9
- // Add Callbacks (callback name => array of functions)
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 -%>
@@ -1,3 +1,3 @@
1
1
  module LiveRecord
2
- VERSION = '0.3.2'.freeze
2
+ VERSION = '0.3.3'.freeze
3
3
  end
@@ -0,0 +1,5 @@
1
+ FactoryGirl.define do
2
+ factory :category do
3
+ name { Faker::Lorem.word }
4
+ end
5
+ end
@@ -1,6 +1,7 @@
1
1
  FactoryGirl.define do
2
2
  factory :post do
3
3
  user
4
+ category
4
5
  title { Faker::Lorem.sentence }
5
6
  content { Faker::Lorem.paragraph }
6
7
  end
@@ -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
@@ -0,0 +1,15 @@
1
+ LiveRecord.Model.create(
2
+ {
3
+ modelName: 'Category',
4
+ hasMany: {
5
+ posts: { foreignKey: 'category_id', modelName: 'Post' }
6
+ },
7
+ plugins: {
8
+ LiveDOM: true
9
+ },
10
+ classMethods: {
11
+ },
12
+ instanceMethods: {
13
+ }
14
+ }
15
+ )
@@ -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
  )
@@ -6,6 +6,10 @@ LiveRecord.Model.create(
6
6
  },
7
7
  plugins: {
8
8
  LiveDOM: true
9
+ },
10
+ classMethods: {
11
+ },
12
+ instanceMethods: {
9
13
  }
10
14
  }
11
15
  )
@@ -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)
@@ -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.2
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-14 00:00:00.000000000 Z
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