live_record 0.2.8 → 0.3.0

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.
@@ -1,30 +1,5 @@
1
1
  class LiveRecord::BaseChannel < ActionCable::Channel::Base
2
2
 
3
- module Helpers
4
- def self.whitelisted_attributes(record, current_user)
5
- whitelisted_attributes = record.class.live_record_whitelisted_attributes(record, current_user)
6
-
7
- unless whitelisted_attributes.is_a? Array
8
- raise "#{record.class}.live_record_whitelisted_attributes should return an array"
9
- end
10
-
11
- whitelisted_attributes = whitelisted_attributes.map(&:to_s)
12
-
13
- if !whitelisted_attributes.empty? && !whitelisted_attributes.include?('id')
14
- raise "#{record.class}.live_record_whitelisted_attributes should return an array that also includes the :id attribute, as you are authorizing at least one other attribute along with it."
15
- end
16
-
17
- whitelisted_attributes.to_set
18
- end
19
-
20
- def self.queryable_attributes(model_class, current_user)
21
- queryable_attributes = model_class.live_record_queryable_attributes(current_user)
22
- raise "#{model_class}.live_record_queryable_attributes should return an array" unless queryable_attributes.is_a? Array
23
- queryable_attributes = queryable_attributes.map(&:to_s)
24
- queryable_attributes.to_set
25
- end
26
- end
27
-
28
3
  protected
29
4
 
30
5
  def filtered_message(message, filters)
@@ -0,0 +1,27 @@
1
+ class LiveRecord::BaseChannel
2
+
3
+ module Helpers
4
+ def self.whitelisted_attributes(record, current_user)
5
+ whitelisted_attributes = record.class.live_record_whitelisted_attributes(record, current_user)
6
+
7
+ unless whitelisted_attributes.is_a? Array
8
+ raise "#{record.class}.live_record_whitelisted_attributes should return an array"
9
+ end
10
+
11
+ whitelisted_attributes = whitelisted_attributes.map(&:to_s)
12
+
13
+ if !whitelisted_attributes.empty? && !whitelisted_attributes.include?('id')
14
+ raise "#{record.class}.live_record_whitelisted_attributes should return an array that also includes the :id attribute, as you are authorizing at least one other attribute along with it."
15
+ end
16
+
17
+ whitelisted_attributes.to_set
18
+ end
19
+
20
+ def self.queryable_attributes(model_class, current_user)
21
+ queryable_attributes = model_class.live_record_queryable_attributes(current_user)
22
+ raise "#{model_class}.live_record_queryable_attributes should return an array" unless queryable_attributes.is_a? Array
23
+ queryable_attributes = queryable_attributes.map(&:to_s)
24
+ queryable_attributes.to_set
25
+ end
26
+ end
27
+ end
@@ -1,4 +1,4 @@
1
- class LiveRecord::PublicationsChannel
1
+ class LiveRecord::BaseChannel
2
2
 
3
3
  module SearchAdapters
4
4
  def self.mapped_active_record_relation(**args)
@@ -5,7 +5,7 @@ class LiveRecord::ChangesChannel < LiveRecord::BaseChannel
5
5
 
6
6
  def subscribed
7
7
  find_record_from_params(params) do |record|
8
- whitelisted_attributes = Helpers.whitelisted_attributes(record, current_user)
8
+ whitelisted_attributes = LiveRecord::BaseChannel::Helpers.whitelisted_attributes(record, current_user)
9
9
 
10
10
  if whitelisted_attributes.present?
11
11
  stream_for record, coder: ActiveSupport::JSON do |message|
@@ -14,7 +14,7 @@ class LiveRecord::ChangesChannel < LiveRecord::BaseChannel
14
14
  rescue ActiveRecord::RecordNotFound
15
15
  end
16
16
 
17
- whitelisted_attributes = Helpers.whitelisted_attributes(record, current_user)
17
+ whitelisted_attributes = LiveRecord::BaseChannel::Helpers.whitelisted_attributes(record, current_user)
18
18
 
19
19
  if whitelisted_attributes.size > 0
20
20
  response = filtered_message(message, whitelisted_attributes)
@@ -35,7 +35,7 @@ class LiveRecord::ChangesChannel < LiveRecord::BaseChannel
35
35
  params = data.symbolize_keys
36
36
 
37
37
  find_record_from_params(params) do |record|
38
- whitelisted_attributes = Helpers.whitelisted_attributes(record, current_user)
38
+ whitelisted_attributes = LiveRecord::BaseChannel::Helpers.whitelisted_attributes(record, current_user)
39
39
 
40
40
  if whitelisted_attributes.size > 0
41
41
  live_record_updates = nil
@@ -14,7 +14,7 @@ class LiveRecord::PublicationsChannel < LiveRecord::BaseChannel
14
14
  reject_subscription
15
15
  end
16
16
 
