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 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