module ApiResource module Scopes extend ActiveSupport::Concern module ClassMethods # TODO: calling these methods should force loading of the resource definition def scopes self.reload_resource_definition return self.related_objects[:scopes] end def scope?(name) self.related_objects[:scopes].has_key?(name.to_sym) end def scope_attributes(name) raise "No such scope #{name}" unless self.scope?(name) self.related_objects[:scopes][name.to_sym] end # Called by base.rb # @param scope_name is the scope_name of the scope from the json # e.g. paged # # @param scope_definition is always a hash with the arguments for the scope # e.g. {:page => "req", "per_page" => "opt"} def scope(scope_name, scope_definition) unless scope_definition.is_a?(Hash) raise ArgumentError, "Expecting an attributes hash given #{scope_definition.inspect}" end self.related_objects[:scopes][scope_name.to_sym] = scope_definition self.class_eval do define_singleton_method(scope_name) do |*args| arg_names = scope_definition.keys arg_types = scope_definition.values finder_opts = { scope_name => {} } arg_names.each_with_index do |arg_name, i| # If we are dealing with a scope with multiple args if arg_types[i] == :rest finder_opts[scope_name][arg_name] = args.slice(i, args.count) # Else we are only dealing with a single argument else if arg_types[i] == :req || (i < args.count) finder_opts[scope_name][arg_name] = args[i] end end end # if we have nothing at this point we should just pass 'true' if finder_opts[scope_name] == {} finder_opts[scope_name] = true end ApiResource::Conditions::ScopeCondition.new(finder_opts, self) end end end # # Apply scopes from params based on our resource # definition # def add_scopes(params, base = self) # scopes are stored as strings but we want to allow params = params.with_indifferent_access base = self.add_static_scopes(params, base) return self.add_dynamic_scopes(params, base) end protected def add_static_scopes(params, base) self.static_scopes.each do |name| if params[name].present? base = base.send(name) end end return base end # # Add our dynamic scopes based on a set of params # # @param params [Hash] User-supplied params # @param base [ApiResource::Conditions::AbstractCondition, Class] Base # Scope # # @return [ApiResource::Conditions::AbstractCondition] [description] def add_dynamic_scopes(params, base) self.dynamic_scopes.each_pair do |name, args| # make sure we have all required arguments next unless self.check_required_scope_args(args, params[name]) # the args we will apply caller_args = [] # iterate through our args and add them to an array to send to our # scope args.keys.each do |subkey| # we only apply things that are present or explicitly false if val = self.get_scope_arg_value(subkey, params[name][subkey]) caller_args << val end end # call our scope with the supplied args base = base.send(name, *caller_args) end return base end # # Check if we have supplied all of the necessary # @param scope [Hash] [Scope Definition # @param params [Hash] [Supplied params] # # @return [Boolean] Validity def check_required_scope_args(scope, params) # make sure we have a hash and it has values return false unless params.is_a?(Hash) && params.present? # find required values required = scope.select{ |k,v| v.to_sym == :req }.keys # make sure we have all of the required values, we allow false required.all? { |key| params[key].present? || params[key] == false } end # # Wrapper method to define all scopes from the resource definition # # @return [Boolean] true def define_all_scopes if self.resource_definition["scopes"] self.resource_definition["scopes"].each_pair do |name, opts| self.scope(name, opts) end end true end # # Scopes that require arguments # # @return [Hash] def dynamic_scopes self.scopes.select { |name, args| args.present? } end # # Get the parsed/formatted arguments to send to the server # # @param key [String, Symbol] Key name for the scope value # @param value [String, Integer, Symbol] Value for the scope # # @return [String, Integer, Symbol, Date] Parsed/formatted value def get_scope_arg_value(key, value) # cast to string to avoid incorred blank? behavior for us return "false" if value == false # if we havea date field, try to parse, falling back to the original # value if key.to_s =~ /date/ value = Date.parse(value) rescue value end # return the final value value end def static_scopes self.scopes.select { |name, args| args.blank? }.keys end end def scopes return self.class.scopes end def scope?(name) return self.class.scope?(name) end def scope_attributes(name) return self.class.scope_attributes(name) end end end