diff --git a/Rakefile b/Rakefile index 28c980e7..20a0ebfd 100644 --- a/Rakefile +++ b/Rakefile @@ -21,7 +21,12 @@ end require 'spec/rake/spectask' Spec::Rake::SpecTask.new(:spec) do |spec| spec.libs << 'lib' << 'spec' - spec.spec_files = FileList['spec/**/*_spec.rb'] + spec.spec_files = FileList[ + #'spec/**/conditions_spec.rb', + #'spec/**/ordering_conditions_spec.rb', + 'spec/**/association_conditions_spec.rb' + #'spec/**/*_spec.rb' + ] end Spec::Rake::SpecTask.new(:rcov) do |spec| diff --git a/lib/searchlogic.rb b/lib/searchlogic.rb index 3726db2f..845a8e4c 100644 --- a/lib/searchlogic.rb +++ b/lib/searchlogic.rb @@ -11,6 +11,7 @@ require "searchlogic/named_scopes/or_conditions" require "searchlogic/search" +ActiveRecord::Relation.send(:include, Searchlogic::CoreExt::Proc) Proc.send(:include, Searchlogic::CoreExt::Proc) Object.send(:include, Searchlogic::CoreExt::Object) @@ -28,9 +29,10 @@ class AssociationProxy end m = Searchlogic::NamedScopes -[Searchlogic::ActiveRecord::NamedScopeTools, +[ + Searchlogic::ActiveRecord::NamedScopeTools, m::Conditions, -# m::AssociationConditions, + m::AssociationConditions, # m::AssociationOrdering, m::Ordering, # m::AliasScope diff --git a/lib/searchlogic/active_record/named_scope_tools.rb b/lib/searchlogic/active_record/named_scope_tools.rb index 7a5d1779..806cba13 100644 --- a/lib/searchlogic/active_record/named_scope_tools.rb +++ b/lib/searchlogic/active_record/named_scope_tools.rb @@ -16,6 +16,7 @@ def named_scope_options(name) key = scopes.key?(name.to_sym) ? name.to_sym : condition_scope_name(name) if key + #TODO: recover the find options from relation eval("options", scopes[key].binding) else nil diff --git a/lib/searchlogic/core_ext/proc.rb b/lib/searchlogic/core_ext/proc.rb index 15bb7d39..4486ad88 100644 --- a/lib/searchlogic/core_ext/proc.rb +++ b/lib/searchlogic/core_ext/proc.rb @@ -14,4 +14,4 @@ def searchlogic_options end end end -end \ No newline at end of file +end diff --git a/lib/searchlogic/named_scopes/association_conditions.rb b/lib/searchlogic/named_scopes/association_conditions.rb index f923798b..bf0db95c 100644 --- a/lib/searchlogic/named_scopes/association_conditions.rb +++ b/lib/searchlogic/named_scopes/association_conditions.rb @@ -5,6 +5,22 @@ module AssociationConditions def condition?(name) # :nodoc: super || association_condition?(name) end + + def _resolve_deep_association_conditions(condition_name, args) + if local_condition?(condition_name) + [self.table_name.to_sym, self.send(condition_name, *args)] + elsif details = association_condition_details(condition_name) + resolution = details[:association].klass._resolve_deep_association_conditions(details[:condition], args) + return nil unless resolution #method did not resolve + this_table = self.table_name.to_sym + join_list = resolution.first.nil? ? this_table : {this_table=>resolution.first} + [join_list, resolution.last] + else #this method did not resolve + nil + end + end + + private def association_condition?(name) @@ -13,13 +29,25 @@ def association_condition?(name) def method_missing(name, *args, &block) if !local_condition?(name) && details = association_condition_details(name) - create_association_condition(details[:association], details[:condition], args, details[:poly_class]) + create_scope_for_association(details[:association], details[:condition], args, details[:poly_class]) send(name, *args) else super end end - + + def create_scope_for_association(association, condition_name, args, poly_class = nil) + resolution = association.klass._resolve_deep_association_conditions(condition_name, args) + return unless resolution + + final_condition = resolution.last + join_hierarchy = resolution.first + algebra = final_condition.joins(join_hierarchy) + + name = [association.name, poly_class && "#{poly_class.name.underscore}_type", condition_name].compact.join("_") + scope(name, algebra) + end + def association_condition_details(name, last_condition = nil) non_poly_assocs = reflect_on_all_associations.reject { |assoc| assoc.options[:polymorphic] }.sort { |a, b| b.name.to_s.size <=> a.name.to_s.size } poly_assocs = reflect_on_all_associations.reject { |assoc| !assoc.options[:polymorphic] }.sort { |a, b| b.name.to_s.size <=> a.name.to_s.size } @@ -51,81 +79,81 @@ def association_condition_details(name, last_condition = nil) end end - def create_association_condition(association, condition_name, args, poly_class = nil) - name = [association.name, poly_class && "#{poly_class.name.underscore}_type", condition_name].compact.join("_") - scope(name, association_condition_options(association, condition_name, args, poly_class)) - end - def association_condition_options(association, association_condition, args, poly_class = nil) - klass = poly_class ? poly_class : association.klass - scope = klass.send(association_condition, *args) - scope_options = klass.named_scope_options(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) } - prepare_named_scope_options(options, association, poly_class) - options - else - proc_args = arity_args(arity) - arg_type = (scope_options.respond_to?(:searchlogic_options) && scope_options.searchlogic_options[:type]) || :string - - eval <<-"end_eval" - searchlogic_lambda(:#{arg_type}) { |#{proc_args.join(",")}| - options = {} - - in_searchlogic_delegation do - scope = klass.send(association_condition, #{proc_args.join(",")}) - options = scope.scope(:find) if scope - end - - - prepare_named_scope_options(options, association, poly_class) - options - } - end_eval - end - end +# def association_condition_options(association, association_condition, args, poly_class = nil) +# klass = poly_class ? poly_class : association.klass +# relation = klass.send(association_condition, *args) +# scope_options = nil #klass.named_scope_options(association_condition) +# arity = -1 #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 = relation.scope(:find) } +# prepare_named_scope_options(options, association, poly_class) +# options +# else +# proc_args = arity_args(arity) +# arg_type = :string #(scope_options.respond_to?(:searchlogic_options) && scope_options.searchlogic_options[:type]) || :string +# +# eval <<-"end_eval" +# searchlogic_lambda(:#{arg_type}) { |#{proc_args.join(",")}| +# options = {} +# +# in_searchlogic_delegation do +# relation = klass.send(association_condition, #{proc_args.join(",")}) +# options = {:conditions=>"users.username LIKE '%joe%'"} #relation.scope(:find) if relation +# end +# +# prepare_named_scope_options(options, association, poly_class) +# options +# } +# end_eval +# end +# end # Used to match the new scopes parameters to the underlying scope. This way we can disguise the # new scope as best as possible instead of taking the easy way out and using *args. - def arity_args(arity) - args = [] - if arity > 0 - arity.times { |i| args << "arg#{i}" } - else - positive_arity = arity * -1 - positive_arity.times do |i| - if i == (positive_arity - 1) - args << "*arg#{i}" - else - args << "arg#{i}" - end - end - end - args - end - - def prepare_named_scope_options(options, association, poly_class = nil) - options.delete(:readonly) # AR likes to set :readonly to true when using the :joins option, we don't want that - - klass = poly_class || association.klass - # sanitize the conditions locally so we get the right table name, otherwise the conditions will be evaluated on the original model - options[:conditions] = klass.sanitize_sql_for_conditions(options[:conditions]) if options[:conditions].is_a?(Hash) - - poly_join = poly_class && inner_polymorphic_join(poly_class.name.underscore, :as => association.name) - - if options[:joins].is_a?(String) || array_of_strings?(options[:joins]) - options[:joins] = [poly_class ? poly_join : inner_joins(association.name), options[:joins]].flatten - elsif poly_class - options[:joins] = options[:joins].blank? ? poly_join : ([poly_join] + klass.inner_joins(options[:joins])) - else - options[:joins] = options[:joins].blank? ? association.name : {association.name => options[:joins]} - end - end +# def arity_args(arity) +# args = [] +# if arity > 0 +# arity.times { |i| args << "arg#{i}" } +# else +# positive_arity = arity * -1 +# positive_arity.times do |i| +# if i == (positive_arity - 1) +# args << "*arg#{i}" +# else +# args << "arg#{i}" +# end +# end +# end +# args +# end + +# #ADDED: this was removed from AR::Base ver2.x, redefined for use in prepare_named_scope_options +# def array_of_strings?(o) +# o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)} +# end +# +# def prepare_named_scope_options(options, association, poly_class = nil) +# options.delete(:readonly) # AR likes to set :readonly to true when using the :joins option, we don't want that +# +# klass = poly_class || association.klass +# # sanitize the conditions locally so we get the right table name, otherwise the conditions will be evaluated on the original model +# options[:conditions] = klass.sanitize_sql_for_conditions(options[:conditions]) if options[:conditions].is_a?(Hash) +# +# poly_join = poly_class && inner_polymorphic_join(poly_class.name.underscore, :as => association.name) +# +# if options[:joins].is_a?(String) || array_of_strings?(options[:joins]) +# options[:joins] = [poly_class ? poly_join : inner_joins(association.name), options[:joins]].flatten +# elsif poly_class +# options[:joins] = options[:joins].blank? ? poly_join : ([poly_join] + klass.inner_joins(options[:joins])) +# else +# options[:joins] = options[:joins].blank? ? association.name : {association.name => options[:joins]} +# end +# end end end end diff --git a/lib/searchlogic/named_scopes/conditions.rb b/lib/searchlogic/named_scopes/conditions.rb index 2dc7aa53..a6a0db3e 100644 --- a/lib/searchlogic/named_scopes/conditions.rb +++ b/lib/searchlogic/named_scopes/conditions.rb @@ -71,7 +71,7 @@ def boolean_condition?(name) def method_missing(name, *args, &block) if details = condition_details(name) - create_condition(details[:column], details[:condition], args) + create_scope(details[:column], details[:condition], args) send(name, *args) elsif boolean_condition?(name) column = name.to_s.gsub(/^not_/, "") @@ -84,6 +84,7 @@ def method_missing(name, *args, &block) def condition_details(method_name) + #TODO: cache these column_name_matcher = column_names.join("|") conditions_matcher = (PRIMARY_CONDITIONS + ALIAS_CONDITIONS).join("|") @@ -92,15 +93,15 @@ def condition_details(method_name) end end - def create_condition(column, condition, args) + def create_scope(column, condition, args) if PRIMARY_CONDITIONS.include?(condition.to_sym) - create_primary_condition(column, condition) + create_primary_scope(column, condition) elsif ALIAS_CONDITIONS.include?(condition.to_sym) - create_alias_condition(column, condition, args) + create_aliased_scope(column, condition, args) end end - def create_primary_condition(column, condition) + def create_primary_scope(column, condition) column_type = columns_hash[column.to_s].type skip_conversion = skip_time_zone_conversion_for_attributes.include?(columns_hash[column.to_s].name.to_sym) match_keyword = ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL" ? "ILIKE" : "LIKE" @@ -190,7 +191,7 @@ def value_with_modifier(value, modifier) end end - def create_alias_condition(column, condition, args) + def create_aliased_scope(column, condition, args) primary_condition = primary_condition(condition) alias_name = "#{column}_#{condition}" primary_name = "#{column}_#{primary_condition}" diff --git a/spec/searchlogic/named_scopes/association_conditions_spec.rb b/spec/searchlogic/named_scopes/association_conditions_spec.rb index 0981aa38..ecc08a63 100644 --- a/spec/searchlogic/named_scopes/association_conditions_spec.rb +++ b/spec/searchlogic/named_scopes/association_conditions_spec.rb @@ -2,197 +2,199 @@ describe Searchlogic::NamedScopes::AssociationConditions do it "should create a named scope" do - Company.users_username_like("bjohnson").proxy_options.should == User.username_like("bjohnson").proxy_options.merge(:joins => :users) + #Company.users_username_like("bjohnson").to_sql.squeeze.should == User.username_like("bjohnson").to_sql.squeeze.merge(:joins => :users) + Company.users_username_like("bjohnson").to_sql.squeeze.should == Company.joins(:users).where("users.username LIKE ?", "%bjohnson%").to_sql.squeeze end it "should create a deep named scope" do - Company.users_orders_total_greater_than(10).proxy_options.should == Order.total_greater_than(10).proxy_options.merge(:joins => {:users => :orders}) + Company.users_orders_total_greater_than(10).to_sql.squeeze.should == Company.joins(:users=>:orders).where('orders.total > ?', 10).to_sql.squeeze end - + it "should allow the use of foreign pre-existing named scopes" do - User.named_scope :uname, lambda { |value| {:conditions => ["users.username = ?", value]} } - Company.users_uname("bjohnson").proxy_options.should == User.uname("bjohnson").proxy_options.merge(:joins => :users) + User.scope :uname, lambda { |value| {:conditions => ["users.username = ?", value]} } + Company.users_uname("bjohnson").to_sql.squeeze.should == Company.joins(:users).where("users.username = ?", "bjohnson").to_sql.squeeze end - + it "should allow the use of deep foreign pre-existing named scopes" do pending - Order.named_scope :big_id, :conditions => "orders.id > 100" - Company.users_orders_big_id.proxy_options.should == Order.big_id.proxy_options.merge(:joins => {:users => :orders}) - end - - it "should allow the use of foreign pre-existing alias scopes" do - User.alias_scope :username_has, lambda { |value| User.username_like(value) } - Company.users_username_has("bjohnson").proxy_options.should == User.username_has("bjohnson").proxy_options.merge(:joins => :users) - end - - it "should not raise errors for scopes that don't return anything" do - User.alias_scope :blank_scope, lambda { |value| } - Company.users_blank_scope("bjohnson").proxy_options.should == {:joins => :users} + condition = "orders.id > 100" + Order.scope :big_id, :conditions => condition + Company.users_orders_big_id.to_sql.squeeze.should == Company.joins(:users=>:orders).where(condition).to_sql.squeeze end - + +# it "should allow the use of foreign pre-existing alias scopes" do +# User.alias_scope :username_has, lambda { |value| User.username_like(value) } +# Company.users_username_has("bjohnson").to_sql.squeeze.should == Company.joins(:users).where("user.username LIKE ?", "%bjohnson%").to_sql.squeeze +# end + +# it "should not raise errors for scopes that don't return anything" do +# User.alias_scope :blank_scope, lambda { |value| } +# Company.users_blank_scope("bjohnson").to_sql.squeeze.should == {:joins => :users} +# end + it "should ignore polymorphic associations" do lambda { Fee.owner_created_at_gt(Time.now) }.should raise_error(NoMethodError) end - + it "should not allow named scopes on non existent association columns" do lambda { User.users_whatever_like("bjohnson") }.should raise_error(NoMethodError) end - + it "should not allow named scopes on non existent deep association columns" do lambda { User.users_orders_whatever_like("bjohnson") }.should raise_error(NoMethodError) end - + it "should allow named scopes to be called multiple times and reflect the value passed" do - Company.users_username_like("bjohnson").proxy_options.should == User.username_like("bjohnson").proxy_options.merge(:joins => :users) - Company.users_username_like("thunt").proxy_options.should == User.username_like("thunt").proxy_options.merge(:joins => :users) - end - - it "should allow deep named scopes to be called multiple times and reflect the value passed" do - Company.users_orders_total_greater_than(10).proxy_options.should == Order.total_greater_than(10).proxy_options.merge(:joins => {:users => :orders}) - Company.users_orders_total_greater_than(20).proxy_options.should == Order.total_greater_than(20).proxy_options.merge(:joins => {:users => :orders}) - end - - it "should have an arity of 1 if the underlying scope has an arity of 1" do - Company.users_orders_total_greater_than(10) - Company.named_scope_arity("users_orders_total_greater_than").should == Order.named_scope_arity("total_greater_than") - end - - it "should have an arity of nil if the underlying scope has an arity of nil" do - Company.users_orders_total_null - Company.named_scope_arity("users_orders_total_null").should == Order.named_scope_arity("total_null") - end - - it "should have an arity of -1 if the underlying scope has an arity of -1" do - Company.users_id_equals_any - Company.named_scope_arity("users_id_equals_any").should == User.named_scope_arity("id_equals_any") - end - - it "should allow aliases" do - Company.users_username_contains("bjohnson").proxy_options.should == User.username_contains("bjohnson").proxy_options.merge(:joins => :users) - end - - it "should allow deep aliases" do - Company.users_orders_total_gt(10).proxy_options.should == Order.total_gt(10).proxy_options.merge(:joins => {:users => :orders}) - end - - it "should include optional associations" do - pending # this is a problem with using inner joins and left outer joins - Company.create - company = Company.create - user = company.users.create - order = user.orders.create(:total => 20, :taxes => 3) - Company.ascend_by_users_orders_total.all.should == Company.all - end - - it "should implement exclusive scoping" do - scope = Company.users_company_name_like("name").users_company_description_like("description") - scope.scope(:find)[:joins].should == [ - "INNER JOIN \"users\" ON companies.id = users.company_id", - "INNER JOIN \"companies\" companies_users ON \"companies_users\".id = \"users\".company_id" - ] - lambda { scope.all }.should_not raise_error - end - - it "should not create the same join twice" do - scope = Company.users_orders_total_gt(10).users_orders_taxes_lt(5).ascend_by_users_orders_total - scope.scope(:find)[:joins].should == [ - "INNER JOIN \"users\" ON companies.id = users.company_id", - "INNER JOIN \"orders\" ON orders.user_id = users.id" - ] - lambda { scope.count }.should_not raise_error - end - - it "should not create the same join twice when traveling through the duplicate join" do - scope = Company.users_username_like("bjohnson").users_orders_total_gt(100) - scope.scope(:find)[:joins].should == [ - "INNER JOIN \"users\" ON companies.id = users.company_id", - "INNER JOIN \"orders\" ON orders.user_id = users.id" - ] - lambda { scope.count }.should_not raise_error - end - - it "should not create the same join twice when traveling through the deep duplicate join" do - scope = Company.users_orders_total_gt(100).users_orders_line_items_price_gt(20) - scope.scope(:find)[:joins].should == [ - "INNER JOIN \"users\" ON companies.id = users.company_id", - "INNER JOIN \"orders\" ON orders.user_id = users.id", - "INNER JOIN \"line_items\" ON line_items.order_id = orders.id" - ] - lambda { scope.all }.should_not raise_error - end - - it "should allow the use of :include when a join was created" do - company = Company.create - user = company.users.create - order = user.orders.create(:total => 20, :taxes => 3) - Company.users_orders_total_gt(10).users_orders_taxes_lt(5).ascend_by_users_orders_total.all(:include => :users).should == Company.all - end - - it "should allow the use of deep :include when a join was created" do - company = Company.create - user = company.users.create - order = user.orders.create(:total => 20, :taxes => 3) - Company.users_orders_total_gt(10).users_orders_taxes_lt(5).ascend_by_users_orders_total.all(:include => {:users => :orders}).should == Company.all - end - - it "should allow the use of :include when traveling through the duplicate join" do - company = Company.create - user = company.users.create(:username => "bjohnson") - order = user.orders.create(:total => 20, :taxes => 3) - Company.users_username_like("bjohnson").users_orders_taxes_lt(5).ascend_by_users_orders_total.all(:include => :users).should == Company.all - end - - it "should allow the use of deep :include when traveling through the duplicate join" do - company = Company.create - user = company.users.create(:username => "bjohnson") - order = user.orders.create(:total => 20, :taxes => 3) - Company.users_orders_taxes_lt(50).ascend_by_users_orders_total.all(:include => {:users => :orders}).should == Company.all - end - - it "should automatically add string joins if the association condition is using strings" do - User.named_scope(:orders_big_id, :joins => User.inner_joins(:orders)) - Company.users_orders_big_id.proxy_options.should == {:joins=>[" INNER JOIN \"users\" ON users.company_id = companies.id ", " INNER JOIN \"orders\" ON orders.user_id = users.id "]} + Company.users_username_like("thunt").should eq_scope(Company.joins(:users).where("users.username LIKE ?", "%thunt%")) + Company.users_username_like("bjohnson").should eq_scope(Company.joins(:users).where("users.username LIKE ?", "%bjohnson%")) end - it "should order the join statements ascending by the fieldnames so that we don't get double joins where the only difference is that the order of the fields is different" do - company = Company.create - user = company.users.create(:company_id => company.id) - company.users.company_id_eq(company.id).should == [user] - end - - it "should sanitize the scope on a foreign model instead of passing the raw options back to the original" do - Company.named_scope(:users_count_10, :conditions => {:users_count => 10}) - User.company_users_count_10.proxy_options.should == {:conditions => "\"companies\".\"users_count\" = 10", :joins => :company} - end - - it "should delegate to polymorphic relationships" do - Audit.auditable_user_type_name_like("ben").proxy_options.should == { - :conditions => ["users.name LIKE ?", "%ben%"], - :joins => "INNER JOIN \"users\" ON \"users\".id = \"audits\".auditable_id AND \"audits\".auditable_type = 'User'" - } - end - - it "should delegate to polymorphic relationships (with a lazy split on _type_)" do - Audit.auditable_user_type_some_type_id_like("ben").proxy_options.should == { - :conditions => ["users.some_type_id LIKE ?", "%ben%"], - :joins => "INNER JOIN \"users\" ON \"users\".id = \"audits\".auditable_id AND \"audits\".auditable_type = 'User'" - } - end - - it "should deep delegate to polymorphic relationships" do - Audit.auditable_user_type_company_name_like("company").proxy_options.should == { - :conditions => ["companies.name LIKE ?", "%company%"], - :joins => ["INNER JOIN \"users\" ON \"users\".id = \"audits\".auditable_id AND \"audits\".auditable_type = 'User'", " INNER JOIN \"companies\" ON \"companies\".id = \"users\".company_id "] - } - end - - it "should allow any on a has_many relationship" do - company1 = Company.create - user1 = company1.users.create - company2 = Company.create - user2 = company2.users.create - user3 = company2.users.create - - Company.users_id_equals_any([user2.id, user3.id]).all(:select => "DISTINCT companies.*").should == [company2] - end +# it "should allow deep named scopes to be called multiple times and reflect the value passed" do +# Company.users_orders_total_greater_than(10).to_sql.squeeze.should == Order.total_greater_than(10).to_sql.squeeze.merge(:joins => {:users => :orders}) +# Company.users_orders_total_greater_than(20).to_sql.squeeze.should == Order.total_greater_than(20).to_sql.squeeze.merge(:joins => {:users => :orders}) +# end +# +# it "should have an arity of 1 if the underlying scope has an arity of 1" do +# Company.users_orders_total_greater_than(10) +# Company.named_scope_arity("users_orders_total_greater_than").should == Order.named_scope_arity("total_greater_than") +# end +# +# it "should have an arity of nil if the underlying scope has an arity of nil" do +# Company.users_orders_total_null +# Company.named_scope_arity("users_orders_total_null").should == Order.named_scope_arity("total_null") +# end +# +# it "should have an arity of -1 if the underlying scope has an arity of -1" do +# Company.users_id_equals_any +# Company.named_scope_arity("users_id_equals_any").should == User.named_scope_arity("id_equals_any") +# end +# +# it "should allow aliases" do +# Company.users_username_contains("bjohnson").to_sql.squeeze.should == User.username_contains("bjohnson").to_sql.squeeze.merge(:joins => :users) +# end +# +# it "should allow deep aliases" do +# Company.users_orders_total_gt(10).to_sql.squeeze.should == Order.total_gt(10).to_sql.squeeze.merge(:joins => {:users => :orders}) +# end +# +# it "should include optional associations" do +# pending # this is a problem with using inner joins and left outer joins +# Company.create +# company = Company.create +# user = company.users.create +# order = user.orders.create(:total => 20, :taxes => 3) +# Company.ascend_by_users_orders_total.all.should == Company.all +# end +# +# it "should implement exclusive scoping" do +# scope = Company.users_company_name_like("name").users_company_description_like("description") +# scope.scope(:find)[:joins].should == [ +# "INNER JOIN \"users\" ON companies.id = users.company_id", +# "INNER JOIN \"companies\" companies_users ON \"companies_users\".id = \"users\".company_id" +# ] +# lambda { scope.all }.should_not raise_error +# end +# +# it "should not create the same join twice" do +# scope = Company.users_orders_total_gt(10).users_orders_taxes_lt(5).ascend_by_users_orders_total +# scope.scope(:find)[:joins].should == [ +# "INNER JOIN \"users\" ON companies.id = users.company_id", +# "INNER JOIN \"orders\" ON orders.user_id = users.id" +# ] +# lambda { scope.count }.should_not raise_error +# end +# +# it "should not create the same join twice when traveling through the duplicate join" do +# scope = Company.users_username_like("bjohnson").users_orders_total_gt(100) +# scope.scope(:find)[:joins].should == [ +# "INNER JOIN \"users\" ON companies.id = users.company_id", +# "INNER JOIN \"orders\" ON orders.user_id = users.id" +# ] +# lambda { scope.count }.should_not raise_error +# end +# +# it "should not create the same join twice when traveling through the deep duplicate join" do +# scope = Company.users_orders_total_gt(100).users_orders_line_items_price_gt(20) +# scope.scope(:find)[:joins].should == [ +# "INNER JOIN \"users\" ON companies.id = users.company_id", +# "INNER JOIN \"orders\" ON orders.user_id = users.id", +# "INNER JOIN \"line_items\" ON line_items.order_id = orders.id" +# ] +# lambda { scope.all }.should_not raise_error +# end +# +# it "should allow the use of :include when a join was created" do +# company = Company.create +# user = company.users.create +# order = user.orders.create(:total => 20, :taxes => 3) +# Company.users_orders_total_gt(10).users_orders_taxes_lt(5).ascend_by_users_orders_total.all(:include => :users).should == Company.all +# end +# +# it "should allow the use of deep :include when a join was created" do +# company = Company.create +# user = company.users.create +# order = user.orders.create(:total => 20, :taxes => 3) +# Company.users_orders_total_gt(10).users_orders_taxes_lt(5).ascend_by_users_orders_total.all(:include => {:users => :orders}).should == Company.all +# end +# +# it "should allow the use of :include when traveling through the duplicate join" do +# company = Company.create +# user = company.users.create(:username => "bjohnson") +# order = user.orders.create(:total => 20, :taxes => 3) +# Company.users_username_like("bjohnson").users_orders_taxes_lt(5).ascend_by_users_orders_total.all(:include => :users).should == Company.all +# end +# +# it "should allow the use of deep :include when traveling through the duplicate join" do +# company = Company.create +# user = company.users.create(:username => "bjohnson") +# order = user.orders.create(:total => 20, :taxes => 3) +# Company.users_orders_taxes_lt(50).ascend_by_users_orders_total.all(:include => {:users => :orders}).should == Company.all +# end +# +# it "should automatically add string joins if the association condition is using strings" do +# User.named_scope(:orders_big_id, :joins => User.inner_joins(:orders)) +# Company.users_orders_big_id.to_sql.squeeze.should == {:joins=>[" INNER JOIN \"users\" ON users.company_id = companies.id ", " INNER JOIN \"orders\" ON orders.user_id = users.id "]} +# end +# +# it "should order the join statements ascending by the fieldnames so that we don't get double joins where the only difference is that the order of the fields is different" do +# company = Company.create +# user = company.users.create(:company_id => company.id) +# company.users.company_id_eq(company.id).should == [user] +# end +# +# it "should sanitize the scope on a foreign model instead of passing the raw options back to the original" do +# Company.named_scope(:users_count_10, :conditions => {:users_count => 10}) +# User.company_users_count_10.to_sql.squeeze.should == {:conditions => "\"companies\".\"users_count\" = 10", :joins => :company} +# end +# +# it "should delegate to polymorphic relationships" do +# Audit.auditable_user_type_name_like("ben").to_sql.squeeze.should == { +# :conditions => ["users.name LIKE ?", "%ben%"], +# :joins => "INNER JOIN \"users\" ON \"users\".id = \"audits\".auditable_id AND \"audits\".auditable_type = 'User'" +# } +# end +# +# it "should delegate to polymorphic relationships (with a lazy split on _type_)" do +# Audit.auditable_user_type_some_type_id_like("ben").to_sql.squeeze.should == { +# :conditions => ["users.some_type_id LIKE ?", "%ben%"], +# :joins => "INNER JOIN \"users\" ON \"users\".id = \"audits\".auditable_id AND \"audits\".auditable_type = 'User'" +# } +# end +# +# it "should deep delegate to polymorphic relationships" do +# Audit.auditable_user_type_company_name_like("company").to_sql.squeeze.should == { +# :conditions => ["companies.name LIKE ?", "%company%"], +# :joins => ["INNER JOIN \"users\" ON \"users\".id = \"audits\".auditable_id AND \"audits\".auditable_type = 'User'", " INNER JOIN \"companies\" ON \"companies\".id = \"users\".company_id "] +# } +# end +# +# it "should allow any on a has_many relationship" do +# company1 = Company.create +# user1 = company1.users.create +# company2 = Company.create +# user2 = company2.users.create +# user3 = company2.users.create +# +# Company.users_id_equals_any([user2.id, user3.id]).all(:select => "DISTINCT companies.*").should == [company2] +# end end \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 356b024a..4cb9634f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -4,6 +4,18 @@ require 'active_record' require 'active_support/core_ext' if ActiveRecord::VERSION::MAJOR > 2 +Spec::Matchers.define :eq_scope do |relation2| + match do |relation1| + relation1.to_sql.squeeze == relation2.to_sql.squeeze + end + failure_message_for_should do |relation1| + "expected\n#{relation2.to_sql.squeeze}\nto equal\n#{relation1.to_sql.squeeze}" + end + failure_message_for_should_not do |relation1| + "expected\n#{relation2.to_sql.squeeze}\nto not equal\n#{relation1.to_sql.squeeze}" + end +end + ENV['TZ'] = 'UTC' Time.zone = 'Eastern Time (US & Canada)'