17
- if !(model_class && Helpers.queryable_attributes(model_class, current_user).present?)
17
+ if !(model_class && LiveRecord::BaseChannel::Helpers.queryable_attributes(model_class, current_user).present?)
18
18
  respond_with_error(:forbidden, 'You do not have privileges to query')
19
19
  reject_subscription
20
20
  end
@@ -22,14 +22,14 @@ class LiveRecord::PublicationsChannel < LiveRecord::BaseChannel
22
22
  stream_from "live_record:publications:#{params[:model_name].underscore}", coder: ActiveSupport::JSON do |message|
23
23
  newly_created_record = model_class.find(message['attributes']['id'])
24
24
 
25
- active_record_relation = SearchAdapters.mapped_active_record_relation(
25
+ active_record_relation = LiveRecord::BaseChannel::SearchAdapters.mapped_active_record_relation(
26
26
  model_class: model_class,
27
27
  conditions_hash: params[:where].to_h,
28
28
  current_user: current_user
29
29
  )
30
30
 
31
31
  if active_record_relation.exists?(id: newly_created_record.id)
32
- whitelisted_attributes = Helpers.whitelisted_attributes(newly_created_record, current_user)
32
+ whitelisted_attributes = LiveRecord::BaseChannel::Helpers.whitelisted_attributes(newly_created_record, current_user)
33
33
 
34
34
  if whitelisted_attributes.size > 0
35
35
  message = { 'action' => 'create', 'attributes' => message['attributes'] }
@@ -46,7 +46,7 @@ class LiveRecord::PublicationsChannel < LiveRecord::BaseChannel
46
46
 
47
47
  if model_class && model_class < ApplicationRecord
48
48
 
49
- active_record_relation = SearchAdapters.mapped_active_record_relation(
49
+ active_record_relation = LiveRecord::BaseChannel::SearchAdapters.mapped_active_record_relation(
50
50
  model_class: model_class,
51
51
  conditions_hash: params[:where].to_h,
52
52
  current_user: current_user,
@@ -61,7 +61,7 @@ class LiveRecord::PublicationsChannel < LiveRecord::BaseChannel
61
61
  # we `transmmit` a message back to client for each matching record
62
62
  active_record_relation.find_each do |record|
63
63
  # but first, check for the authorised attributes, if exists
64
- whitelisted_attributes = Helpers.whitelisted_attributes(record, current_user)
64
+ whitelisted_attributes = LiveRecord::BaseChannel::Helpers.whitelisted_attributes(record, current_user)
65
65
 
66
66
  if whitelisted_attributes.size > 0
67
67
  message = { 'action' => 'create', 'attributes' => record.attributes }
data/config/puma.rb ADDED
@@ -0,0 +1,2 @@
1
+ workers 3
2
+ preload_app!
@@ -4,10 +4,12 @@ module LiveRecord
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
7
+ after_update_commit :__live_record_dereference_changed_attributes__
7
8
  before_update :__live_record_reference_changed_attributes__
8
9
  after_update_commit :__live_record_broadcast_record_update__
9
10
  after_destroy_commit :__live_record_broadcast_record_destroy__
10
11
  after_create_commit :__live_record_broadcast_record_create__
12
+ after_commit :__live_record_broadcast_record_autoload__, on: [:update, :create]
11
13
 
12
14
  def self.live_record_whitelisted_attributes(record, current_user)
13
15
  []
@@ -19,9 +21,12 @@ module LiveRecord
19
21
  @_live_record_changed_attributes = changed
20
22
  end
21
23
 
24
+ def __live_record_dereference_changed_attributes__
25
+ @_live_record_changed_attributes = nil
26
+ end
27
+
22
28
  def __live_record_broadcast_record_update__
23
29
  included_attributes = attributes.slice(*@_live_record_changed_attributes)
24
- @_live_record_changed_attributes = nil
25
30
  message_data = { 'action' => 'update', 'attributes' => included_attributes }
26
31
  LiveRecord::ChangesChannel.broadcast_to(self, message_data)
27
32
  LiveRecordUpdate.create!(recordable_type: self.class, recordable_id: self.id, created_at: DateTime.now)
@@ -36,7 +41,29 @@ module LiveRecord
36
41
  message_data = { 'action' => 'create', 'attributes' => attributes }
37
42
  ActionCable.server.broadcast "live_record:publications:#{self.class.name.underscore}", message_data
38
43
  end
44
+
45
+ def __live_record_broadcast_record_autoload__
46
+ included_attributes = nil
47
+
48
+ changed_attributes = attributes.slice(*@_live_record_changed_attributes)
49
+
50
+ # if after_update
51
+ if changed_attributes.present?
52
+ included_attributes = changed_attributes
53
+ # else if after_create_commit
54
+ else
55
+ included_attributes = attributes
56
+ end
57
+
58
+ message_data = {
59
+ 'action' => 'create_or_update',
60
+ 'model_name' => self.class.to_s,
61
+ 'record_id' => id,
62
+ 'attributes' => included_attributes
63
+ }
64
+ ActionCable.server.broadcast 'live_record:autoloads', message_data
65
+ end
39
66
  end
40
67
  end
41
68
  end
42
- end
69
+ end
@@ -1,3 +1,3 @@
1
1
  module LiveRecord
2
- VERSION = '0.2.8'.freeze
2
+ VERSION = '0.3.0'.freeze
3
3
  end
data/live_record.gemspec CHANGED
@@ -17,19 +17,23 @@ Gem::Specification.new do |s|
17
17
  s.add_dependency 'rails', '>= 5.0.0', '< 5.2'
18
18
 
19
19
  s.add_development_dependency 'rails', '~> 5.1'
20
+ s.add_development_dependency 'puma', '~> 3.10'
20
21
  s.add_development_dependency 'bundler', '~> 1.3'
21
- s.add_development_dependency 'rspec-rails', '~> 3.6'
22
- s.add_development_dependency 'combustion', '~> 0.7'
23
22
  s.add_development_dependency 'byebug', '~> 9.0'
24
- s.add_development_dependency 'sqlite3', '~> 1.3'
25
- s.add_development_dependency 'redis', '~> 3.3'
26
- s.add_development_dependency 'puma', '~> 3.10'
27
- s.add_development_dependency 'jquery-rails', '~> 4.3'
23
+
28
24
  s.add_development_dependency 'sprockets-rails', '~> 3.2'
29
25
  s.add_development_dependency 'coffee-rails', '~> 4.2'
26
+ s.add_development_dependency 'jquery-rails', '~> 4.3'
30
27
  s.add_development_dependency 'jbuilder', '~> 2.7'
28
+
29
+ s.add_development_dependency 'sqlite3', '~> 1.3'
30
+ s.add_development_dependency 'redis', '~> 3.3'
31
+
32
+ s.add_development_dependency 'rspec-rails', '~> 3.6'
33
+ s.add_development_dependency 'combustion', '~> 0.7'
31
34
  s.add_development_dependency 'chromedriver-helper', '~> 1.1'
32
35
  s.add_development_dependency 'selenium-webdriver', '~> 3.5'
33
36
  s.add_development_dependency 'faker', '~> 1.8'
34
37
  s.add_development_dependency 'database_cleaner', '~> 1.6'
38
+ s.add_development_dependency 'timecop', '0.9.1'
35
39
  end
@@ -9,9 +9,15 @@ RSpec.feature 'LiveRecord Syncing', type: :feature do
9
9
  let!(:users) { [user1, user2] }
10
10
  let!(:posts) { [post1, post2, post3] }
11
11
 
12
+ # LiveRecord.helpers.loadRecords({ modelName: 'Post' });
13
+ # LiveRecord.helpers.loadRecords({ modelName: 'User', url: '<%= j users_path %>' });
14
+ # LiveRecord.Model.all.Post.subscribe({ where: { is_enabled_eq: true, content_eq: 'somecontent' }});
15
+
12
16
  scenario 'User sees live changes (updates) of post records', js: true do
13
17
  visit '/posts'
14
18
 
19
+ execute_script("LiveRecord.helpers.loadRecords({ modelName: 'Post' });")
20
+
15
21
  post1_title_td = find('td', text: post1.title, wait: 10)
16
22
  post2_title_td = find('td', text: post2.title, wait: 10)
17
23
  post3_title_td = find('td', text: post3.title, wait: 10)
@@ -27,6 +33,8 @@ RSpec.feature 'LiveRecord Syncing', type: :feature do
27
33
  scenario 'User sees live changes (destroy) of post records', js: true do
28
34
  visit '/posts'
29
35
 
36
+ execute_script("LiveRecord.helpers.loadRecords({ modelName: 'Post' });")
37
+
30
38
  expect{find('td', text: post1.title, wait: 10)}.to_not raise_error
31
39
  expect{find('td', text: post2.title, wait: 10)}.to_not raise_error
32
40
  expect{find('td', text: post3.title, wait: 10)}.to_not raise_error
@@ -43,6 +51,8 @@ RSpec.feature 'LiveRecord Syncing', type: :feature do
43
51
  scenario 'User sees live changes (updates) of post records, but only changes from whitelisted authorised attributes', js: true do
44
52
  visit '/posts'
45
53
 
54
+ execute_script("LiveRecord.helpers.loadRecords({ modelName: 'Post' });")
55
+
46
56
  post1_title_td = find('td', text: post1.title, wait: 10)
47
57
  post1_content_td = find('td', text: post1.content, wait: 10)
48
58
  post2_title_td = find('td', text: post2.title, wait: 10)
@@ -65,6 +75,13 @@ RSpec.feature 'LiveRecord Syncing', type: :feature do
65
75
  scenario 'JS-Client can access Model associations record objects in its current store', js: true do
66
76
  visit '/posts'
67
77
 
78
+ execute_script(
79
+ <<-eos
80
+ LiveRecord.helpers.loadRecords({ modelName: 'Post' });
81
+ LiveRecord.helpers.loadRecords({ modelName: 'User', url: '#{users_path}' });
82
+ eos
83
+ )
84
+
68
85
  # let's wait for all records first before checking correct associations
69
86
  wait before: -> { evaluate_script('Object.keys( LiveRecord.Model.all.User.all ).length') }, becomes: -> (value) { value == users.size }, duration: 10.seconds
70
87
  expect(evaluate_script('Object.keys( LiveRecord.Model.all.User.all ).length')).to eq users.size
@@ -94,35 +111,35 @@ RSpec.feature 'LiveRecord Syncing', type: :feature do
94
111
  expect(evaluate_script("LiveRecord.Model.all.Post.all[#{post3.id}].user()")).to eq nil
95
112
  end
96
113
 
97
- # see spec/internal/app/views/posts/index.html.erb to see the subscribe "conditions"
98
114
  scenario 'JS-Client receives live new (create) post records where specified "conditions" matched', js: true do
99
115
  visit '/posts'
100
116
 
101
- # wait first for all posts to be loaded
102
- wait before: -> { evaluate_script("Object.keys( LiveRecord.Model.all.Post.all ).length") }, becomes: -> (value) { value == Post.all.count }, duration: 10.seconds
103
- expect(evaluate_script("Object.keys( LiveRecord.Model.all.Post.all ).length")).to be Post.all.count
117
+ execute_script("LiveRecord.Model.all.Post.subscribe({ where: { is_enabled_eq: true }});")
104
118
 
105
- post4 = create(:post, id: 98, is_enabled: true)
106
- post5 = create(:post, id: 99, is_enabled: false)
119
+ sleep(1)
120
+
121
+ post4 = create(:post, is_enabled: true)
122
+ post5 = create(:post, is_enabled: false)
123
+ post6 = create(:post, is_enabled: true)
107
124
 
108
125
  wait before: -> { evaluate_script("LiveRecord.Model.all.Post.all[#{post4.id}] == undefined") }, becomes: -> (value) { value == false }
109
126
  expect(evaluate_script("LiveRecord.Model.all.Post.all[#{post4.id}] == undefined")).to be false
110
127
 
111
128
  wait before: -> { evaluate_script("LiveRecord.Model.all.Post.all[#{post5.id}] == undefined") }, becomes: -> (value) { value == true }
112
129
  expect(evaluate_script("LiveRecord.Model.all.Post.all[#{post5.id}] == undefined")).to be true
130
+
131
+ wait before: -> { evaluate_script("LiveRecord.Model.all.Post.all[#{post6.id}] == undefined") }, becomes: -> (value) { value == false }
132
+ expect(evaluate_script("LiveRecord.Model.all.Post.all[#{post6.id}] == undefined")).to be false
113
133
  end
114
134
 
115
- # see spec/internal/app/views/posts/index.html.erb to see the subscribe "conditions"
116
135
  scenario 'JS-Client receives live new (create) post records where only considered "conditions" are the whitelisted authorised attributes', js: true do
117
136
  visit '/posts'
118
137
 
119
- # wait first for all posts to be loaded
120
- wait before: -> { evaluate_script("Object.keys( LiveRecord.Model.all.Post.all ).length") }, becomes: -> (value) { value == Post.all.count }, duration: 10.seconds
121
- expect(evaluate_script("Object.keys( LiveRecord.Model.all.Post.all ).length")).to be Post.all.count
138
+ execute_script("LiveRecord.Model.all.Post.subscribe({ where: { is_enabled_eq: true, content_eq: 'somecontent' }});")
122
139
 
123
- # in index.html.erb:
124
- # LiveRecord.Model.all.Post.subscribe({ where: { is_enabled: true, content: 'somecontent' }});
125
- # because `content` is not whitelisted, therefore the `content` condition above is disregarded
140
+ sleep(1)
141
+
142
+ # because `content` is not whitelisted in models/post.rb, therefore the `content` condition is disregarded from above
126
143
  post4 = create(:post, is_enabled: true, content: 'somecontent')
127
144
  post5 = create(:post, is_enabled: true, content: 'contentisnotwhitelistedthereforewontbeconsidered')
128
145
 
@@ -137,20 +154,105 @@ RSpec.feature 'LiveRecord Syncing', type: :feature do
137
154
  scenario 'JS-Client receives live new (create) post records having only the whitelisted authorised attributes', js: true do
138
155
  visit '/posts'
139
156
 
140
- # wait first for all posts to be loaded
141
- wait before: -> { evaluate_script("Object.keys( LiveRecord.Model.all.Post.all ).length") }, becomes: -> (value) { value == Post.all.count }, duration: 10.seconds
142
- expect(evaluate_script("Object.keys( LiveRecord.Model.all.Post.all ).length")).to be Post.all.count
157
+ execute_script("LiveRecord.Model.all.Post.subscribe();")
158
+
159
+ sleep(1)
143
160
 
144
161
  post4 = create(:post, is_enabled: true, title: 'sometitle', content: 'somecontent')
145
162
 
146
163
  wait before: -> { evaluate_script("LiveRecord.Model.all.Post.all[#{post4.id}] == undefined") }, becomes: -> (value) { value == false }
164
+ expect(evaluate_script("LiveRecord.Model.all.Post.all[#{post4.id}] == undefined")).to be false
147
165
  expect(evaluate_script("LiveRecord.Model.all.Post.all[#{post4.id}].title()")).to eq post4.title
148
- expect(evaluate_script("LiveRecord.Model.all.Post.all[#{post4.id}].content()")).to eq nil
166
+ expect(evaluate_script("LiveRecord.Model.all.Post.all[#{post4.id}].attributes.content")).to eq nil
167
+ end
168
+
169
+ scenario 'JS-Client receives live autoloaded (create or update) post records where specified "conditions" matched', js: true do
170
+ visit '/posts'
171
+
172
+ # prepopulate
173
+ disabled_post_that_will_be_enabled = create(:post, is_enabled: false)
174
+
175
+ execute_script("LiveRecord.Model.all.Post.autoload({ where: { is_enabled_eq: true }});")
176
+
177
+ sleep(1)
178
+
179
+ disabled_post = create(:post, is_enabled: false)
180
+ enabled_post = create(:post, is_enabled: true)
181
+
182
+ sleep(5)
183
+
184
+ disabled_post_that_will_be_enabled.update!(is_enabled: true)
185
+
186
+ wait before: -> { evaluate_script("LiveRecord.Model.all.Post.all[#{disabled_post.id}] == undefined") }, becomes: -> (value) { value == true }
187
+ expect(evaluate_script("LiveRecord.Model.all.Post.all[#{disabled_post.id}] == undefined")).to be true
188
+
189
+ wait before: -> { evaluate_script("LiveRecord.Model.all.Post.all[#{enabled_post.id}] == undefined") }, becomes: -> (value) { value == false }
190
+ expect(evaluate_script("LiveRecord.Model.all.Post.all[#{enabled_post.id}] == undefined")).to be false
191
+
192
+ wait before: -> { evaluate_script("LiveRecord.Model.all.Post.all[#{disabled_post_that_will_be_enabled.id}] == undefined") }, becomes: -> (value) { value == false }
193
+ expect(evaluate_script("LiveRecord.Model.all.Post.all[#{disabled_post_that_will_be_enabled.id}] == undefined")).to be false
194
+ end
195
+
196
+ scenario 'JS-Client receives live autoloaded (create or update) post records where only considered "conditions" are the whitelisted authorised attributes', js: true do
197
+ visit '/posts'
198
+
199
+ # prepopulate
200
+ updated_post1 = create(:post, is_enabled: false, content: 'somecontent')
201
+ updated_post2 = create(:post, is_enabled: false, content: 'contentisnotwhitelistedthereforewontbeconsidered')
202
+
203
+ execute_script("LiveRecord.Model.all.Post.autoload({ where: { is_enabled_eq: true, content_eq: 'somecontent' }});")
204
+
205
+ sleep(1)
206
+
207
+ # because `content` is not whitelisted in models/post.rb, therefore the `content` condition is disregarded from above
208
+ created_post1 = create(:post, is_enabled: true, content: 'somecontent')
209
+ created_post2 = create(:post, is_enabled: true, content: 'contentisnotwhitelistedthereforewontbeconsidered')
210
+ updated_post1.update!(is_enabled: true)
211
+ updated_post2.update!(is_enabled: true)
212
+
213
+ wait before: -> { evaluate_script("LiveRecord.Model.all.Post.all[#{created_post1.id}] == undefined") }, becomes: -> (value) { value == false }
214
+ expect(evaluate_script("LiveRecord.Model.all.Post.all[#{created_post1.id}] == undefined")).to be false
215
+
216
+ wait before: -> { evaluate_script("LiveRecord.Model.all.Post.all[#{created_post2.id}] == undefined") }, becomes: -> (value) { value == false }
217
+ expect(evaluate_script("LiveRecord.Model.all.Post.all[#{created_post2.id}] == undefined")).to be false
218
+
219
+ wait before: -> { evaluate_script("LiveRecord.Model.all.Post.all[#{updated_post1.id}] == undefined") }, becomes: -> (value) { value == false }
220
+ expect(evaluate_script("LiveRecord.Model.all.Post.all[#{updated_post1.id}] == undefined")).to be false
221
+
222
+ wait before: -> { evaluate_script("LiveRecord.Model.all.Post.all[#{updated_post2.id}] == undefined") }, becomes: -> (value) { value == false }
223
+ expect(evaluate_script("LiveRecord.Model.all.Post.all[#{updated_post2.id}] == undefined")).to be false
224
+ end
225
+
226
+ # see spec/internal/app/models/post.rb to view specified whitelisted attributes
227
+ scenario 'JS-Client receives live autoloaded (create or update) post records having only the whitelisted authorised attributes', js: true do
228
+ visit '/posts'
229
+
230
+ # prepopulate
231
+ updated_post = create(:post, is_enabled: true, title: 'sometitle', content: 'somecontent')
232
+
233
+ execute_script("LiveRecord.Model.all.Post.autoload();")
234
+
235
+ sleep(1)
236
+
237
+ created_post = create(:post, is_enabled: true, title: 'sometitle', content: 'somecontent')
238
+ updated_post = create(:post, is_enabled: false, title: 'sometitle', content: 'somecontent')
239
+
240
+ wait before: -> { evaluate_script("LiveRecord.Model.all.Post.all[#{created_post.id}] == undefined") }, becomes: -> (value) { value == false }
241
+ expect(evaluate_script("LiveRecord.Model.all.Post.all[#{created_post.id}] == undefined")).to be false
242
+ expect(evaluate_script("LiveRecord.Model.all.Post.all[#{created_post.id}].title()")).to eq created_post.title
243
+ expect(evaluate_script("LiveRecord.Model.all.Post.all[#{created_post.id}].attributes.content")).to eq nil
244
+
245
+ wait before: -> { evaluate_script("LiveRecord.Model.all.Post.all[#{updated_post.id}] == undefined") }, becomes: -> (value) { value == false }
246
+ expect(evaluate_script("LiveRecord.Model.all.Post.all[#{updated_post.id}] == undefined")).to be false
247
+ expect(evaluate_script("LiveRecord.Model.all.Post.all[#{updated_post.id}].title()")).to eq updated_post.title
248
+ expect(evaluate_script("LiveRecord.Model.all.Post.all[#{updated_post.id}].attributes.content")).to eq nil
149
249
  end
150
250
 
151
251
  scenario 'JS-Client should not have shared callbacks for those callbacks defined only for a particular post record', js: true do
152
252
  visit '/posts'
153
253
 
254
+ execute_script("LiveRecord.helpers.loadRecords({ modelName: 'Post' });")
255
+
154
256
  # wait first for all posts to be loaded
155
257
  wait before: -> { evaluate_script("Object.keys( LiveRecord.Model.all.Post.all ).length") }, becomes: -> (value) { value == Post.all.count }, duration: 10.seconds
156
258
  expect(evaluate_script("Object.keys( LiveRecord.Model.all.Post.all ).length")).to be Post.all.count
@@ -169,9 +271,7 @@ RSpec.feature 'LiveRecord Syncing', type: :feature do
169
271
  scenario 'JS-Client should receive response :forbidden error when using `subscribe()` but that the current_user is forbidden to do so', js: true do
170
272
  visit '/posts'
171
273
 
172
- # wait first for all posts to be loaded
173
- wait before: -> { evaluate_script("Object.keys( LiveRecord.Model.all.Post.all ).length") }, becomes: -> (value) { value == Post.all.count }, duration: 10.seconds
174
- expect(evaluate_script("Object.keys( LiveRecord.Model.all.Post.all ).length")).to be Post.all.count
274
+ execute_script("LiveRecord.Model.all.Post.subscribe();")
175
275
 
176
276
  expect_any_instance_of(LiveRecord::BaseChannel).to(
177
277
  receive(:respond_with_error).with(:forbidden, 'You do not have privileges to query').once
@@ -194,12 +294,17 @@ RSpec.feature 'LiveRecord Syncing', type: :feature do
194
294
 
195
295
  visit '/posts'
196
296
 
297
+ execute_script(
298
+ <<-eos
299
+ LiveRecord.helpers.loadRecords({ modelName: 'Post' });
300
+ LiveRecord.Model.all.Post.subscribe();
301
+ eos
302
+ )
303
+
197
304
  # wait first for all posts to be loaded
198
305
  wait before: -> { evaluate_script("Object.keys( LiveRecord.Model.all.Post.all ).length") }, becomes: -> (value) { value == Post.all.count }, duration: 10.seconds
199
306
  expect(evaluate_script("Object.keys( LiveRecord.Model.all.Post.all ).length")).to be Post.all.count
200
307
 
201
- disconnection_time = 10 # seconds
202
-
203
308
  Thread.new do
204
309
  sleep(2)
205
310
 
@@ -213,7 +318,7 @@ RSpec.feature 'LiveRecord Syncing', type: :feature do
213
318
  post_that_will_be_updated_while_disconnected_2.update!(title: 'newtitle')
214
319
  end
215
320
 
216
- # LiveRecord.stop_all_streams
321
+ disconnection_time = 10
217
322
  sleep(disconnection_time)
218
323
 
219
324
  wait before: -> { evaluate_script("LiveRecord.Model.all.Post.all[#{post_that_will_be_updated_while_disconnected_1.id}] == undefined") }, becomes: -> (value) { value == false }, duration: 10.seconds
@@ -223,18 +328,22 @@ RSpec.feature 'LiveRecord Syncing', type: :feature do
223
328
  expect(evaluate_script("LiveRecord.Model.all.Post.all[#{post_that_will_be_updated_while_disconnected_2.id}].title()")).to eq 'newtitle'
224
329
  end
225
330
 
226
- scenario 'JS-Client receives all post records created during the time it got disconnected', js: true do
331
+ scenario 'JS-Client receives all post records created (matching the conditions) during the time it got disconnected', js: true do
227
332
  visit '/posts'
228
333
 
229
- # wait first for all posts to be loaded
230
- wait before: -> { evaluate_script("Object.keys( LiveRecord.Model.all.Post.all ).length") }, becomes: -> (value) { value == Post.all.count }, duration: 10.seconds
231
- expect(evaluate_script("Object.keys( LiveRecord.Model.all.Post.all ).length")).to be Post.all.count
232
-
233
- disconnection_time = 10 # seconds
234
-
235
334
  post_created_before_disconnection = nil
236
335
  post_created_while_disconnected_1 = nil
237
336
  post_created_while_disconnected_2 = nil
337
+ post_created_while_disconnected_but_does_not_match = nil
338
+
339
+ # this needs to be before execute_script, otherwise it gets loaded by the subscription
340
+ Timecop.freeze((DateTime.now - LiveRecord.configuration.sync_record_buffer_time) - 30.seconds) do
341
+ post_created_before_disconnection = create(:post, is_enabled: true)
342
+ end
343
+
344
+ execute_script("LiveRecord.Model.all.Post.subscribe({where: {is_enabled_eq: true}});")
345
+
346
+ sleep(1)
238
347
 
239
348
  Thread.new do
240
349
  sleep(2)
@@ -245,13 +354,85 @@ RSpec.feature 'LiveRecord Syncing', type: :feature do
245
354
  end
246
355
 
247
356
  # then we create records while disconnected
248
- post_created_while_disconnected_1 = create(:post, is_enabled: true, created_at: (DateTime.now - LiveRecord.configuration.sync_record_buffer_time) + 30.seconds)
249
- post_created_while_disconnected_2 = create(:post, is_enabled: true, created_at: (DateTime.now - LiveRecord.configuration.sync_record_buffer_time) + 99.hours)
250
- # we need to create this here, otherwise it will be loaded on the Page immediately by the .loadRecords() in JS
251
- post_created_before_disconnection = create(:post, is_enabled: true, created_at: (DateTime.now - LiveRecord.configuration.sync_record_buffer_time) - 30.seconds)
357
+ Timecop.freeze((DateTime.now - LiveRecord.configuration.sync_record_buffer_time) + 30.seconds) do
358
+ post_created_while_disconnected_1 = create(:post, is_enabled: true)
359
+ end
360
+
361
+ Timecop.freeze((DateTime.now - LiveRecord.configuration.sync_record_buffer_time) + 99.hours) do
362
+ post_created_while_disconnected_2 = create(:post, is_enabled: true)
363
+ end
364
+
365
+ Timecop.freeze((DateTime.now - LiveRecord.configuration.sync_record_buffer_time) + 99.hours) do
366
+ post_created_while_disconnected_but_does_not_match = create(:post, is_enabled: false)
367
+ end
368
+ end
369
+
370
+ disconnection_time = 10
371
+ sleep(disconnection_time)
372
+
373
+ wait before: -> { evaluate_script("LiveRecord.Model.all.Post.all[#{post_created_while_disconnected_1.id}] == undefined") }, becomes: -> (value) { value == false }, duration: 10.seconds
374
+ expect(evaluate_script("LiveRecord.Model.all.Post.all[#{post_created_while_disconnected_1.id}] == undefined")).to be false
375
+
376
+ wait before: -> { evaluate_script("LiveRecord.Model.all.Post.all[#{post_created_while_disconnected_2.id}] == undefined") }, becomes: -> (value) { value == false }
377
+ expect(evaluate_script("LiveRecord.Model.all.Post.all[#{post_created_while_disconnected_2.id}] == undefined")).to be false
378
+
379
+ wait before: -> { evaluate_script("LiveRecord.Model.all.Post.all[#{post_created_while_disconnected_but_does_not_match.id}] == undefined") }, becomes: -> (value) { value == true }
380
+ expect(evaluate_script("LiveRecord.Model.all.Post.all[#{post_created_while_disconnected_but_does_not_match.id}] == undefined")).to be true
381
+
382
+ wait before: -> { evaluate_script("LiveRecord.Model.all.Post.all[#{post_created_before_disconnection.id}] == undefined") }, becomes: -> (value) { value == false }
383
+ expect(evaluate_script("LiveRecord.Model.all.Post.all[#{post_created_before_disconnection.id}] == undefined")).to be true
384
+ end
385
+
386
+ scenario 'JS-Client receives all post records created/updated (matching the conditions), during the time it got disconnected', js: true do
387
+ post_created_before_disconnection = nil
388
+ post_created_while_disconnected_1 = nil
389
+ post_created_while_disconnected_2 = nil
390
+ post_created_while_disconnected_but_does_not_match = nil
391
+
392
+ # prepopulate before loading of page/script
393
+ post_updated_before_disconnection = create(:post, is_enabled: false)
394
+ post_updated_while_disconnected_1 = create(:post, is_enabled: false)
395
+ post_updated_while_disconnected_2 = create(:post, is_enabled: false)
396
+ post_updated_while_disconnected_but_does_not_match = create(:post, is_enabled: true)
397
+
398
+ # this needs to be before execute_script, otherwise it gets loaded by the subscription
399
+ Timecop.freeze((DateTime.now - LiveRecord.configuration.sync_record_buffer_time) - 30.seconds) do
400
+ post_created_before_disconnection = create(:post, is_enabled: true)
401
+ post_updated_before_disconnection.update!(is_enabled: true)
402
+ end
403
+
404
+ visit '/posts'
405
+
406
+ execute_script("LiveRecord.Model.all.Post.autoload({where: {is_enabled_eq: true}});")
407
+
408
+ sleep(1)
409
+
410
+ Thread.new do
411
+ sleep(2)
412
+
413
+ # temporarily stop all current autoloads_channel connections
414
+ ObjectSpace.each_object(LiveRecord::AutoloadsChannel) do |autoloads_channel|
415
+ autoloads_channel.connection.close
416
+ end
417
+
418
+ # then we create/update records while disconnected
419
+ Timecop.freeze((DateTime.now - LiveRecord.configuration.sync_record_buffer_time) + 30.seconds) do
420
+ post_created_while_disconnected_1 = create(:post, is_enabled: true)
421
+ post_updated_while_disconnected_1.update!(is_enabled: true)
422
+ end
423
+
424
+ Timecop.freeze((DateTime.now - LiveRecord.configuration.sync_record_buffer_time) + 99.hours) do
425
+ post_created_while_disconnected_2 = create(:post, is_enabled: true)
426
+ post_updated_while_disconnected_2.update!(is_enabled: true)
427
+ end
428
+
429
+ Timecop.freeze((DateTime.now - LiveRecord.configuration.sync_record_buffer_time) + 99.hours) do
430
+ post_created_while_disconnected_but_does_not_match = create(:post, is_enabled: false, created_at: (DateTime.now - LiveRecord.configuration.sync_record_buffer_time) + 99.hours)
431
+ post_updated_while_disconnected_but_does_not_match.update!(is_enabled: false)
432
+ end
252
433
  end
253
434
 
254
- # LiveRecord.stop_all_streams
435
+ disconnection_time = 10
255
436
  sleep(disconnection_time)
256
437
 
257
438
  wait before: -> { evaluate_script("LiveRecord.Model.all.Post.all[#{post_created_while_disconnected_1.id}] == undefined") }, becomes: -> (value) { value == false }, duration: 10.seconds
@@ -260,6 +441,9 @@ RSpec.feature 'LiveRecord Syncing', type: :feature do
260
441
  wait before: -> { evaluate_script("LiveRecord.Model.all.Post.all[#{post_created_while_disconnected_2.id}] == undefined") }, becomes: -> (value) { value == false }
261
442
  expect(evaluate_script("LiveRecord.Model.all.Post.all[#{post_created_while_disconnected_2.id}] == undefined")).to be false
262
443
 
444
+ wait before: -> { evaluate_script("LiveRecord.Model.all.Post.all[#{post_created_while_disconnected_but_does_not_match.id}] == undefined") }, becomes: -> (value) { value == true }
445
+ expect(evaluate_script("LiveRecord.Model.all.Post.all[#{post_created_while_disconnected_but_does_not_match.id}] == undefined")).to be true
446
+
263
447
  wait before: -> { evaluate_script("LiveRecord.Model.all.Post.all[#{post_created_before_disconnection.id}] == undefined") }, becomes: -> (value) { value == false }
264
448
  expect(evaluate_script("LiveRecord.Model.all.Post.all[#{post_created_before_disconnection.id}] == undefined")).to be true
265
449
  end