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.
- checksums.yaml +4 -4
- data/README.md +154 -59
- data/app/assets/javascripts/live_record/model/create.coffee +78 -0
- data/app/channels/live_record/autoloads_channel.rb +92 -0
- data/app/channels/live_record/base_channel.rb +0 -25
- data/app/channels/live_record/base_channel/helpers.rb +27 -0
- data/app/channels/live_record/{publications_channel → base_channel}/search_adapters.rb +1 -1
- data/app/channels/live_record/changes_channel.rb +3 -3
- data/app/channels/live_record/publications_channel.rb +5 -5
- data/config/puma.rb +2 -0
- data/lib/live_record/model/callbacks.rb +29 -2
- data/lib/live_record/version.rb +1 -1
- data/live_record.gemspec +10 -6
- data/spec/features/live_record_syncing_spec.rb +219 -35
- data/spec/internal/app/views/posts/index.html.erb +0 -5
- data/spec/internal/db/schema.rb +4 -0
- data/spec/rails_helper.rb +1 -1
- metadata +53 -36
@@ -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
|
@@ -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
@@ -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
|
data/lib/live_record/version.rb
CHANGED
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
|
-
|
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
|
-
|
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
|
-
|
106
|
-
|
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
|
-
|
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
|
-
|
124
|
-
|
125
|
-
# because `content` is not whitelisted, therefore the `content` condition
|
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
|
-
|
141
|
-
|
142
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
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
|
-
|
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
|