Module: Remotable::ActiveRecordExtender::ClassMethods
- Includes:
- Nosync
- Defined in:
- lib/remotable/active_record_extender.rb
Instance Attribute Summary collapse
Instance Method Summary
collapse
-
#__remotable_local_lookup(local_attributes, values) ⇒ Object
-
#__remotable_lookup(key, local_attributes, values) ⇒ Object
-
#all_by_remote ⇒ Object
-
#attr_remote(*attrs) ⇒ Object
-
#default_route_for(local_key, remote_key = nil) ⇒ Object
-
#expire_all! ⇒ Object
-
#expires_after(*args) ⇒ Object
-
#fetch_by(remote_attr, *values) ⇒ Object
Looks the resource up remotely, by the given attribute If the resource is found, wraps it in a new local resource and returns that.
-
#fetch_corresponding_local_resources(remote_resources) ⇒ Object
-
#fetch_with(local_key, options = {}) ⇒ Object
-
#find_by_remote_query(remote_method_name, *args) ⇒ Object
-
#find_remote_resource_by(remote_attr, *values) ⇒ Object
-
#find_remote_resource_for_local_by(local_resource, remote_attr, *values) ⇒ Object
-
#instantiate(*args) ⇒ Object
!nb: this method is called when associations are loaded so you can use the remoted record in associations.
-
#instantiate_instance_of(*args) ⇒ Object
!nb: In Rails 6+, this has been extracted from instantiate and can be called to instantiate homogenous sets of records without calling instantiate.
-
#invoke_remote_model_find_by(remote_attr, *values) ⇒ Object
-
#invoke_remote_model_find_by_for_local(local_resource, remote_attr, *values) ⇒ Object
-
#local_attribute_name(remote_attr) ⇒ Object
-
#local_attribute_names ⇒ Object
-
#local_attribute_routes ⇒ Object
-
#local_key(remote_key = nil) ⇒ Object
-
#map_remote_resources_to_local(remote_resources) ⇒ Object
-
#method_missing(method_sym, *values, &block) ⇒ Object
-
#nosync? ⇒ Boolean
-
#recognize_remote_finder_method(method_sym) ⇒ Object
If the missing method IS a Remotable finder method, returns the remote key (may be a composite key).
-
#remotable_skip_validation! ⇒ Object
-
#remotable_skip_validation_on_sync? ⇒ Boolean
-
#remote_attribute_map ⇒ Object
-
#remote_attribute_name(local_attr) ⇒ Object
-
#remote_attribute_names ⇒ Object
-
#remote_key(*args) ⇒ Object
Sets the key with which a resource is identified remotely.
-
#remote_path_for(remote_key, *values) ⇒ Object
-
#remote_path_for_composite_key(route, local_key, values) ⇒ Object
-
#remote_path_for_simple_key(route, local_key, value) ⇒ Object
-
#remote_timeout(*args) ⇒ Object
-
#report_ignored_503_error(error) ⇒ Object
-
#report_ignored_network_error(error) ⇒ Object
-
#report_ignored_ssl_error(error) ⇒ Object
-
#report_ignored_timeout_error(error) ⇒ Object
-
#respond_to?(method_sym, include_private = false) ⇒ Boolean
!todo: create these methods on an anonymous module and mix it in.
-
#route_for(remote_key) ⇒ Object
-
#sync_all! ⇒ Object
-
#sync_on_instantiate(record) ⇒ Object
Methods included from Nosync
extended, included
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method_sym, *values, &block) ⇒ Object
230
231
232
233
234
235
236
237
238
239
240
241
|
# File 'lib/remotable/active_record_extender.rb', line 230
def method_missing(method_sym, *values, &block)
method_details = recognize_remote_finder_method(method_sym)
return super(method_sym, *values, &block) unless method_details
local_attributes = method_details[:local_attributes]
raise ArgumentError, "#{method_sym} was called with #{values.length} but #{local_attributes.length} was expected" unless values.length == local_attributes.length
local_resource = __remotable_lookup(method_details[:remote_key], local_attributes, values)
local_resource = nil if local_resource && local_resource.destroyed?
raise ActiveRecord::RecordNotFound if local_resource.nil? && (method_sym =~ /!$/)
local_resource
end
|
Instance Attribute Details
#_expires_after ⇒ Object
Returns the value of attribute _expires_after.
46
47
48
|
# File 'lib/remotable/active_record_extender.rb', line 46
def _expires_after
@_expires_after
end
|
#_local_attribute_routes ⇒ Object
Returns the value of attribute _local_attribute_routes.
46
47
48
|
# File 'lib/remotable/active_record_extender.rb', line 46
def _local_attribute_routes
@_local_attribute_routes
end
|
#_remote_attribute_map ⇒ Object
Returns the value of attribute _remote_attribute_map.
46
47
48
|
# File 'lib/remotable/active_record_extender.rb', line 46
def _remote_attribute_map
@_remote_attribute_map
end
|
#_remote_timeout ⇒ Object
Returns the value of attribute _remote_timeout.
46
47
48
|
# File 'lib/remotable/active_record_extender.rb', line 46
def _remote_timeout
@_remote_timeout
end
|
#remotable_skip_validation_on_sync ⇒ Object
Returns the value of attribute remotable_skip_validation_on_sync.
46
47
48
|
# File 'lib/remotable/active_record_extender.rb', line 46
def remotable_skip_validation_on_sync
@remotable_skip_validation_on_sync
end
|
Instance Method Details
#__remotable_local_lookup(local_attributes, values) ⇒ Object
249
250
251
252
253
|
# File 'lib/remotable/active_record_extender.rb', line 249
def __remotable_local_lookup(local_attributes, values)
(0...local_attributes.length)
.inject(self) { |scope, i| scope.where(local_attributes[i] => values[i]) }
.limit(1).first
end
|
#__remotable_lookup(key, local_attributes, values) ⇒ Object
243
244
245
246
247
|
# File 'lib/remotable/active_record_extender.rb', line 243
def __remotable_lookup(key, local_attributes, values)
__remotable_local_lookup(local_attributes, values) || fetch_by(key, *values)
rescue ActiveRecord::RecordNotUnique
__remotable_local_lookup(local_attributes, values)
end
|
#all_by_remote ⇒ Object
366
367
368
|
# File 'lib/remotable/active_record_extender.rb', line 366
def all_by_remote
find_by_remote_query(:all)
end
|
#attr_remote(*attrs) ⇒ Object
95
96
97
98
99
100
|
# File 'lib/remotable/active_record_extender.rb', line 95
def attr_remote(*attrs)
map = attrs.
map = attrs.map_to_self.merge(map)
self._remote_attribute_map = map
self._local_attribute_routes = {} end
|
#default_route_for(local_key, remote_key = nil) ⇒ Object
161
162
163
164
165
166
167
168
|
# File 'lib/remotable/active_record_extender.rb', line 161
def default_route_for(local_key, remote_key=nil)
remote_key ||= remote_attribute_name(local_key)
if remote_key.to_s == primary_key
":#{local_key}"
else
"by_#{local_key}/:#{local_key}"
end
end
|
#expire_all! ⇒ Object
282
283
284
|
# File 'lib/remotable/active_record_extender.rb', line 282
def expire_all!
update_all(expires_at: 1.day.ago)
end
|
#expires_after(*args) ⇒ Object
90
91
92
93
|
# File 'lib/remotable/active_record_extender.rb', line 90
def expires_after(*args)
self._expires_after = args.first if args.any?
_expires_after
end
|
#fetch_by(remote_attr, *values) ⇒ Object
Looks the resource up remotely, by the given attribute If the resource is found, wraps it in a new local resource and returns that.
296
297
298
299
|
# File 'lib/remotable/active_record_extender.rb', line 296
def fetch_by(remote_attr, *values)
remote_resource = find_remote_resource_by(remote_attr, *values)
remote_resource && new_from_remote(remote_resource)
end
|
#fetch_corresponding_local_resources(remote_resources) ⇒ Object
408
409
410
411
412
413
414
415
|
# File 'lib/remotable/active_record_extender.rb', line 408
def fetch_corresponding_local_resources(remote_resources)
conditions = Array.wrap(remote_key).each_with_object({}) do |remote_attr, query|
local_attr = local_attribute_name(remote_attr)
query[local_attr] = remote_resources.map { |resource| resource[remote_attr] }
end
where(conditions)
end
|
#fetch_with(local_key, options = {}) ⇒ Object
118
119
120
|
# File 'lib/remotable/active_record_extender.rb', line 118
def fetch_with(local_key, options={})
self._local_attribute_routes.merge!(local_key => options[:path])
end
|
#find_by_remote_query(remote_method_name, *args) ⇒ Object
370
371
372
373
374
375
|
# File 'lib/remotable/active_record_extender.rb', line 370
def find_by_remote_query(remote_method_name, *args)
remote_set_timeout :list
remote_resources = Array.wrap(remote_model.send(remote_method_name, *args))
map_remote_resources_to_local(remote_resources)
end
|
#find_remote_resource_by(remote_attr, *values) ⇒ Object
301
302
303
|
# File 'lib/remotable/active_record_extender.rb', line 301
def find_remote_resource_by(remote_attr, *values)
invoke_remote_model_find_by(remote_attr, *values)
end
|
#find_remote_resource_for_local_by(local_resource, remote_attr, *values) ⇒ Object
305
306
307
308
309
310
311
|
# File 'lib/remotable/active_record_extender.rb', line 305
def find_remote_resource_for_local_by(local_resource, remote_attr, *values)
if remote_model.respond_to?(:find_by_for_local)
invoke_remote_model_find_by_for_local(local_resource, remote_attr, *values)
else
invoke_remote_model_find_by(remote_attr, *values)
end
end
|
#instantiate(*args) ⇒ Object
!nb: this method is called when associations are loaded so you can use the remoted record in associations.
174
175
176
177
178
|
# File 'lib/remotable/active_record_extender.rb', line 174
def instantiate(*args)
super.tap do |record|
sync_on_instantiate(record) unless ActiveRecord.version.segments.first > 5
end
end
|
#instantiate_instance_of(*args) ⇒ Object
!nb: In Rails 6+, this has been extracted from instantiate and can be called to instantiate homogenous sets of records without calling instantiate
182
183
184
185
186
|
# File 'lib/remotable/active_record_extender.rb', line 182
def instantiate_instance_of(*args)
super.tap do |record|
sync_on_instantiate(record)
end
end
|
#invoke_remote_model_find_by(remote_attr, *values) ⇒ Object
313
314
315
316
317
318
319
320
321
322
323
|
# File 'lib/remotable/active_record_extender.rb', line 313
def invoke_remote_model_find_by(remote_attr, *values)
remote_set_timeout :fetch
find_by = remote_model.method(:find_by)
case find_by.arity
when 1; find_by.call(remote_path_for(remote_attr, *values))
when 2; find_by.call(remote_attr, *values)
else
raise InvalidRemoteModel, "#{remote_model}.find_by should take either 1 or 2 parameters"
end
end
|
#invoke_remote_model_find_by_for_local(local_resource, remote_attr, *values) ⇒ Object
325
326
327
328
329
330
331
332
333
334
335
|
# File 'lib/remotable/active_record_extender.rb', line 325
def invoke_remote_model_find_by_for_local(local_resource, remote_attr, *values)
remote_set_timeout :pull
find_by_for_local = remote_model.method(:find_by_for_local)
case find_by_for_local.arity
when 2; find_by_for_local.call(local_resource, remote_path_for(remote_attr, *values))
when 3; find_by_for_local.call(local_resource, remote_attr, *values)
else
raise InvalidRemoteModel, "#{remote_model}.find_by_for_local should take either 2 or 3 parameters"
end
end
|
#local_attribute_name(remote_attr) ⇒ Object
152
153
154
|
# File 'lib/remotable/active_record_extender.rb', line 152
def local_attribute_name(remote_attr)
_remote_attribute_map[remote_attr] || remote_attr
end
|
#local_attribute_names ⇒ Object
144
145
146
|
# File 'lib/remotable/active_record_extender.rb', line 144
def local_attribute_names
_remote_attribute_map.values
end
|
#local_attribute_routes ⇒ Object
114
115
116
|
# File 'lib/remotable/active_record_extender.rb', line 114
def local_attribute_routes
self._local_attribute_routes
end
|
#local_key(remote_key = nil) ⇒ Object
131
132
133
134
135
136
137
138
|
# File 'lib/remotable/active_record_extender.rb', line 131
def local_key(remote_key=nil)
remote_key ||= self.remote_key
if remote_key.is_a?(Array)
remote_key.map(&method(:local_attribute_name))
else
local_attribute_name(remote_key)
end
end
|
#map_remote_resources_to_local(remote_resources) ⇒ Object
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
|
# File 'lib/remotable/active_record_extender.rb', line 377
def map_remote_resources_to_local(remote_resources)
return [] if remote_resources.nil? || remote_resources.empty?
local_resources = nosync { fetch_corresponding_local_resources(remote_resources).to_a }
remote_resources.map do |remote_resource|
local_resource = local_resources.detect { |local_resource|
Array.wrap(remote_key).all? { |remote_attr|
local_attr = local_attribute_name(remote_attr)
local_resource.send(local_attr) == remote_resource[remote_attr]
}
}
if local_resource
local_resource.instance_variable_set :@remote_resource, remote_resource
local_resource.pull_remote_data!
else
new_from_remote(remote_resource)
end
end
end
|
#nosync? ⇒ Boolean
49
50
51
52
53
|
# File 'lib/remotable/active_record_extender.rb', line 49
def nosync?
return true if remote_model.nil?
return super if nosync_value?
Remotable.nosync?
end
|
#recognize_remote_finder_method(method_sym) ⇒ Object
If the missing method IS a Remotable finder method, returns the remote key (may be a composite key). Otherwise, returns false.
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
|
# File 'lib/remotable/active_record_extender.rb', line 258
def recognize_remote_finder_method(method_sym)
method_name = method_sym.to_s
return false unless method_name =~ /find_by_([^!]*)(!?)/
local_attributes = $1.split("_and_").map(&:to_sym)
remote_attributes = local_attributes.map(&method(:remote_attribute_name))
local_key, remote_key = if local_attributes.length == 1
[local_attributes[0], remote_attributes[0]]
else
[local_attributes, remote_attributes]
end
generate_default_remote_key
return false unless _local_attribute_routes.key?(local_key)
{ :local_attributes => local_attributes,
:remote_key => remote_key }
end
|
#remotable_skip_validation! ⇒ Object
122
123
124
|
# File 'lib/remotable/active_record_extender.rb', line 122
def remotable_skip_validation!
self.remotable_skip_validation_on_sync = true
end
|
#remotable_skip_validation_on_sync? ⇒ Boolean
126
127
128
|
# File 'lib/remotable/active_record_extender.rb', line 126
def remotable_skip_validation_on_sync?
self.remotable_skip_validation_on_sync
end
|
#remote_attribute_map ⇒ Object
110
111
112
|
# File 'lib/remotable/active_record_extender.rb', line 110
def remote_attribute_map
self._remote_attribute_map
end
|
#remote_attribute_name(local_attr) ⇒ Object
148
149
150
|
# File 'lib/remotable/active_record_extender.rb', line 148
def remote_attribute_name(local_attr)
_remote_attribute_map.key(local_attr) || local_attr
end
|
#remote_attribute_names ⇒ Object
140
141
142
|
# File 'lib/remotable/active_record_extender.rb', line 140
def remote_attribute_names
_remote_attribute_map.keys
end
|
#remote_key(*args) ⇒ Object
Sets the key with which a resource is identified remotely. If no remote key is set, the remote key is assumed to be :id. Which could be explicitly set like this:
remote_key :id
It can can be a composite key:
remote_key [:calendar_id, :id]
You can also supply a path for the remote key which will be passed to fetch_with:
remote_key [:calendar_id, :id], :path => "calendars/:calendar_id/events/:id"
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
# File 'lib/remotable/active_record_extender.rb', line 70
def remote_key(*args)
if args.any?
remote_key = args.shift
options = args.shift || {}
Array.wrap(remote_key).each do |attribute|
raise(":#{attribute} is not the name of a remote attribute") unless remote_attribute_names.member?(attribute)
end
fetch_with(local_key(remote_key), options)
@remote_key = remote_key
else
@remote_key || generate_default_remote_key
end
end
|
#remote_path_for(remote_key, *values) ⇒ Object
339
340
341
342
343
344
345
346
347
348
|
# File 'lib/remotable/active_record_extender.rb', line 339
def remote_path_for(remote_key, *values)
route = route_for(remote_key)
local_key = self.local_key(remote_key)
if remote_key.is_a?(Array)
remote_path_for_composite_key(route, local_key, values)
else
remote_path_for_simple_key(route, local_key, values.first)
end
end
|
#remote_path_for_composite_key(route, local_key, values) ⇒ Object
354
355
356
357
358
359
360
361
362
363
|
# File 'lib/remotable/active_record_extender.rb', line 354
def remote_path_for_composite_key(route, local_key, values)
values.flatten!
unless values.length == local_key.length
raise ArgumentError, "local_key has #{local_key.length} attributes but values has #{values.length}"
end
(0...values.length).inject(route) do |route, i|
route.gsub(/:#{local_key[i]}/, ERB::Util.url_encode(values[i].to_s))
end
end
|
#remote_path_for_simple_key(route, local_key, value) ⇒ Object
350
351
352
|
# File 'lib/remotable/active_record_extender.rb', line 350
def remote_path_for_simple_key(route, local_key, value)
route.gsub(/:#{local_key}/, ERB::Util.url_encode(value.to_s))
end
|
#remote_timeout(*args) ⇒ Object
102
103
104
105
106
107
108
|
# File 'lib/remotable/active_record_extender.rb', line 102
def remote_timeout(*args)
if args.any?
self._remote_timeout = n = args.first
self._remote_timeout = {:list => n, :fetch => n, :pull => n, :create => n, :update => n, :destroy => n} if n.is_a?(Numeric)
end
_remote_timeout
end
|
#report_ignored_503_error(error) ⇒ Object
213
214
215
|
# File 'lib/remotable/active_record_extender.rb', line 213
def report_ignored_503_error(error)
Remotable.logger.error "[remotable:#{name.underscore}:instantiate] #{error.message}"
end
|
#report_ignored_network_error(error) ⇒ Object
209
210
211
|
# File 'lib/remotable/active_record_extender.rb', line 209
def report_ignored_network_error(error)
Remotable.logger.error "[remotable:#{name.underscore}:instantiate] #{error.message}"
end
|
#report_ignored_ssl_error(error) ⇒ Object
217
218
219
|
# File 'lib/remotable/active_record_extender.rb', line 217
def report_ignored_ssl_error(error)
Remotable.logger.error "[remotable:#{name.underscore}:instantiate] #{error.message}"
end
|
#report_ignored_timeout_error(error) ⇒ Object
205
206
207
|
# File 'lib/remotable/active_record_extender.rb', line 205
def report_ignored_timeout_error(error)
Remotable.logger.error "[remotable:#{name.underscore}:instantiate] #{error.message}"
end
|
#respond_to?(method_sym, include_private = false) ⇒ Boolean
!todo: create these methods on an anonymous module and mix it in
225
226
227
228
|
# File 'lib/remotable/active_record_extender.rb', line 225
def respond_to?(method_sym, include_private=false)
return true if recognize_remote_finder_method(method_sym)
super(method_sym, include_private)
end
|
#route_for(remote_key) ⇒ Object
156
157
158
159
|
# File 'lib/remotable/active_record_extender.rb', line 156
def route_for(remote_key)
local_key = self.local_key(remote_key)
_local_attribute_routes[local_key] || default_route_for(local_key, remote_key)
end
|
#sync_all! ⇒ Object
286
287
288
289
|
# File 'lib/remotable/active_record_extender.rb', line 286
def sync_all!
expire_all!
all.to_a
end
|
#sync_on_instantiate(record) ⇒ Object
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
|
# File 'lib/remotable/active_record_extender.rb', line 188
def sync_on_instantiate(record)
if record.expired? && !record.nosync?
begin
Remotable.logger.debug "[remotable:#{name.underscore}:sync_on_instantiate](#{record.fetch_value.inspect}) expired #{record.expires_at}"
record.pull_remote_data!
rescue Remotable::TimeoutError
report_ignored_timeout_error($!)
rescue Remotable::NetworkError
report_ignored_network_error($!)
rescue Remotable::ServiceUnavailableError
report_ignored_503_error($!)
rescue Remotable::SSLError
report_ignored_ssl_error($!)
end
end
end
|