Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Simplify condition creation and leave method_missing alone

  • Loading branch information...
commit 3306d9b5c987428fb12579bbca3bf53807f46d4c 1 parent 443decd
@binarylogic authored
View
14 lib/searchlogic/active_record/named_scope_tools.rb
@@ -14,14 +14,14 @@ module NamedScopeTools
# method.
def named_scope_options(name)
key = scopes.key?(name.to_sym) ? name.to_sym : condition_scope_name(name)
-
- if key
+
+ if key && scopes[key]
eval("options", scopes[key].binding)
else
nil
end
end
-
+
# The arity for a named scope's proc is important, because we use the arity
# to determine if the condition should be ignored when calling the search method.
# If the condition is false and the arity is 0, then we skip it all together. Ex:
@@ -37,7 +37,7 @@ def named_scope_arity(name)
options = named_scope_options(name)
options.respond_to?(:arity) ? options.arity : nil
end
-
+
# When searchlogic calls a named_scope on a foreigh model it will execute that scope and then call scope(:find).
# When we get these options we want this to be in an exclusive scope, especially if we are calling a condition on
# the same originating model:
@@ -58,7 +58,7 @@ def in_searchlogic_delegation(&block)
with_exclusive_scope(&block)
Thread.current["searchlogic_delegation"] = old
end
-
+
# A convenience method for creating inner join sql to that your inner joins
# are consistent with how Active Record creates them. Basically a tool for
# you to use when writing your own named scopes. This way you know for sure
@@ -71,7 +71,7 @@ def in_searchlogic_delegation(&block)
def inner_joins(association_name)
::ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, association_name, nil).join_associations.collect { |assoc| assoc.association_join }
end
-
+
# A convenience methods to create a join on a polymorphic associations target.
# Ex:
#
@@ -91,7 +91,7 @@ def inner_polymorphic_join(target, options = {})
"INNER JOIN #{options[:target_table]} ON #{options[:target_table]}.id = #{options[:on_table_name]}.#{options[:as]}_id AND " +
"#{options[:on_table_name]}.#{options[:as]}_type = #{postgres ? "E" : ""}'#{target.to_s.camelize}'"
end
-
+
# See inner_joins. Does the same thing except creates LEFT OUTER joins.
def left_outer_joins(association_name)
::ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, association_name, nil).join_associations.collect { |assoc| assoc.association_join }
View
23 lib/searchlogic/named_scopes/alias_scope.rb
@@ -45,23 +45,24 @@ def alias_scope(name, options = nil)
end
end
alias_method :scope_procedure, :alias_scope
-
- def alias_scopes # :nodoc:
- read_inheritable_attribute(:alias_scopes) || write_inheritable_attribute(:alias_scopes, {})
- end
-
- def alias_scope?(name) # :nodoc:
- return false if name.blank?
- alias_scopes.key?(name.to_sym)
- end
-
+
def condition?(name) # :nodoc:
super || alias_scope?(name)
end
-
+
def named_scope_options(name) # :nodoc:
super || alias_scopes[name.to_sym]
end
+
+ private
+ def alias_scopes # :nodoc:
+ read_inheritable_attribute(:alias_scopes) || write_inheritable_attribute(:alias_scopes, {})
+ end
+
+ def alias_scope?(name) # :nodoc:
+ return false if name.blank?
+ alias_scopes.key?(name.to_sym)
+ end
end
end
end
View
19 lib/searchlogic/named_scopes/association_conditions.rb
@@ -11,10 +11,9 @@ def association_condition?(name)
!association_condition_details(name).nil? unless name.to_s.downcase.match("_or_")
end
- def method_missing(name, *args, &block)
+ def create_condition(name)
if !local_condition?(name) && details = association_condition_details(name)
- create_association_condition(details[:association], details[:condition], args, details[:poly_class])
- send(name, *args)
+ create_association_condition(details[:association], details[:condition], details[:poly_class])
else
super
end
@@ -51,27 +50,27 @@ def association_condition_details(name, last_condition = nil)
end
end
- def create_association_condition(association, condition_name, args, poly_class = nil)
+ def create_association_condition(association, condition_name, poly_class = nil)
name = [association.name, poly_class && "#{poly_class.name.underscore}_type", condition_name].compact.join("_")
- named_scope(name, association_condition_options(association, condition_name, args, poly_class))
+ named_scope(name, association_condition_options(association, condition_name, poly_class))
end
- def association_condition_options(association, association_condition, args, poly_class = nil)
+ def association_condition_options(association, association_condition, poly_class = nil)
klass = poly_class ? poly_class : association.klass
- scope = klass.send(association_condition, *args)
- scope_options = klass.named_scope_options(association_condition)
+ raise ArgumentError.new("The #{klass} class does not respond to the #{association_condition} scope") if !klass.respond_to?(association_condition)
arity = klass.named_scope_arity(association_condition)
if !arity || arity == 0
# The underlying condition doesn't require any parameters, so let's just create a simple
# named scope that is based on a hash.
options = {}
- in_searchlogic_delegation { options = scope.scope(:find) }
+ in_searchlogic_delegation { options = klass.send(association_condition).scope(:find) }
prepare_named_scope_options(options, association, poly_class)
options
else
- proc_args = arity_args(arity)
+ scope_options = klass.named_scope_options(association_condition)
scope_options = scope_options.respond_to?(:searchlogic_options) ? scope_options.searchlogic_options.clone : {}
+ proc_args = arity_args(arity)
arg_type = scope_options.delete(:type) || :string
eval <<-"end_eval"
View
17 lib/searchlogic/named_scopes/association_ordering.rb
@@ -13,21 +13,20 @@ module AssociationOrdering
def condition?(name) # :nodoc:
super || association_ordering_condition?(name)
end
-
+
private
def association_ordering_condition?(name)
!association_ordering_condition_details(name).nil?
end
-
- def method_missing(name, *args, &block)
+
+ def create_condition(name)
if details = association_ordering_condition_details(name)
- create_association_ordering_condition(details[:association], details[:order_as], details[:condition], args)
- send(name, *args)
+ create_association_ordering_condition(details[:association], details[:order_as], details[:condition])
else
super
end
end
-
+
def association_ordering_condition_details(name)
associations = reflect_on_all_associations
association_names = associations.collect { |assoc| assoc.name }
@@ -35,8 +34,8 @@ def association_ordering_condition_details(name)
{:order_as => $1, :association => associations.find { |a| a.name == $2.to_sym }, :condition => $3}
end
end
-
- def create_association_ordering_condition(association, order_as, condition, args)
+
+ def create_association_ordering_condition(association, order_as, condition)
cond = condition
poly_class = nil
if condition =~ /^(\w+)_type_(\w+)$/
@@ -44,7 +43,7 @@ def create_association_ordering_condition(association, order_as, condition, args
cond = $2
poly_class = poly_type.camelcase.constantize if poly_type
end
- named_scope("#{order_as}_by_#{association.name}_#{condition}", association_condition_options(association, "#{order_as}_by_#{cond}", args, poly_class))
+ named_scope("#{order_as}_by_#{association.name}_#{condition}", association_condition_options(association, "#{order_as}_by_#{cond}", poly_class))
end
end
end
View
40 lib/searchlogic/named_scopes/conditions.rb
@@ -57,6 +57,14 @@ def condition?(name)
local_condition?(name)
end
+ # We want to return true for any conditions that can be called, and while we're at it. We might as well
+ # create the condition so we don't have to do it again.
+ def respond_to?(*args)
+ name = args.first
+ result = super
+ (!result && self != ::ActiveRecord::Base && !create_condition(name).blank?) || result
+ end
+
private
def local_condition?(name)
return false if name.blank?
@@ -70,13 +78,8 @@ def boolean_condition?(name)
end
def method_missing(name, *args, &block)
- if details = condition_details(name)
- create_condition(details[:column], details[:condition], args)
- send(name, *args)
- elsif boolean_condition?(name)
- column = name.to_s.gsub(/^not_/, "")
- named_scope name, :conditions => {column => (name.to_s =~ /^not_/).nil?}
- send(name)
+ if create_condition(name)
+ send(name, *args, &block)
else
super
end
@@ -91,11 +94,17 @@ def condition_details(method_name)
end
end
- def create_condition(column_name, condition, args)
- if PRIMARY_CONDITIONS.include?(condition.to_sym)
- create_primary_condition(column_name, condition)
- elsif ALIAS_CONDITIONS.include?(condition.to_sym)
- create_alias_condition(column_name, condition, args)
+ def create_condition(name)
+ if details = condition_details(name)
+ if PRIMARY_CONDITIONS.include?(details[:condition].to_sym)
+ create_primary_condition(details[:column], details[:condition])
+ elsif ALIAS_CONDITIONS.include?(details[:condition].to_sym)
+ create_alias_condition(details[:column], details[:condition])
+ end
+
+ elsif boolean_condition?(name)
+ column = name.to_s.gsub(/^not_/, "")
+ named_scope name, :conditions => {column => (name.to_s =~ /^not_/).nil?}
end
end
@@ -204,12 +213,13 @@ def value_with_modifier(value, modifier)
end
end
- def create_alias_condition(column_name, condition, args)
+ def create_alias_condition(column_name, condition)
primary_condition = primary_condition(condition)
alias_name = "#{column_name}_#{condition}"
primary_name = "#{column_name}_#{primary_condition}"
- send(primary_name, *args) # go back to method_missing and make sure we create the method
- (class << self; self; end).class_eval { alias_method alias_name, primary_name }
+ if respond_to?(primary_name)
+ (class << self; self; end).class_eval { alias_method alias_name, primary_name }
+ end
end
# Returns the primary condition for the given alias. Ex:
View
32 lib/searchlogic/named_scopes/or_conditions.rb
@@ -5,30 +5,30 @@ module NamedScopes
module OrConditions
class NoConditionSpecifiedError < StandardError; end
class UnknownConditionError < StandardError; end
-
+
def condition?(name) # :nodoc:
super || or_condition?(name)
end
-
+
def named_scope_options(name) # :nodoc:
super || super(or_conditions(name).try(:join, "_or_"))
end
-
+
private
def or_condition?(name)
!or_conditions(name).nil?
end
-
- def method_missing(name, *args, &block)
+
+ def create_condition(name)
if conditions = or_conditions(name)
- create_or_condition(conditions, args)
- (class << self; self; end).class_eval { alias_method name, conditions.join("_or_") } if !respond_to?(name)
- send(name, *args)
+ create_or_condition(conditions)
+ alias_name = conditions.join("_or_")
+ (class << self; self; end).class_eval { alias_method name, conditions.join("_or_") } if name != alias_name
else
super
end
end
-
+
def or_conditions(name)
# First determine if we should even work on the name, we want to be as quick as possible
# with this.
@@ -41,7 +41,7 @@ def or_conditions(name)
end
end
end
-
+
def split_or_condition(name)
parts = name.to_s.split("_or_")
new_parts = []
@@ -54,7 +54,7 @@ def split_or_condition(name)
end
new_parts
end
-
+
# The purpose of this method is to convert the method name parts into actual condition names.
#
# Example:
@@ -90,13 +90,13 @@ def interpolate_or_conditions(parts)
raise NoConditionSpecifiedError.new("The '#{part}' column doesn't know which condition to use, if you use an exact column " +
"name you need to specify a condition sometime after (ex: id_or_created_at_lt), where id would use the 'lt' condition.")
end
-
+
conditions << "#{part}_#{last_condition}"
else
raise UnknownConditionError.new("The condition '#{part}' is not a valid condition, we could not find any scopes that match this.")
end
end
-
+
conditions.reverse
end
@@ -116,8 +116,8 @@ def full_association_path(part, last_condition, given_assoc)
end
{ :path => path, :column => part, :condition => last_condition }
end
-
- def create_or_condition(scopes, args)
+
+ def create_or_condition(scopes)
scopes_options = scopes.collect { |scope, *args| send(scope, *args).proxy_options }
# We're using first scope to determine column's type
scope = named_scope_options(scopes.first)
@@ -126,7 +126,7 @@ def create_or_condition(scopes, args)
merge_scopes_with_or(scopes.collect { |scope| clone.send(scope, *args) })
}
end
-
+
def merge_scopes_with_or(scopes)
scopes_options = scopes.collect { |scope| scope.scope(:find) }
conditions = scopes_options.reject { |o| o[:conditions].nil? }.collect { |o| sanitize_sql(o[:conditions]) }
View
12 lib/searchlogic/named_scopes/ordering.rb
@@ -10,27 +10,25 @@ module Ordering
def condition?(name) # :nodoc:
super || ordering_condition?(name)
end
-
+
private
def ordering_condition?(name) # :nodoc:
!ordering_condition_details(name).nil?
end
-
- def method_missing(name, *args, &block)
+
+ def create_condition(name)
if name == :order
named_scope name, lambda { |scope_name|
return {} if !condition?(scope_name)
send(scope_name).proxy_options
}
- send(name, *args)
elsif details = ordering_condition_details(name)
create_ordering_conditions(details[:column])
- send(name, *args)
else
super
end
end
-
+
def ordering_condition_details(name)
if name.to_s =~ /^(ascend|descend)_by_(#{column_names.join("|")})$/
{:order_as => $1, :column => $2}
@@ -38,7 +36,7 @@ def ordering_condition_details(name)
{}
end
end
-
+
def create_ordering_conditions(column)
named_scope("ascend_by_#{column}".to_sym, {:order => "#{table_name}.#{column} ASC"})
named_scope("descend_by_#{column}".to_sym, {:order => "#{table_name}.#{column} DESC"})
View
2  spec/searchlogic/named_scopes/alias_scope_spec.rb
@@ -18,6 +18,6 @@
end
it "should inherit alias scopes from superclasses" do
- Class.new(User).alias_scope?("username_has").should be_true
+ Class.new(User).condition?("username_has").should be_true
end
end
View
9 spec/searchlogic/named_scopes/association_conditions_spec.rb
@@ -209,5 +209,12 @@
user.orders.count.should == 1
user.orders.shipped_on_not_null.shipped_on_greater_than(2.days.ago).count.should == 1
end
-
+
+ it "should allow chained dynamic scopes without losing association scope conditions" do
+ user = User.create
+ order1 = Order.create :user => user, :shipped_on => Time.now, :total => 2
+ order2 = Order.create :shipped_on => Time.now, :total => 2
+ user.orders.id_equals(order1.id).count.should == 1
+ user.orders.id_equals(order1.id).total_equals(2).count.should == 1
+ end
end
View
6 spec/searchlogic/named_scopes/conditions_spec.rb
@@ -2,8 +2,12 @@
describe Searchlogic::NamedScopes::Conditions do
it "should be dynamically created and then cached" do
- User.should_not respond_to(:age_less_than)
+ User.scopes.key?(:age_less_than).should == false
User.age_less_than(5)
+ User.scopes.key?(:age_less_than).should == true
+ end
+
+ it "should respond to the scope" do
User.should respond_to(:age_less_than)
end
View
6 spec/searchlogic/named_scopes/ordering_spec.rb
@@ -1,12 +1,6 @@
require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper")
describe Searchlogic::NamedScopes::Ordering do
- it "should be dynamically created and then cached" do
- User.should_not respond_to(:ascend_by_username)
- User.ascend_by_username
- User.should respond_to(:ascend_by_username)
- end
-
it "should have ascending" do
%w(bjohnson thunt).each { |username| User.create(:username => username) }
User.ascend_by_username.all.should == User.all(:order => "username ASC")

0 comments on commit 3306d9b

Please sign in to comment.
Something went wrong with that request. Please try again.