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