live_record 0.2.8 → 0.3.0

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