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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -2
  3. data/{lib/live_record/.rspec → .rspec} +0 -0
  4. data/.travis.yml +0 -1
  5. data/Gemfile +3 -1
  6. data/README.md +363 -202
  7. data/Rakefile +5 -0
  8. data/app/assets/javascripts/live_record.coffee +4 -0
  9. data/app/assets/javascripts/live_record/helpers.coffee +4 -0
  10. data/app/assets/javascripts/live_record/helpers/load_records.coffee +13 -7
  11. data/app/assets/javascripts/live_record/helpers/spaceship.coffee +13 -0
  12. data/app/assets/javascripts/live_record/model.coffee +4 -0
  13. data/app/assets/javascripts/live_record/model/create.coffee +96 -27
  14. data/app/assets/javascripts/live_record/plugins.coffee +3 -0
  15. data/app/assets/javascripts/live_record/plugins/live_dom.coffee +5 -19
  16. data/app/assets/javascripts/live_record/plugins/live_dom/apply_to_model.coffee +17 -0
  17. data/app/channels/live_record/base_channel.rb +41 -0
  18. data/app/channels/live_record/changes_channel.rb +59 -0
  19. data/app/channels/live_record/publications_channel.rb +134 -0
  20. data/{lib/live_record/config.ru → config.ru} +0 -0
  21. data/lib/live_record.rb +2 -0
  22. data/lib/live_record/action_view_extensions/view_helper.rb +22 -0
  23. data/lib/live_record/configure.rb +19 -0
  24. data/lib/live_record/generators/install_generator.rb +9 -4
  25. data/lib/live_record/generators/templates/create_live_record_updates.rb +1 -1
  26. data/lib/live_record/generators/templates/index.html.erb +1 -0
  27. data/lib/live_record/generators/templates/model.rb.rb +4 -4
  28. data/lib/live_record/model/callbacks.rb +8 -2
  29. data/lib/live_record/version.rb +1 -1
  30. data/live_record.gemspec +2 -2
  31. data/{lib/live_record/spec → spec}/factories/posts.rb +0 -0
  32. data/spec/features/live_record_syncing_spec.rb +184 -0
  33. data/spec/helpers/wait.rb +19 -0
  34. data/{lib/live_record/spec → spec}/internal/app/assets/config/manifest.js +0 -0
  35. data/{lib/live_record/spec → spec}/internal/app/assets/javascripts/application.js +0 -0
  36. data/{lib/live_record/spec → spec}/internal/app/assets/javascripts/cable.js +0 -0
  37. data/spec/internal/app/assets/javascripts/posts.coffee +8 -0
  38. data/{lib/live_record/spec → spec}/internal/app/channels/application_cable/channel.rb +0 -0
  39. data/{lib/live_record/spec → spec}/internal/app/channels/application_cable/connection.rb +4 -4
  40. data/{lib/live_record/spec → spec}/internal/app/controllers/application_controller.rb +0 -0
  41. data/{lib/live_record/spec → spec}/internal/app/controllers/posts_controller.rb +1 -0
  42. data/{lib/live_record/spec → spec}/internal/app/models/application_record.rb +0 -0
  43. data/{lib/live_record/spec → spec}/internal/app/models/live_record_update.rb +0 -0
  44. data/spec/internal/app/models/post.rb +11 -0
  45. data/{lib/live_record/spec → spec}/internal/app/views/layouts/application.html.erb +0 -0
  46. data/{lib/live_record/spec → spec}/internal/app/views/posts/_form.html.erb +0 -0
  47. data/{lib/live_record/spec → spec}/internal/app/views/posts/_post.json.jbuilder +0 -0
  48. data/{lib/live_record/spec → spec}/internal/app/views/posts/edit.html.erb +0 -0
  49. data/{lib/live_record/spec → spec}/internal/app/views/posts/index.html.erb +6 -5
  50. data/{lib/live_record/spec → spec}/internal/app/views/posts/index.json.jbuilder +0 -0
  51. data/{lib/live_record/spec → spec}/internal/app/views/posts/new.html.erb +0 -0
  52. data/{lib/live_record/spec → spec}/internal/app/views/posts/show.html.erb +0 -0
  53. data/{lib/live_record/spec → spec}/internal/app/views/posts/show.json.jbuilder +0 -0
  54. data/{lib/live_record/spec → spec}/internal/config/cable.yml +0 -0
  55. data/{lib/live_record/spec → spec}/internal/config/database.yml +0 -0
  56. data/{lib/live_record/spec → spec}/internal/config/routes.rb +0 -0
  57. data/{lib/live_record/spec → spec}/internal/db/schema.rb +2 -0
  58. data/{lib/live_record/spec → spec}/internal/public/favicon.ico +0 -0
  59. data/{lib/live_record/spec → spec}/rails_helper.rb +4 -2
  60. data/{lib/live_record/spec → spec}/spec_helper.rb +0 -0
  61. metadata +64 -56
  62. data/app/assets/javascripts/live_record.js +0 -4
  63. data/app/assets/javascripts/live_record/helpers.js +0 -4
  64. data/app/assets/javascripts/live_record/model.js +0 -4
  65. data/app/assets/javascripts/live_record/plugins.js +0 -3
  66. data/lib/live_record/channel/implement.rb +0 -100
  67. data/lib/live_record/spec/features/live_record_syncing_spec.rb +0 -60
  68. data/lib/live_record/spec/internal/app/assets/javascripts/posts.coffee +0 -14
  69. data/lib/live_record/spec/internal/app/channels/live_record_channel.rb +0 -4
  70. data/lib/live_record/spec/internal/app/models/post.rb +0 -11
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ RSpec::Core::RakeTask.new('spec')
4
+
5
+ task default: :spec
@@ -0,0 +1,4 @@
1
+ #= require_self
2
+ #= require_directory ./live_record/
3
+
4
+ this.LiveRecord ||= {}
@@ -0,0 +1,4 @@
1
+ #= require_self
2
+ #= require_directory ./helpers/
3
+
4
+ LiveRecord.helpers ||= {}
@@ -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 << record
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, records) if args['onLoad']
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
@@ -0,0 +1,4 @@
1
+ #= require_self
2
+ #= require_directory ./model/
3
+
4
+ LiveRecord.Model ||= {}
@@ -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.prototype.modelName = ->
31
- Model.modelName
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
- channel: 'LiveRecordChannel'
39
- model_name: this.modelName()
40
- record_id: this.id()
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
- Model.addCallback = Model.prototype.addCallback = (callbackKey, callbackFunction) ->
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
- Model.removeCallback = Model.prototype.removeCallback = (callbackKey, callbackFunction) ->
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
@@ -0,0 +1,3 @@
1
+ #= require_self
2
+
3
+ LiveRecord.plugins ||= {}
@@ -1,21 +1,7 @@
1
- this.LiveRecord.plugins.LiveDOM || (this.LiveRecord.plugins.LiveDOM = {});
1
+ #= require_self
2
+ #= require_directory ./live_dom/
2
3
 
3
- if window.jQuery == undefined
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
- for key, value of this.attributes
15
- $updateableElements.filter('[data-live-record-update-from="' + Model.modelName + '-' + this.id() + '-' + key + '"]').text(this[key]())
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