Module: Remotable::ActiveRecordExtender::ClassMethods
- Includes:
- Nosync
- Defined in:
- lib/remotable/active_record_extender.rb
Instance Attribute Summary collapse
Instance Method Summary
collapse
-
#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
(also: #find_by)
-
#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.
-
#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_key(remote_key = nil) ⇒ Object
-
#map_remote_resources_to_local(remote_resources) ⇒ Object
-
#method_missing(method_sym, *args, &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).
-
#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_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
Methods included from Nosync
#nosync, #nosync!, #nosync=, #nosync_value?
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method_sym, *args, &block) ⇒ Object
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
|
# File 'lib/remotable/active_record_extender.rb', line 193
def method_missing(method_sym, *args, &block)
method_details = recognize_remote_finder_method(method_sym)
if method_details
local_attributes = method_details[:local_attributes]
values = args
unless values.length == local_attributes.length
raise ArgumentError, "#{method_sym} was called with #{values.length} but #{local_attributes.length} was expected"
end
local_resource = ((0...local_attributes.length).inject(self) do |scope, i|
scope.where(local_attributes[i] => values[i])
end).first || fetch_by(method_details[:remote_key], *values)
raise ActiveRecord::RecordNotFound if local_resource.nil? && (method_sym =~ /!$/)
local_resource
else
super(method_sym, *args, &block)
end
end
|
Instance Attribute Details
#local_attribute_routes ⇒ Object
Returns the value of attribute local_attribute_routes.
114
115
116
|
# File 'lib/remotable/active_record_extender.rb', line 114
def local_attribute_routes
@local_attribute_routes
end
|
#remote_attribute_map ⇒ Object
Returns the value of attribute remote_attribute_map.
114
115
116
|
# File 'lib/remotable/active_record_extender.rb', line 114
def remote_attribute_map
@remote_attribute_map
end
|
Instance Method Details
#all_by_remote ⇒ Object
321
322
323
|
# File 'lib/remotable/active_record_extender.rb', line 321
def all_by_remote
find_by_remote_query(:all)
end
|
#attr_remote(*attrs) ⇒ Object
92
93
94
95
96
97
|
# File 'lib/remotable/active_record_extender.rb', line 92
def attr_remote(*attrs)
map = attrs.
map = attrs.map_to_self.merge(map)
@remote_attribute_map = map
@local_attribute_routes = {} end
|
#default_route_for(local_key, remote_key = nil) ⇒ Object
147
148
149
150
151
152
153
154
|
# File 'lib/remotable/active_record_extender.rb', line 147
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
241
242
243
|
# File 'lib/remotable/active_record_extender.rb', line 241
def expire_all!
update_all(["expires_at=?", 1.day.ago])
end
|
#expires_after(*args) ⇒ Object
87
88
89
90
|
# File 'lib/remotable/active_record_extender.rb', line 87
def expires_after(*args)
@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.
250
251
252
253
|
# File 'lib/remotable/active_record_extender.rb', line 250
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
362
363
364
365
366
367
368
369
|
# File 'lib/remotable/active_record_extender.rb', line 362
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
Also known as:
find_by
107
108
109
|
# File 'lib/remotable/active_record_extender.rb', line 107
def fetch_with(local_key, options={})
@local_attribute_routes.merge!(local_key => options[:path])
end
|
#find_by_remote_query(remote_method_name, *args) ⇒ Object
325
326
327
328
329
|
# File 'lib/remotable/active_record_extender.rb', line 325
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
255
256
257
|
# File 'lib/remotable/active_record_extender.rb', line 255
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
259
260
261
262
263
264
265
|
# File 'lib/remotable/active_record_extender.rb', line 259
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.
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
# File 'lib/remotable/active_record_extender.rb', line 160
def instantiate(*args)
record = super
if record.expired? && !record.nosync?
begin
Remotable.logger.debug "[remotable:#{name.underscore}:instantiate](#{record.fetch_value.inspect}) expired #{record.expires_at}"
record.pull_remote_data!
record = nil if record.destroyed?
rescue Remotable::TimeoutError
report_ignored_timeout_error($!)
rescue Remotable::ServiceUnavailableError
report_ignored_503_error($!)
end
end
record
end
|
#invoke_remote_model_find_by(remote_attr, *values) ⇒ Object
267
268
269
270
271
272
273
274
275
276
277
|
# File 'lib/remotable/active_record_extender.rb', line 267
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
279
280
281
282
283
284
285
286
287
288
289
|
# File 'lib/remotable/active_record_extender.rb', line 279
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
138
139
140
|
# File 'lib/remotable/active_record_extender.rb', line 138
def local_attribute_name(remote_attr)
remote_attribute_map[remote_attr] || remote_attr
end
|
#local_attribute_names ⇒ Object
130
131
132
|
# File 'lib/remotable/active_record_extender.rb', line 130
def local_attribute_names
remote_attribute_map.values
end
|
#local_key(remote_key = nil) ⇒ Object
117
118
119
120
121
122
123
124
|
# File 'lib/remotable/active_record_extender.rb', line 117
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
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
|
# File 'lib/remotable/active_record_extender.rb', line 331
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
46
47
48
49
50
|
# File 'lib/remotable/active_record_extender.rb', line 46
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.
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
|
# File 'lib/remotable/active_record_extender.rb', line 217
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
|
#remote_attribute_name(local_attr) ⇒ Object
134
135
136
|
# File 'lib/remotable/active_record_extender.rb', line 134
def remote_attribute_name(local_attr)
remote_attribute_map.key(local_attr) || local_attr
end
|
#remote_attribute_names ⇒ Object
126
127
128
|
# File 'lib/remotable/active_record_extender.rb', line 126
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"
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
# File 'lib/remotable/active_record_extender.rb', line 67
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
293
294
295
296
297
298
299
300
301
302
|
# File 'lib/remotable/active_record_extender.rb', line 293
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
308
309
310
311
312
313
314
315
316
317
|
# File 'lib/remotable/active_record_extender.rb', line 308
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]}/, values[i].to_s)
end
end
|
#remote_path_for_simple_key(route, local_key, value) ⇒ Object
304
305
306
|
# File 'lib/remotable/active_record_extender.rb', line 304
def remote_path_for_simple_key(route, local_key, value)
route.gsub(/:#{local_key}/, value.to_s)
end
|
#remote_timeout(*args) ⇒ Object
99
100
101
102
103
104
105
|
# File 'lib/remotable/active_record_extender.rb', line 99
def remote_timeout(*args)
if args.any?
@remote_timeout = n = args.first
@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
180
181
182
|
# File 'lib/remotable/active_record_extender.rb', line 180
def report_ignored_503_error(error)
Remotable.logger.error "[remotable:#{name.underscore}:instantiate] #{error.message}"
end
|
#report_ignored_timeout_error(error) ⇒ Object
176
177
178
|
# File 'lib/remotable/active_record_extender.rb', line 176
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
188
189
190
191
|
# File 'lib/remotable/active_record_extender.rb', line 188
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
142
143
144
145
|
# File 'lib/remotable/active_record_extender.rb', line 142
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
|