module ApiResource
module Finders
extend ActiveSupport::Concern
extend ActiveSupport::Autoload
autoload :AbstractFinder
autoload :ResourceFinder
autoload :SingleFinder
autoload :SingleObjectAssociationFinder
autoload :MultiObjectAssociationFinder
module ClassMethods
# This decides which finder method to call.
# It accepts arguments of the form "scope", "options={}"
# where options can be standard rails options or :expires_in.
# If :expires_in is set, it caches it for expires_in seconds.
# Need to support the following cases
# => 1) Klass.find(1)
# => 2) Klass.find(:all, :params => {a => b})
# => 3) Klass.find(:first, :params => {a => b})
# => 4) Klass.includes(:assoc).find(1)
# => 5) Klass.active.find(1)
# => 6) Klass.includes(:assoc).find(:all, a => b)
def find(*arguments)
# make sure we have class data before loading
self.load_resource_definition
# Conditions sometimes call find, passing themselves as the last arg.
if arguments.last.is_a?(ApiResource::Conditions::AbstractCondition)
cond = arguments.slice!(arguments.length - 1)
else
cond = nil
end
# Support options being passed in as a hash.
options = arguments.extract_options! || {}
# Remaining arguments are the scope.
scope = arguments
# TODO: Make this into a class attribute properly (if it isn't already)
# this is a little bit of a hack because options can sometimes be a Condition
expiry = (options.is_a?(Hash) ? options.delete(:expires_in) : nil) || ApiResource::Base.ttl || 0
ApiResource.with_ttl(expiry.to_f) do
# If we have a condition or options to process...
if cond || options.present?
# Convert options hash to scope condition.
if options.is_a?(Hash)
opts = options.with_indifferent_access.delete(:params) || options || {}
options = ApiResource::Conditions::ScopeCondition.new(opts, self)
end
# Combine all combinations of conditions and options
if cond
if options
final_cond = cond.merge!(options)
else
final_cond = cond
end
elsif options
final_cond = options
end
# If we have one argument, it's either a word argument
# like first, last, or all, or its a number.
if Array.wrap(scope).size == 1
scope = scope.first if scope.is_a?(Array)
# Create the finder with the conditions, then call the scope.
# e.g. Class.scope(1).first
if [:all, :first, :last].include?(scope)
fnd = ApiResource::Finders::ResourceFinder.new(self, final_cond)
fnd.send(scope)
# If we have no conditions or they are only prefixes or
# includes,and only one argument (not a word) then we
# only have a single item to find.
# e.g. Class.includes.find(1)
elsif final_cond.blank_conditions? || final_cond.conditions.include?(:foreign_key_id)
scope = scope.first if scope.is_a?(Array)
final_cond = final_cond.merge!(ApiResource::Conditions::ScopeCondition.new({:id => scope}, self))
ApiResource::Finders::SingleFinder.new(self, final_cond).load
else
# Otherwise we are chaining a find onto
# the end of a set of conditions.
# e.g. Class.scope(1).find(1)
fnd = final_cond.merge!(ApiResource::Conditions::ScopeCondition.new({:find => {:ids => scope}}, self))
fnd.send(:all)
end
else
# We are searching for multiple ids.
# e.g. Class.scope(1).find(1,2)
fnd = final_cond.merge!(ApiResource::Conditions::ScopeCondition.new({:find => {:ids => scope}}, self))
fnd.send(:all)
end
else
# No conditions
if Array.wrap(scope).size == 1
scope = scope.first if scope.is_a?(Array)
# We are calling first, last, or all on the class itself.
# e.g. Class.first
if [:all, :first, :last].include?(scope)
final_cond = ApiResource::Conditions::ScopeCondition.new({scope => true}, self)
fnd = ApiResource::Finders::ResourceFinder.new(self, final_cond)
fnd.send(scope)
else
# We are performing a simple find of a single object
# e.g. Class.find(1)
scope = scope.first if scope.is_a?(Array)
final_cond = ApiResource::Conditions::ScopeCondition.new({:id => scope}, self)
ApiResource::Finders::SingleFinder.new(self, final_cond).load
end
else
# We are performing a find on multiple objects
# e.g. Class.find(1,2)
ApiResource::Conditions::ScopeCondition.new({:find => {:ids => scope}}, self)
fnd.send(:all)
end
end
end
end
# A convenience wrapper for find(:first, *args). You can pass
# in all the same arguments to this method as you can to
# find(:first).
def first(*args)
find(:first, *args)
end
# A convenience wrapper for find(:last, *args). You can pass
# in all the same arguments to this method as you can to
# find(:last).
def last(*args)
find(:last, *args)
end
# This is an alias for find(:all). You can pass in all the same
# arguments to this method as you can to find(:all)
def all(*args)
find(:all, *args)
end
def instantiate_collection(collection)
collection.collect{|record|
instantiate_record(record)
}
end
def instantiate_record(record)
self.load_resource_definition
ret = self.allocate
ret.instance_variable_set(
:@attributes, record.with_indifferent_access
)
ret.instance_variable_set(
:@attributes_cache, HashWithIndifferentAccess.new
)
ret
end
end
end
end