live_record 0.1.2 → 0.2.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.
- checksums.yaml +4 -4
- data/.gitignore +2 -2
- data/{lib/live_record/.rspec → .rspec} +0 -0
- data/.travis.yml +0 -1
- data/Gemfile +3 -1
- data/README.md +363 -202
- data/Rakefile +5 -0
- data/app/assets/javascripts/live_record.coffee +4 -0
- data/app/assets/javascripts/live_record/helpers.coffee +4 -0
- data/app/assets/javascripts/live_record/helpers/load_records.coffee +13 -7
- data/app/assets/javascripts/live_record/helpers/spaceship.coffee +13 -0
- data/app/assets/javascripts/live_record/model.coffee +4 -0
- data/app/assets/javascripts/live_record/model/create.coffee +96 -27
- data/app/assets/javascripts/live_record/plugins.coffee +3 -0
- data/app/assets/javascripts/live_record/plugins/live_dom.coffee +5 -19
- data/app/assets/javascripts/live_record/plugins/live_dom/apply_to_model.coffee +17 -0
- data/app/channels/live_record/base_channel.rb +41 -0
- data/app/channels/live_record/changes_channel.rb +59 -0
- data/app/channels/live_record/publications_channel.rb +134 -0
- data/{lib/live_record/config.ru → config.ru} +0 -0
- data/lib/live_record.rb +2 -0
- data/lib/live_record/action_view_extensions/view_helper.rb +22 -0
- data/lib/live_record/configure.rb +19 -0
- data/lib/live_record/generators/install_generator.rb +9 -4
- data/lib/live_record/generators/templates/create_live_record_updates.rb +1 -1
- data/lib/live_record/generators/templates/index.html.erb +1 -0
- data/lib/live_record/generators/templates/model.rb.rb +4 -4
- data/lib/live_record/model/callbacks.rb +8 -2
- data/lib/live_record/version.rb +1 -1
- data/live_record.gemspec +2 -2
- data/{lib/live_record/spec → spec}/factories/posts.rb +0 -0
- data/spec/features/live_record_syncing_spec.rb +184 -0
- data/spec/helpers/wait.rb +19 -0
- data/{lib/live_record/spec → spec}/internal/app/assets/config/manifest.js +0 -0
- data/{lib/live_record/spec → spec}/internal/app/assets/javascripts/application.js +0 -0
- data/{lib/live_record/spec → spec}/internal/app/assets/javascripts/cable.js +0 -0
- data/spec/internal/app/assets/javascripts/posts.coffee +8 -0
- data/{lib/live_record/spec → spec}/internal/app/channels/application_cable/channel.rb +0 -0
- data/{lib/live_record/spec → spec}/internal/app/channels/application_cable/connection.rb +4 -4
- data/{lib/live_record/spec → spec}/internal/app/controllers/application_controller.rb +0 -0
- data/{lib/live_record/spec → spec}/internal/app/controllers/posts_controller.rb +1 -0
- data/{lib/live_record/spec → spec}/internal/app/models/application_record.rb +0 -0
- data/{lib/live_record/spec → spec}/internal/app/models/live_record_update.rb +0 -0
- data/spec/internal/app/models/post.rb +11 -0
- data/{lib/live_record/spec → spec}/internal/app/views/layouts/application.html.erb +0 -0
- data/{lib/live_record/spec → spec}/internal/app/views/posts/_form.html.erb +0 -0
- data/{lib/live_record/spec → spec}/internal/app/views/posts/_post.json.jbuilder +0 -0
- data/{lib/live_record/spec → spec}/internal/app/views/posts/edit.html.erb +0 -0
- data/{lib/live_record/spec → spec}/internal/app/views/posts/index.html.erb +6 -5
- data/{lib/live_record/spec → spec}/internal/app/views/posts/index.json.jbuilder +0 -0
- data/{lib/live_record/spec → spec}/internal/app/views/posts/new.html.erb +0 -0
- data/{lib/live_record/spec → spec}/internal/app/views/posts/show.html.erb +0 -0
- data/{lib/live_record/spec → spec}/internal/app/views/posts/show.json.jbuilder +0 -0
- data/{lib/live_record/spec → spec}/internal/config/cable.yml +0 -0
- data/{lib/live_record/spec → spec}/internal/config/database.yml +0 -0
- data/{lib/live_record/spec → spec}/internal/config/routes.rb +0 -0
- data/{lib/live_record/spec → spec}/internal/db/schema.rb +2 -0
- data/{lib/live_record/spec → spec}/internal/public/favicon.ico +0 -0
- data/{lib/live_record/spec → spec}/rails_helper.rb +4 -2
- data/{lib/live_record/spec → spec}/spec_helper.rb +0 -0
- metadata +64 -56
- data/app/assets/javascripts/live_record.js +0 -4
- data/app/assets/javascripts/live_record/helpers.js +0 -4
- data/app/assets/javascripts/live_record/model.js +0 -4
- data/app/assets/javascripts/live_record/plugins.js +0 -3
- data/lib/live_record/channel/implement.rb +0 -100
- data/lib/live_record/spec/features/live_record_syncing_spec.rb +0 -60
- data/lib/live_record/spec/internal/app/assets/javascripts/posts.coffee +0 -14
- data/lib/live_record/spec/internal/app/channels/live_record_channel.rb +0 -4
- data/lib/live_record/spec/internal/app/models/post.rb +0 -11
data/Rakefile
ADDED
@@ -8,23 +8,29 @@ LiveRecord.helpers.loadRecords = (args) ->
|
|
8
8
|
args['url']
|
9
9
|
).done(
|
10
10
|
(data) ->
|
11
|
+
record_or_records = undefined
|
12
|
+
|
11
13
|
# Array JSON
|
12
14
|
if $.isArray(data)
|
13
|
-
records_attributes = data
|
15
|
+
records_attributes = data
|
14
16
|
records = []
|
15
17
|
|
16
18
|
for record_attributes in records_attributes
|
17
|
-
record = new LiveRecord.Model.all[args['modelName']](record_attributes)
|
18
|
-
record.create()
|
19
|
-
records
|
19
|
+
record = new LiveRecord.Model.all[args['modelName']](record_attributes)
|
20
|
+
record.create()
|
21
|
+
records.push(record)
|
22
|
+
|
23
|
+
record_or_records = records
|
20
24
|
|
21
25
|
# Single-Record JSON
|
22
26
|
else
|
23
27
|
record_attributes = data
|
24
|
-
record = new LiveRecord.Model.all[args['modelName']](record_attributes)
|
25
|
-
record.create()
|
28
|
+
record = new LiveRecord.Model.all[args['modelName']](record_attributes)
|
29
|
+
record.create()
|
30
|
+
|
31
|
+
record_or_records = record
|
26
32
|
|
27
|
-
args['onLoad'].call(this,
|
33
|
+
args['onLoad'].call(this, record_or_records) if args['onLoad']
|
28
34
|
).fail(
|
29
35
|
(jqxhr, textStatus, error) ->
|
30
36
|
args['onError'].call(this, jqxhr, textStatus, error) if args['onError']
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# ref: https://stackoverflow.com/questions/34852855/combined-comparison-spaceship-operator-in-javascript
|
2
|
+
|
3
|
+
LiveRecord.helpers.spaceship = (val1, val2) ->
|
4
|
+
if val1 == null or val2 == null or typeof val1 != typeof val2
|
5
|
+
return null
|
6
|
+
if typeof val1 == 'string'
|
7
|
+
val1.localeCompare val2
|
8
|
+
else
|
9
|
+
if val1 > val2
|
10
|
+
return 1
|
11
|
+
else if val1 < val2
|
12
|
+
return -1
|
13
|
+
0
|
@@ -6,18 +6,6 @@ LiveRecord.Model.create = (config) ->
|
|
6
6
|
# NEW
|
7
7
|
Model = (attributes) ->
|
8
8
|
this.attributes = attributes
|
9
|
-
# instance callbacks
|
10
|
-
this._callbacks = {
|
11
|
-
'on:connect': [],
|
12
|
-
'on:disconnect': [],
|
13
|
-
'on:response_error': [],
|
14
|
-
'before:create': [],
|
15
|
-
'after:create': [],
|
16
|
-
'before:update': [],
|
17
|
-
'after:update': [],
|
18
|
-
'before:destroy': [],
|
19
|
-
'after:destroy': []
|
20
|
-
}
|
21
9
|
|
22
10
|
Object.keys(this.attributes).forEach (attribute_key) ->
|
23
11
|
if Model.prototype[attribute_key] == undefined
|
@@ -27,18 +15,81 @@ LiveRecord.Model.create = (config) ->
|
|
27
15
|
|
28
16
|
Model.modelName = config.modelName
|
29
17
|
|
30
|
-
Model.
|
31
|
-
|
18
|
+
Model.store = {}
|
19
|
+
|
20
|
+
Model.all = {}
|
21
|
+
|
22
|
+
Model.subscriptions = []
|
23
|
+
|
24
|
+
Model.subscribe = (config) ->
|
25
|
+
config || (config = {})
|
26
|
+
config.callbacks || (config.callbacks = {})
|
27
|
+
|
28
|
+
subscription = App.cable.subscriptions.create(
|
29
|
+
{
|
30
|
+
channel: 'LiveRecord::PublicationsChannel'
|
31
|
+
model_name: Model.modelName
|
32
|
+
where: config.where
|
33
|
+
},
|
34
|
+
|
35
|
+
connected: ->
|
36
|
+
if this.liveRecord._staleSince != undefined
|
37
|
+
@syncRecords()
|
38
|
+
|
39
|
+
config.callbacks['on:connect'].call(this) if config.callbacks['on:connect']
|
40
|
+
|
41
|
+
disconnected: ->
|
42
|
+
this.liveRecord._staleSince = (new Date()).toISOString() unless this.liveRecord._staleSince
|
43
|
+
config.callbacks['on:disconnect'].call(this) if config.callbacks['on:disconnect']
|
44
|
+
|
45
|
+
received: (data) ->
|
46
|
+
@onAction[data.action].call(this, data)
|
47
|
+
|
48
|
+
onAction:
|
49
|
+
create: (data) ->
|
50
|
+
config.callbacks['before:create'].call(this, data) if config.callbacks['before:create']
|
51
|
+
record = new Model(data.attributes)
|
52
|
+
record.create()
|
53
|
+
config.callbacks['after:create'].call(this, data) if config.callbacks['after:create']
|
54
|
+
|
55
|
+
syncRecords: ->
|
56
|
+
@perform(
|
57
|
+
'sync_records',
|
58
|
+
model_name: Model.modelName,
|
59
|
+
where: config.where,
|
60
|
+
stale_since: this.liveRecord._staleSince
|
61
|
+
)
|
62
|
+
this.liveRecord._staleSince = undefined
|
63
|
+
)
|
64
|
+
|
65
|
+
subscription.liveRecord = {}
|
66
|
+
subscription.liveRecord.modelName = config.modelName
|
67
|
+
subscription.liveRecord.where = config.where
|
68
|
+
subscription.liveRecord.callbacks = config.callbacks
|
69
|
+
|
70
|
+
this.subscriptions.push(subscription)
|
71
|
+
subscription
|
72
|
+
|
73
|
+
Model.unsubscribe = (subscription) ->
|
74
|
+
index = this.subscriptions.indexOf(subscription)
|
75
|
+
throw new Error('`subscription` argument does not exist in ' + this.modelName + ' subscriptions list') if index == -1
|
76
|
+
|
77
|
+
App.cable.subscriptions.remove(subscription)
|
78
|
+
|
79
|
+
this.subscriptions.splice(index, 1)
|
80
|
+
subscription
|
32
81
|
|
33
82
|
Model.prototype.subscribe = ->
|
34
83
|
return this.subscription if this.subscription != undefined
|
35
84
|
|
36
85
|
# listen for record changes (update / destroy)
|
37
|
-
subscription = App['live_record_' + this.modelName() + '_' + this.id()] = App.cable.subscriptions.create(
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
86
|
+
subscription = App['live_record_' + this.modelName() + '_' + this.id()] = App.cable.subscriptions.create(
|
87
|
+
{
|
88
|
+
channel: 'LiveRecord::ChangesChannel'
|
89
|
+
model_name: this.modelName()
|
90
|
+
record_id: this.id()
|
91
|
+
},
|
92
|
+
|
42
93
|
record: ->
|
43
94
|
return @_record if @_record
|
44
95
|
identifier = JSON.parse(this.identifier)
|
@@ -94,6 +145,12 @@ LiveRecord.Model.create = (config) ->
|
|
94
145
|
|
95
146
|
this.subscription = subscription
|
96
147
|
|
148
|
+
Model.prototype.model = ->
|
149
|
+
Model
|
150
|
+
|
151
|
+
Model.prototype.modelName = ->
|
152
|
+
Model.modelName
|
153
|
+
|
97
154
|
Model.prototype.unsubscribe = ->
|
98
155
|
return if this.subscription == undefined
|
99
156
|
App.cable.subscriptions.remove(this.subscription)
|
@@ -102,14 +159,14 @@ LiveRecord.Model.create = (config) ->
|
|
102
159
|
Model.prototype.isSubscribed = ->
|
103
160
|
this.subscription != undefined
|
104
161
|
|
105
|
-
# ALL
|
106
|
-
Model.all = {}
|
107
|
-
|
108
162
|
# CREATE
|
109
|
-
Model.prototype.create = () ->
|
163
|
+
Model.prototype.create = (options) ->
|
164
|
+
throw new Error(Model.modelName+'('+this.id()+') is already in the store') if Model.all[this.attributes.id]
|
110
165
|
this._callCallbacks('before:create', undefined)
|
111
166
|
|
112
167
|
Model.all[this.attributes.id] = this
|
168
|
+
# because we do not know if this newly created object is statle, then we just set it to very long time ago, before we subscribe()
|
169
|
+
this._staleSince = (new Date(1900, 0, 1)).toISOString()
|
113
170
|
this.subscribe()
|
114
171
|
|
115
172
|
this._callCallbacks('after:create', undefined)
|
@@ -138,7 +195,6 @@ LiveRecord.Model.create = (config) ->
|
|
138
195
|
|
139
196
|
# CALLBACKS
|
140
197
|
|
141
|
-
## class callbacks
|
142
198
|
Model._callbacks = {
|
143
199
|
'on:connect': [],
|
144
200
|
'on:disconnect': [],
|
@@ -150,14 +206,28 @@ LiveRecord.Model.create = (config) ->
|
|
150
206
|
'before:destroy': [],
|
151
207
|
'after:destroy': []
|
152
208
|
}
|
209
|
+
|
210
|
+
Model.prototype._callbacks = {
|
211
|
+
'on:connect': [],
|
212
|
+
'on:disconnect': [],
|
213
|
+
'on:response_error': [],
|
214
|
+
'before:create': [],
|
215
|
+
'after:create': [],
|
216
|
+
'before:update': [],
|
217
|
+
'after:update': [],
|
218
|
+
'before:destroy': [],
|
219
|
+
'after:destroy': []
|
220
|
+
}
|
153
221
|
|
154
|
-
|
222
|
+
# adding new callbackd to the list
|
223
|
+
Model.prototype.addCallback = Model.addCallback = (callbackKey, callbackFunction) ->
|
155
224
|
index = this._callbacks[callbackKey].indexOf(callbackFunction)
|
156
225
|
if index == -1
|
157
226
|
this._callbacks[callbackKey].push(callbackFunction)
|
158
227
|
return callbackFunction
|
159
228
|
|
160
|
-
|
229
|
+
# removing a callback from the list
|
230
|
+
Model.prototype.removeCallback = Model.removeCallback = (callbackKey, callbackFunction) ->
|
161
231
|
index = this._callbacks[callbackKey].indexOf(callbackFunction)
|
162
232
|
if index != -1
|
163
233
|
this._callbacks[callbackKey].splice(index, 1)
|
@@ -179,7 +249,6 @@ LiveRecord.Model.create = (config) ->
|
|
179
249
|
for callbackFunction in callbackFunctions
|
180
250
|
Model.addCallback(callbackKey, callbackFunction)
|
181
251
|
|
182
|
-
|
183
252
|
# enable plugins from arguments
|
184
253
|
for pluginKey, pluginValue of config.plugins
|
185
254
|
if LiveRecord.plugins
|
@@ -1,21 +1,7 @@
|
|
1
|
-
|
1
|
+
#= require_self
|
2
|
+
#= require_directory ./live_dom/
|
2
3
|
|
3
|
-
|
4
|
-
throw new Error('jQuery is not loaded yet, and is a dependency of LiveRecord')
|
5
|
-
|
6
|
-
LiveRecord.plugins.LiveDOM.applyToModel = (Model, pluginValue) ->
|
7
|
-
return if pluginValue != true
|
8
|
-
|
9
|
-
# DOM callbacks
|
10
|
-
|
11
|
-
Model._updateDomCallback = ->
|
12
|
-
$updateableElements = $('[data-live-record-update-from]')
|
4
|
+
LiveRecord.plugins.LiveDOM ||= {}
|
13
5
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
Model._destroyDomCallback = ->
|
18
|
-
$('[data-live-record-destroy-from="' + Model.modelName + '-' + this.id() + '"]').remove()
|
19
|
-
|
20
|
-
Model.addCallback('after:update', Model._updateDomCallback)
|
21
|
-
Model.addCallback('after:destroy', Model._destroyDomCallback)
|
6
|
+
if window.jQuery == undefined
|
7
|
+
throw new Error('jQuery is not loaded yet, and is a dependency of LiveRecord')
|
@@ -0,0 +1,17 @@
|
|
1
|
+
LiveRecord.plugins.LiveDOM.applyToModel = (Model, pluginValue) ->
|
2
|
+
return if pluginValue != true
|
3
|
+
|
4
|
+
# DOM callbacks
|
5
|
+
Model._updateDomCallback = (domContext)->
|
6
|
+
domContext ||= $('body')
|
7
|
+
|
8
|
+
$updatableElements = domContext.find('[data-live-record-update-from]')
|
9
|
+
|
10
|
+
for key, value of this.attributes
|
11
|
+
$updatableElements.filter('[data-live-record-update-from="' + Model.modelName + '-' + this.id() + '-' + key + '"]').text(this[key]())
|
12
|
+
|
13
|
+
Model._destroyDomCallback = ->
|
14
|
+
$('[data-live-record-destroy-from="' + Model.modelName + '-' + this.id() + '"]').remove()
|
15
|
+
|
16
|
+
Model.addCallback('after:update', Model._updateDomCallback)
|
17
|
+
Model.addCallback('after:destroy', Model._destroyDomCallback)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class LiveRecord::BaseChannel < ActionCable::Channel::Base
|
2
|
+
|
3
|
+
protected
|
4
|
+
|
5
|
+
def authorised_attributes(record, current_user)
|
6
|
+
whitelisted_attributes = record.class.live_record_whitelisted_attributes(record, current_user)
|
7
|
+
raise "#{record.model}.live_record_whitelisted_attributes should return an array" unless whitelisted_attributes.is_a? Array
|
8
|
+
([:id] + whitelisted_attributes).map(&:to_s).to_set
|
9
|
+
end
|
10
|
+
|
11
|
+
def filtered_message(message, filters)
|
12
|
+
message['attributes'].slice!(*filters) if message['attributes'].present?
|
13
|
+
message
|
14
|
+
end
|
15
|
+
|
16
|
+
def find_record_from_params(params)
|
17
|
+
model_class = params[:model_name].safe_constantize
|
18
|
+
|
19
|
+
if model_class && model_class < ApplicationRecord
|
20
|
+
record = model_class.find_by(id: params[:record_id])
|
21
|
+
|
22
|
+
if record.present?
|
23
|
+
yield record
|
24
|
+
else
|
25
|
+
transmit 'action' => 'destroy'
|
26
|
+
end
|
27
|
+
else
|
28
|
+
respond_with_error(:bad_request, 'Not a correct model name')
|
29
|
+
reject_subscription
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def respond_with_error(type, message = nil)
|
34
|
+
case type
|
35
|
+
when :forbidden
|
36
|
+
transmit error: { 'code' => 'forbidden', 'message' => (message || 'You are not authorised') }
|
37
|
+
when :bad_request
|
38
|
+
transmit error: { 'code' => 'bad_request', 'message' => (message || 'Invalid request parameters') }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# This channel streams changes (update/destroy) from records to connected clients, through ActiveRecord callbacks
|
2
|
+
# This also supports syncing (old changes) when a client somehow got disconnected (i.e. network problems),
|
3
|
+
# through a separate cache `live_record_updates` table.
|
4
|
+
class LiveRecord::ChangesChannel < LiveRecord::BaseChannel
|
5
|
+
|
6
|
+
def subscribed
|
7
|
+
find_record_from_params(params) do |record|
|
8
|
+
authorised_attributes = authorised_attributes(record, current_user)
|
9
|
+
|
10
|
+
if authorised_attributes.present?
|
11
|
+
stream_for record, coder: ActiveSupport::JSON do |message|
|
12
|
+
begin
|
13
|
+
record.reload
|
14
|
+
rescue ActiveRecord::RecordNotFound
|
15
|
+
end
|
16
|
+
|
17
|
+
authorised_attributes = authorised_attributes(record, current_user)
|
18
|
+
|
19
|
+
# if not just :id
|
20
|
+
if authorised_attributes.size > 1
|
21
|
+
response = filtered_message(message, authorised_attributes)
|
22
|
+
transmit response if response.present?
|
23
|
+
else
|
24
|
+
respond_with_error(:forbidden)
|
25
|
+
reject_subscription
|
26
|
+
end
|
27
|
+
end
|
28
|
+
else
|
29
|
+
respond_with_error(:forbidden)
|
30
|
+
reject
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def sync_record(data)
|
36
|
+
find_record_from_params(data.symbolize_keys) do |record|
|
37
|
+
authorised_attributes = authorised_attributes(record, current_user)
|
38
|
+
|
39
|
+
# if not just :id
|
40
|
+
if authorised_attributes.size > 1
|
41
|
+
live_record_update = LiveRecordUpdate.where(
|
42
|
+
recordable_type: record.class.name,
|
43
|
+
recordable_id: record.id
|
44
|
+
).where(
|
45
|
+
'created_at >= ?', DateTime.parse(data['stale_since']) - LiveRecord.configuration.sync_record_buffer_time
|
46
|
+
).order(id: :asc)
|
47
|
+
|
48
|
+
if live_record_update.exists?
|
49
|
+
message = { 'action' => 'update', 'attributes' => record.attributes }
|
50
|
+
response = filtered_message(message, authorised_attributes)
|
51
|
+
transmit response if response.present?
|
52
|
+
end
|
53
|
+
else
|
54
|
+
respond_with_error(:forbidden)
|
55
|
+
reject_subscription
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# This channel streams new records to connected clients whenever the "where" condition supplied by the client matches
|
2
|
+
# This implementation can be quite inefficient because there's only one pub-sub queue used for each model, but because of
|
3
|
+
# constraints ( see https://github.com/jrpolidario/live_record/issues/2 ) and because Users are authorised-validated anyway
|
4
|
+
# in each stream, then there's already an overhead delay. I am prioritising development convenience (as Rails does), in order
|
5
|
+
# to achieve a simpler API; in this example, it would be something like in JS:
|
6
|
+
# `LiveRecord.Model.all.Book.subscribe({where: is_enabled: true})`
|
7
|
+
class LiveRecord::PublicationsChannel < LiveRecord::BaseChannel
|
8
|
+
|
9
|
+
def subscribed
|
10
|
+
model_class = params[:model_name].safe_constantize
|
11
|
+
|
12
|
+
if !(model_class && model_class < ApplicationRecord)
|
13
|
+
respond_with_error(:bad_request, 'Not a correct model name')
|
14
|
+
reject_subscription
|
15
|
+
end
|
16
|
+
|
17
|
+
stream_from "live_record:publications:#{params[:model_name].underscore}", coder: ActiveSupport::JSON do |message|
|
18
|
+
newly_created_record = model_class.find(message['attributes']['id'])
|
19
|
+
|
20
|
+
@authorised_attributes ||= authorised_attributes(newly_created_record, current_user)
|
21
|
+
|
22
|
+
active_record_relation = SearchAdapters.mapped_active_record_relation(
|
23
|
+
model_class: model_class,
|
24
|
+
conditions_hash: params[:where].to_h,
|
25
|
+
current_user: current_user,
|
26
|
+
authorised_attributes: @authorised_attributes
|
27
|
+
)
|
28
|
+
|
29
|
+
if active_record_relation.exists?(id: newly_created_record.id)
|
30
|
+
# if not just :id
|
31
|
+
if @authorised_attributes.size > 1
|
32
|
+
message = { 'action' => 'create', 'attributes' => message['attributes'] }
|
33
|
+
response = filtered_message(message, @authorised_attributes)
|
34
|
+
transmit response if response.present?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def sync_records(data)
|
41
|
+
params = data.symbolize_keys
|
42
|
+
model_class = params[:model_name].safe_constantize
|
43
|
+
|
44
|
+
if model_class && model_class < ApplicationRecord
|
45
|
+
|
46
|
+
records = model_class.where(
|
47
|
+
'created_at >= ?', DateTime.parse(params[:stale_since]) - LiveRecord.configuration.sync_record_buffer_time
|
48
|
+
)
|
49
|
+
|
50
|
+
# we `transmmit` a message back to client for each matching record
|
51
|
+
records.find_each do |record|
|
52
|
+
# now we check each record if it is part of the "where" condition
|
53
|
+
current_authorised_attributes = authorised_attributes(record, current_user)
|
54
|
+
|
55
|
+
active_record_relation = SearchAdapters.mapped_active_record_relation(
|
56
|
+
model_class: model_class,
|
57
|
+
conditions_hash: params[:where].to_h,
|
58
|
+
current_user: current_user,
|
59
|
+
authorised_attributes: current_authorised_attributes
|
60
|
+
)
|
61
|
+
|
62
|
+
if active_record_relation.exists?(id: record)
|
63
|
+
message = { 'action' => 'create', 'attributes' => record.attributes }
|
64
|
+
response = filtered_message(message, current_authorised_attributes)
|
65
|
+
transmit response if response.present?
|
66
|
+
end
|
67
|
+
end
|
68
|
+
else
|
69
|
+
respond_with_error(:bad_request, 'Not a correct model name')
|
70
|
+
reject_subscription
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
module SearchAdapters
|
75
|
+
def self.mapped_active_record_relation(**args)
|
76
|
+
# if ransack is loaded, use ransack
|
77
|
+
if Gem.loaded_specs.has_key? 'ransack'
|
78
|
+
active_record_relation = RansackAdapter.mapped_active_record_relation(args)
|
79
|
+
else
|
80
|
+
active_record_relation = ActiveRecordDefaultAdapter.mapped_active_record_relation(args)
|
81
|
+
end
|
82
|
+
active_record_relation
|
83
|
+
end
|
84
|
+
|
85
|
+
module RansackAdapter
|
86
|
+
def self.mapped_active_record_relation(**args)
|
87
|
+
model_class = args.fetch(:model_class)
|
88
|
+
conditions_hash = args.fetch(:conditions_hash)
|
89
|
+
current_user = args.fetch(:current_user)
|
90
|
+
|
91
|
+
model_class.ransack(conditions_hash, auth_object: current_user).result
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
module ActiveRecordDefaultAdapter
|
96
|
+
def self.mapped_active_record_relation(**args)
|
97
|
+
model_class = args.fetch(:model_class)
|
98
|
+
conditions_hash = args.fetch(:conditions_hash)
|
99
|
+
authorised_attributes = args.fetch(:authorised_attributes)
|
100
|
+
|
101
|
+
current_active_record_relation = model_class.all
|
102
|
+
|
103
|
+
conditions_hash.each do |key, value|
|
104
|
+
operator = key.split('_').last
|
105
|
+
# to get attribute_name, we subtract the end part of the string with size of operator substring; i.e.: created_at_lteq -> created_at
|
106
|
+
attribute_name = key[0..(-1 - operator.size - 1)]
|
107
|
+
|
108
|
+
if authorised_attributes == :all || authorised_attributes.include?(attribute_name)
|
109
|
+
case operator
|
110
|
+
when 'eq'
|
111
|
+
current_active_record_relation = current_active_record_relation.where(attribute_name => value)
|
112
|
+
when 'not_eq'
|
113
|
+
current_active_record_relation = current_active_record_relation.where.not(attribute_name => value)
|
114
|
+
when 'gt'
|
115
|
+
current_active_record_relation = current_active_record_relation.where(model_class.arel_table[attribute_name].gt(value))
|
116
|
+
when 'gteq'
|
117
|
+
current_active_record_relation = current_active_record_relation.where(model_class.arel_table[attribute_name].gteq(value))
|
118
|
+
when 'lt'
|
119
|
+
current_active_record_relation = current_active_record_relation.where(model_class.arel_table[attribute_name].lt(value))
|
120
|
+
when 'lteq'
|
121
|
+
current_active_record_relation = current_active_record_relation.where(model_class.arel_table[attribute_name].lteq(value))
|
122
|
+
when 'in'
|
123
|
+
current_active_record_relation = current_active_record_relation.where(attribute_name => Array.wrap(value))
|
124
|
+
when 'not_in'
|
125
|
+
current_active_record_relation = current_active_record_relation.where.not(attribute_name => Array.wrap(value))
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
current_active_record_relation
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|