module Searchgasm #:nodoc: module Search #:nodoc: # = Searchgasm # # Please refer the README.rdoc for usage, examples, and installation. class Base include Searchgasm::Shared::Utilities include Searchgasm::Shared::VirtualClasses # Options ActiveRecord allows when searching AR_FIND_OPTIONS = ::ActiveRecord::Base.valid_find_options # Options ActiveRecord allows when performing calculations AR_CALCULATIONS_OPTIONS = (::ActiveRecord::Base.valid_calculations_options - [:select, :limit, :offset, :order, :group]) AR_OPTIONS = (AR_FIND_OPTIONS + AR_CALCULATIONS_OPTIONS).uniq # Options that ActiveRecord doesn't suppport, but Searchgasm does SPECIAL_FIND_OPTIONS = [:order_by, :order_as, :page, :per_page, :priority_order, :priority_order_by, :priority_order_as] # Valid options you can use when searching OPTIONS = SPECIAL_FIND_OPTIONS + AR_OPTIONS # the order is very important, these options get set in this order attr_accessor *AR_OPTIONS attr_writer :scope class << self # Used in the ActiveRecord methods to determine if Searchgasm should get involved or not. # This keeps Searchgasm out of the way unless it is needed. def needed?(model_class, options) return false if options.blank? SPECIAL_FIND_OPTIONS.each do |option| return true if options.symbolize_keys.keys.include?(option) end Searchgasm::Conditions::Base.needed?(model_class, options[:conditions]) end end def initialize(init_options = {}) self.options = init_options end # Flag to determine if searchgasm is acting as a filter for the ActiveRecord search methods. # By filter it means that searchgasm is being used on the default ActiveRecord search methods, like all, count, find(:all), first, etc. def acting_as_filter=(value) @acting_as_filter = value end # See acting_as_filter= def acting_as_filter? @acting_as_filter == true end # Specific implementation of cloning def clone options = {} (AR_OPTIONS - [:conditions]).each { |option| options[option] = instance_variable_get("@#{option}") } options[:conditions] = conditions.conditions SPECIAL_FIND_OPTIONS.each { |option| options[option] = send(option) } obj = self.class.new(options) obj.protect = protected? obj.scope = scope obj end alias_method :dup, :clone # Makes using searchgasm in the console less annoying and keeps the output meaningful and useful def inspect current_find_options = {} (AR_OPTIONS - [:conditions]).each do |option| value = send(option) next if value.nil? current_find_options[option] = value end conditions_value = conditions.conditions current_find_options[:conditions] = conditions_value unless conditions_value.blank? current_find_options[:scope] = scope unless scope.blank? "#<#{klass}Search #{current_find_options.inspect}>" end # Merges all joins together, including the scopes joins for def joins all_joins = (safe_to_array(conditions.auto_joins) + safe_to_array(order_by_auto_joins) + safe_to_array(priority_order_by_auto_joins) + safe_to_array(@joins)).uniq # For partial backwards compatibility, delete if the scope contains conflicts, AR 2.2 does this for you scope_joins = safe_to_array(scope && scope[:joins]) all_joins.delete_if { |j| scope_joins.include?(j) } unless scope_joins.blank? all_joins.size <= 1 ? all_joins.first : all_joins end =begin def joins auto_joins = joins_to_sql_array((safe_to_array(conditions.auto_joins) + safe_to_array(order_by_auto_joins) + safe_to_array(priority_order_by_auto_joins)).uniq) scope_joins = joins_to_sql_array(scope && scope[:joins]) include_joins = joins_to_sql_array(include) #raise auto_joins.inspect if conditions.respond_to?(:dogs) && conditions.dogs.id == 1 auto_joins.delete_if { |auto_join| scope_joins.include?(auto_join) || include_joins.include?(auto_join) } #raise auto_joins.inspect if auto_joins.size > 1 end =end def limit=(value) @set_limit = true @limit = value.blank? || value == 0 ? nil : value.to_i end def limit @limit ||= Config.per_page if !acting_as_filter? && !@set_limit @limit end def offset=(value) @offset = value.blank? ? nil : value.to_i end def options=(values) return unless values.is_a?(Hash) values.symbolize_keys.fast_assert_valid_keys(OPTIONS) values.each { |key, value| send("#{key}=", value) } end # Sanitizes everything down into options ActiveRecord::Base.find can understand def sanitize(searching = true) find_options = {} (searching ? AR_FIND_OPTIONS : AR_CALCULATIONS_OPTIONS).each do |find_option| value = send(find_option) next if value.blank? find_options[find_option] = value end find_options end def scope @scope ||= {} end private def joins_to_sql_array(joins) unless array_of_strings?(safe_to_array(joins)) join_dependency = ::ActiveRecord::Associations::ClassMethods::JoinDependency.new(klass, joins, nil) join_dependency.join_associations.collect { |assoc| assoc.association_join } else joins.is_a?(Array) ? joins : safe_to_array(joins) end end def safe_to_array(o) case o when NilClass [] when Array o else [o] end end def array_of_strings?(o) o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)} end end end end