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 initialize_arguments!(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 = @expiry ApiResource.with_ttl(expiry.to_f) do if numeric_find if single_find && (@conditions.blank_conditions? || include_associations_only?) # 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(:association).find(1) # Class.find(1) @scope = @scope.first if @scope.is_a?(Array) final_cond = @conditions.merge!(ApiResource::Conditions::ScopeCondition.new({:id => @scope}, self)) ApiResource::Finders::SingleFinder.new(self, final_cond).load else # e.g. Class.scope(1).find(1) # Class.includes(:association).find(1,2) # Class.find(1,2) # Class.active.find(1) if Array.wrap(@scope).size == 1 && @scope.is_a?(Array) @scope = @scope.first end fnd = @conditions.merge!(ApiResource::Conditions::ScopeCondition.new({:find => {:ids => @scope}}, self)) fnd.send(:all) end else # e.g. Class.scope(1).first # Class.first @scope = @scope.first if @scope.is_a?(Array) new_condition = @scope == :all ? {} : {@scope => true} final_cond = @conditions.merge!ApiResource::Conditions::ScopeCondition.new(new_condition, self) fnd = ApiResource::Finders::ResourceFinder.new(self, final_cond) fnd.send(@scope) 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 protected def arg_ary if @scope.blank? return :none elsif Array.wrap(@scope).size == 1 return :single else return :multiple end end def arg_type arg = Array.wrap(@scope).first case arg when :first, :last :bookend when :all :all_records else :number end end def numeric_find arg_type == :number end def single_find return arg_ary == :single end def initialize_arguments!(args) args = Array.wrap(args) # Conditions sometimes call find, passing themselves as the last arg. if args.last.is_a?(ApiResource::Conditions::AbstractCondition) cond = args.slice!(args.length - 1) else cond = nil end # Support options being passed in as a hash. options = args.extract_options! if options.blank? options = nil end @expiry = (options.is_a?(Hash) ? options.delete(:expires_in) : nil) || ApiResource::Base.ttl || 0 combine_conditions(options, cond) # Remaining args are the scope. @scope = args end def combine_conditions(options, condition) # 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 final_cond = ApiResource::Conditions::ScopeCondition.new({}, self) # Combine all combinations of conditions and options if condition if options final_cond = condition.merge!(options) else final_cond = condition end elsif options final_cond = options end @conditions = final_cond end def include_associations_only? if @conditions.blank_conditions? return false else return @conditions.conditions.include?(:foreign_key_id) && @conditions.conditions.size == 1 end end end end end