Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #10012: ar_joins_fixed_for_10011.diff

File ar_joins_fixed_for_10011.diff, 25.6 kB (added by RubyRedRick, 1 year ago)
  • activerecord/test/associations/ar_joins_test.rb

    old new  
     1require 'abstract_unit' 
     2require 'fixtures/post' 
     3require 'fixtures/comment' 
     4require 'fixtures/author' 
     5require 'fixtures/category' 
     6require 'fixtures/categorization' 
     7require 'fixtures/company' 
     8require 'fixtures/topic' 
     9require 'fixtures/reply' 
     10require 'fixtures/developer' 
     11require 'fixtures/project' 
     12 
     13class ArJoinsTest < Test::Unit::TestCase 
     14  fixtures :authors, :posts, :comments, :categories, :categories_posts, :people, 
     15           :developers, :projects, :developers_projects, 
     16           :categorizations, :companies, :accounts, :topics 
     17 
     18  def test_ar_joins 
     19    authors = Author.find(:all, :joins => :posts, :conditions => ['posts.type = ?', "Post"]) 
     20    assert_not_equal(0 , authors.length) 
     21    authors.each do |author| 
     22      assert !(author.send(:instance_variables).include? "@posts") 
     23      assert(!author.readonly?, "non-string join value produced read-only result.") 
     24    end 
     25  end 
     26 
     27  def test_ar_joins_with_cascaded_two_levels 
     28    authors = Author.find(:all, :joins=>{:posts=>:comments}) 
     29    assert_equal(2, authors.length) 
     30    authors.each do |author| 
     31      assert !(author.send(:instance_variables).include? "@posts") 
     32      assert(!author.readonly?, "non-string join value produced read-only result.") 
     33    end 
     34    authors = Author.find(:all, :joins=>{:posts=>:comments}, :conditions => ["comments.body = ?", "go crazy" ]) 
     35    assert_equal(1, authors.length) 
     36    authors.each do |author| 
     37      assert !(author.send(:instance_variables).include? "@posts") 
     38      assert(!author.readonly?, "non-string join value produced read-only result.") 
     39    end 
     40  end 
     41 
     42 
     43  def test_ar_joins_with_complex_conditions 
     44    authors = Author.find(:all, :joins=>{:posts=>[:comments, :categories]}, 
     45    :conditions => ["categories.name = ?  AND posts.title = ?", "General", "So I was thinking"]  
     46    ) 
     47    assert_equal(1, authors.length) 
     48    authors.each do |author| 
     49      assert !(author.send(:instance_variables).include? "@posts") 
     50      assert(!author.readonly?, "non-string join value produced read-only result.") 
     51    end 
     52    assert_equal("David", authors.first.name) 
     53  end    
     54   
     55  def test_ar_join_with_has_many_and_limit_and_scoped_and_explicit_conditions 
     56    Post.with_scope(:find => { :conditions => "1=1" }) do 
     57      posts = authors(:david).posts.find(:all,  
     58        :joins    => :comments,  
     59        :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'", 
     60        :limit      => 2 
     61      ) 
     62      assert_equal 2, posts.size 
     63       
     64      count = Post.count( 
     65        :joins    => [ :comments, :author ],  
     66        :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')", 
     67        :limit      => 2 
     68      ) 
     69      assert_equal count, posts.size 
     70    end 
     71  end 
     72   
     73  def test_ar_join_with_scoped_order_using_association_limiting_without_explicit_scope 
     74    posts_with_explicit_order = Post.find(:all, :conditions => 'comments.id is not null', :joins => :comments, :order => 'posts.id DESC', :limit => 2) 
     75    posts_with_scoped_order = Post.with_scope(:find => {:order => 'posts.id DESC'}) do 
     76      Post.find(:all, :conditions => 'comments.id is not null', :joins => :comments, :limit => 2) 
     77    end 
     78    assert_equal posts_with_explicit_order, posts_with_scoped_order 
     79  end 
     80   
     81  def test_scoped_find_include 
     82    # with the include, will retrieve only developers for the given project 
     83    scoped_developers = Developer.with_scope(:find => { :joins => :projects }) do 
     84      Developer.find(:all, :conditions => 'projects.id = 2') 
     85    end 
     86    assert scoped_developers.include?(developers(:david)) 
     87    assert !scoped_developers.include?(developers(:jamis)) 
     88    assert_equal 1, scoped_developers.size 
     89  end 
     90   
     91   
     92  def test_nested_scoped_find_ar_join 
     93    Developer.with_scope(:find => { :joins => :projects }) do 
     94      Developer.with_scope(:find => { :conditions => "projects.id = 2" }) do 
     95        assert_equal('David', Developer.find(:first).name) 
     96      end 
     97    end   
     98  end 
     99 
     100  def test_nested_scoped_find_merged_ar_join 
     101    # :include's remain unique and don't "double up" when merging 
     102    Developer.with_scope(:find => { :joins => :projects, :conditions => "projects.id = 2" }) do 
     103      Developer.with_scope(:find => { :joins => :projects }) do 
     104        assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:ar_joins].length 
     105        assert_equal('David', Developer.find(:first).name) 
     106      end 
     107    end 
     108    # the nested scope doesn't remove the first :include 
     109    Developer.with_scope(:find => { :joins => :projects, :conditions => "projects.id = 2" }) do 
     110      Developer.with_scope(:find => { :joins => [] }) do 
     111        assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:ar_joins].length  
     112        assert_equal('David', Developer.find(:first).name) 
     113      end 
     114    end   
     115    # mixing array and symbol include's will merge correctly 
     116    Developer.with_scope(:find => { :joins => [:projects], :conditions => "projects.id = 2" }) do 
     117      Developer.with_scope(:find => { :joins => :projects }) do 
     118        assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:ar_joins].length 
     119        assert_equal('David', Developer.find(:first).name) 
     120      end 
     121    end        
     122  end  
     123 
     124  def test_nested_scoped_find_replace_include 
     125    Developer.with_scope(:find => { :joins => :projects }) do 
     126      Developer.with_exclusive_scope(:find => { :joins => [] }) do 
     127        assert_equal 0, Developer.instance_eval('current_scoped_methods')[:find][:ar_joins].length 
     128      end 
     129    end   
     130  end 
     131   
     132# 
     133# Calculations 
     134 
     135  def test_count_with_ar_joins 
     136    assert_equal(2, Author.count(:joins => :posts, :conditions => ['posts.type = ?', "Post"])) 
     137    assert_equal(1, Author.count(:joins => :posts, :conditions => ['posts.type = ?', "SpecialPost"])) 
     138  end   
     139   
     140  def test_should_get_maximum_of_field_with_joins 
     141    assert_equal 50, Account.maximum(:credit_limit, :joins=> :firm, :conditions => "companies.name != 'Summit'") 
     142  end  
     143   
     144  def test_should_get_maximum_of_field_with_scoped_include 
     145    Account.with_scope :find => { :joins => :firm, :conditions => "companies.name != 'Summit'" } do 
     146      assert_equal 50, Account.maximum(:credit_limit) 
     147    end 
     148  end 
     149   
     150  def test_should_not_modify_options_when_using_ar_joins_on_count 
     151    options = {:conditions => 'companies.id > 1', :joins => :firm} 
     152    options_copy = options.dup 
     153     
     154    Account.count(:all, options) 
     155    assert_equal options_copy, options 
     156  end 
     157   
     158end 
     159 
  • activerecord/lib/active_record/calculations.rb

    old new  
    1515      # The third approach, count using options, accepts an option hash as the only parameter. The options are: 
    1616      # 
    1717      # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro. 
    18       # * <tt>:joins</tt>: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed). 
    19       #   The records will be returned read-only since they will have attributes that do not correspond to the table's columns. 
     18      # * <tt>:joins</tt>: Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed). 
     19      #    or names associations in the same form used for the :include option. 
     20      #   If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns. 
     21      #   Pass :readonly => false to override. 
     22      #   See adding joins for associations under Association. 
    2023      # * <tt>:include</tt>: Named associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer 
    2124      #   to already defined associations. When using named associations count returns the number DISTINCT items for the model you're counting. 
    2225      #   See eager loading under Associations. 
     
    109112      #   Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for everyone with a last name other than 'Drake' 
    110113      #   Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) # Selects the minimum age for any family without any minors 
    111114      def calculate(operation, column_name, options = {}) 
     115        options, ar_joins = *extract_ar_join_from_options(options) 
    112116        validate_calculation_options(operation, options) 
     117        options[:ar_joins] = ar_joins if ar_joins 
    113118        column_name     = options[:select] if options[:select] 
    114119        column_name     = '*' if column_name == :all 
    115120        column          = column_for column_name 
     
    150155          options = options.symbolize_keys 
    151156 
    152157          scope           = scope(:find) 
     158          if scope &&scope[:ar_joins] 
     159            scope = scope.dup 
     160            options = options.dup 
     161            options[:ar_joins] = scope.delete(:ar_joins) 
     162          end 
    153163          merged_includes = merge_includes(scope ? scope[:include] : [], options[:include]) 
     164          merged_includes = merge_includes(merged_includes, options[:ar_joins]) 
    154165          aggregate_alias = column_alias_for(operation, column_name) 
    155166 
    156167          if operation == 'count' 
     
    173184          sql << " FROM (SELECT DISTINCT #{column_name}" if use_workaround 
    174185          sql << " FROM #{table_name} " 
    175186          if merged_includes.any? 
    176             join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, options[:joins]
     187            join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, options[:joins], options[:ar_joins]
    177188            sql << join_dependency.join_associations.collect{|join| join.association_join }.join 
    178189          end 
    179190          add_joins!(sql, options, scope) 
  • activerecord/lib/active_record/associations.rb

    old new  
    486486    #  
    487487    # When eager loaded, conditions are interpolated in the context of the model class, not the model instance.  Conditions are lazily interpolated 
    488488    # before the actual model exists. 
    489     #  
     489    # 
     490    # == Adding Joins For Associations to Queries Using the :joins option 
     491    # 
     492    # ActiveRecord::Base#find provides a :joins option, which takes either a string or values accepted by the :include option. 
     493    # if the value is a string, then it should contain a SQL fragment containing a join clause. 
     494    # 
     495    # Non-string values of :joins will add an automatic join clause to the query in the same way that the :include option does but with two critical 
     496    # differences: 
     497    # 
     498    #     1. A normal (inner) join will be performed instead of the outer join generated by :include. 
     499    #        this means that only objects which have objects attached to the association will be included in the result. 
     500    #        For example, suppose we have the following tables (in yaml format): 
     501    # 
     502    #        Authors 
     503    #          fred: 
     504    #            id: 1    
     505    #            name: Fred 
     506    #          steve: 
     507    #            id: 2 
     508    #            name: Steve 
     509    # 
     510    #        Contributions 
     511    #          only: 
     512    #            id: 1 
     513    #            author_id: 1    
     514    #            description: Atta Boy Letter for Steve      
     515    #            date: 2007-10-27 14:09:54 
     516    # 
     517    #        and corresponding AR Classes 
     518    # 
     519    #        class Author: < ActiveRecord::Base 
     520    #            has_many :contributions 
     521    #        end 
     522    # 
     523    #        class Contribution < ActiveRecord::Base 
     524    #            belongs_to :author 
     525    #        end 
     526    # 
     527    #        The query Author.find(:all) will return both authors, but Author.find(:all, :joins => :contributions) will 
     528    #        only return authors who have at least one contribution, in this case only the first. 
     529    #        This is only a degenerate case of the more typical use of :joins with a non-string value. 
     530    #        For example to find authors who have at least one contribution before a certain date we can use: 
     531    # 
     532    #            Author.find(:all, :joins => :contributions, :conditions => ["contributions.date <= ?", 1.week.ago.to_s(:db)]) 
     533    # 
     534    #     2. Only instances of the class to which the find is sent will be instantiated. ActiveRecord objects will not  
     535    #        be instantiated for rows reached through the associations.  
     536    # 
     537    #  The difference between using :joins vs :include to name associated records is that :joins allows associated tables to 
     538    #  participate in selection criteria in the query without incurring the overhead of instantiating associated objects. 
     539    #  This can be important when the number of associated objects in the database is large, and they will not be used, or 
     540    #  only those associated with a paricular object or objects will be used after the query, making two queries more 
     541    #  efficient than one. 
     542    # 
     543    #  Note that while using a string value for :joins marks the result objects as read-only, the objects resulting 
     544    #  from a call to find with a non-string :joins option value will be writable. 
     545    #      
    490546    # == Table Aliasing 
    491547    # 
    492548    # ActiveRecord uses table aliasing in the case that a table is referenced multiple times in a join.  If a table is referenced only once, 
     
    11211177         
    11221178        def find_with_associations(options = {}) 
    11231179          catch :invalid_query do 
    1124             join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins]) 
     1180            if ar_joins = scope(:find, :ar_joins)                            
     1181              options = options.dup 
     1182              options[:ar_joins] = ar_joins 
     1183            end 
     1184            includes = merge_includes(scope(:find, :include), options[:include]) 
     1185            includes = merge_includes(includes, options[:ar_joins]) 
     1186            join_dependency = JoinDependency.new(self, includes, options[:joins], options[:ar_joins]) 
    11251187            rows = select_all_rows(options, join_dependency) 
    11261188            return join_dependency.instantiate(rows) 
    11271189          end 
     
    13751437        class JoinDependency # :nodoc: 
    13761438          attr_reader :joins, :reflections, :table_aliases 
    13771439 
    1378           def initialize(base, associations, joins
     1440          def initialize(base, associations, joins, ar_joins = nil
    13791441            @joins                 = [JoinBase.new(base, joins)] 
     1442            @ar_joins              = ar_joins 
    13801443            @associations          = associations 
    13811444            @reflections           = [] 
    13821445            @base_records_hash     = {} 
     
    14001463              unless @base_records_hash[primary_id] 
    14011464                @base_records_in_order << (@base_records_hash[primary_id] = join_base.instantiate(row)) 
    14021465              end 
    1403               construct(@base_records_hash[primary_id], @associations, join_associations.dup, row) 
     1466              construct(@base_records_hash[primary_id], @associations, join_associations.dup, row) unless @ar_joins 
    14041467            end 
    1405             remove_duplicate_results!(join_base.active_record, @base_records_in_order, @associations) 
     1468            remove_duplicate_results!(join_base.active_record, @base_records_in_order, @associations) unless @ar_joins 
    14061469            return @base_records_in_order 
    14071470          end 
    14081471 
     
    14441507                  reflection = parent.reflections[associations.to_s.intern] or 
    14451508                  raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?" 
    14461509                  @reflections << reflection 
    1447                   @joins << JoinAssociation.new(reflection, self, parent) 
     1510                  @joins << (@ar_joins ? ARJoinAssociation : JoinAssociation).new(reflection, self, parent) 
    14481511                when Array 
    14491512                  associations.each do |association| 
    14501513                    build(association, parent) 
     
    15481611            end 
    15491612          end 
    15501613 
    1551           class JoinAssociation < JoinBase # :nodoc: 
     1614          class JoinAssociation < JoinBase # :nodoc:      
    15521615            attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name 
    15531616            delegate    :options, :klass, :through_reflection, :source_reflection, :to => :reflection 
    15541617 
     
    15911654                end 
    15921655              end 
    15931656            end 
    1594  
     1657             
    15951658            def association_join 
    15961659              join = case reflection.macro 
    15971660                when :has_and_belongs_to_many 
    1598                   " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [ 
     1661                  " #{join_type} %s ON %s.%s = %s.%s " % [ 
    15991662                     table_alias_for(options[:join_table], aliased_join_table_name), 
    16001663                     aliased_join_table_name, 
    16011664                     options[:foreign_key] || reflection.active_record.to_s.foreign_key, 
    16021665                     parent.aliased_table_name, reflection.active_record.primary_key] + 
    1603                   " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [ 
     1666                  " #{join_type} %s ON %s.%s = %s.%s " % [ 
    16041667                     table_name_and_alias, aliased_table_name, klass.primary_key, 
    16051668                     aliased_join_table_name, options[:association_foreign_key] || klass.to_s.foreign_key 
    16061669                     ] 
     
    16581721                        end 
    16591722                      end 
    16601723 
    1661                       " LEFT OUTER JOIN %s ON (%s.%s = %s.%s%s%s%s) " % [ 
     1724                      " #{join_type} %s ON (%s.%s = %s.%s%s%s%s) " % [ 
    16621725                        table_alias_for(through_reflection.klass.table_name, aliased_join_table_name), 
    16631726                        parent.aliased_table_name, reflection.active_record.connection.quote_column_name(parent.primary_key), 
    16641727                        aliased_join_table_name, reflection.active_record.connection.quote_column_name(jt_foreign_key),  
    16651728                        jt_as_extra, jt_source_extra, jt_sti_extra 
    16661729                      ] + 
    1667                       " LEFT OUTER JOIN %s ON (%s.%s = %s.%s%s) " % [ 
     1730                      " #{join_type} %s ON (%s.%s = %s.%s%s) " % [ 
    16681731                        table_name_and_alias,  
    16691732                        aliased_table_name, reflection.active_record.connection.quote_column_name(first_key), 
    16701733                        aliased_join_table_name, reflection.active_record.connection.quote_column_name(second_key), 
     
    16721735                      ] 
    16731736 
    16741737                    when reflection.options[:as] && [:has_many, :has_one].include?(reflection.macro) 
    1675                       " LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s" % [ 
     1738                      " #{join_type} %s ON %s.%s = %s.%s AND %s.%s = %s" % [ 
    16761739                        table_name_and_alias, 
    16771740                        aliased_table_name, "#{reflection.options[:as]}_id", 
    16781741                        parent.aliased_table_name, parent.primary_key, 
     
    16811744                      ] 
    16821745                    else 
    16831746                      foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key 
    1684                       " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [ 
     1747                      " #{join_type} %s ON %s.%s = %s.%s " % [ 
    16851748                        table_name_and_alias, 
    16861749                        aliased_table_name, foreign_key, 
    16871750                        parent.aliased_table_name, parent.primary_key 
    16881751                      ] 
    16891752                  end 
    16901753                when :belongs_to 
    1691                   " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [ 
     1754                  " #{join_type} %s ON %s.%s = %s.%s " % [ 
    16921755                     table_name_and_alias, aliased_table_name, reflection.klass.primary_key, 
    16931756                     parent.aliased_table_name, options[:foreign_key] || klass.to_s.foreign_key 
    16941757                    ] 
     
    17231786 
    17241787              def interpolate_sql(sql) 
    17251788                instance_eval("%@#{sql.gsub('@', '\@')}@")  
    1726               end  
     1789              end 
     1790               
     1791           private 
     1792              def join_type 
     1793                "LEFT OUTER JOIN" 
     1794              end 
     1795  
    17271796          end 
     1797          class ARJoinAssociation < JoinAssociation  
     1798            private 
     1799              def join_type 
     1800                "INNER JOIN" 
     1801              end 
     1802          end 
    17281803        end 
    17291804    end 
    17301805  end 
  • activerecord/lib/active_record/base.rb

    old new  
    380380      # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause. 
    381381      # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned. 
    382382      # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4. 
    383       # * <tt>:joins</tt>: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed). 
    384       #   The records will be returned read-only since they will have attributes that do not correspond to the table's columns. 
     383      # * <tt>:joins</tt>: Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed). 
     384      #    or names associations in the same form used for the :include option. 
     385      #   If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns. 
    385386      #   Pass :readonly => false to override. 
     387      #   See adding joins for associations under Association. 
    386388      # * <tt>:include</tt>: Names associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer 
    387389      #   to already defined associations. See eager loading under Associations. 
    388390      # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not 
     
    428430      #   end 
    429431      def find(*args) 
    430432        options = args.extract_options! 
     433        # Note:  we extract any :joins option with a non-string value from the options, and turn it into 
     434        #  an internal option :ar_joins.  This allows code called from her to find the ar_joins, and 
     435        #  it bypasses marking the result as read_only. 
     436        #  A normal string join marks the result as read-only because it contains attributes from joined tables 
     437        #  which are not in the base table and therefore prevent the result from being saved. 
     438        #  In the case of an ar_join, the JoinDependency created to instantiate the results eliminates these 
     439        #  bogus attributes.  See JoinDependency#instantiate, and JoinBase#instantiate in associations.rb. 
     440        options, ar_joins = *extract_ar_join_from_options(options) 
    431441        validate_find_options(options) 
    432442        set_readonly_option!(options) 
     443        options[:ar_joins] = ar_joins if ar_joins 
    433444 
    434445        case args.first 
    435446          when :first then find_initial(options) 
     
    10191030          options.update(:limit => 1) unless options[:include] 
    10201031          find_every(options).first 
    10211032        end 
    1022  
     1033         
     1034        # If options contains :joins, with a non-string value 
     1035        #  remove it from options 
     1036        # return the updated or unchanged options, and the ar_join value or nil 
     1037        def extract_ar_join_from_options(options) 
     1038          new_options = options.dup 
     1039          join_option = new_options.delete(:joins) 
     1040          (join_option && !join_option.kind_of?(String)) ? [new_options, join_option] : [options, nil] 
     1041        end 
     1042         
    10231043        def find_every(options) 
    1024           records = scoped?(:find, :include) || options[:include]
     1044          records = scoped?(:find, :include) || options[:include] || scoped?(:find, :ar_joins) || (options[:ar_joins])
    10251045            find_with_associations(options) :  
    10261046            find_by_sql(construct_finder_sql(options)) 
    10271047 
     
    14451465 
    14461466          if f = method_scoping[:find] 
    14471467            f.assert_valid_keys(VALID_FIND_OPTIONS) 
     1468            # see note about :joins and :ar_joins in ActiveRecord::Base#find 
     1469            f, ar_joins = *extract_ar_join_from_options(f) 
    14481470            set_readonly_option! f 
     1471            if ar_joins 
     1472              f[:ar_joins] = ar_joins 
     1473              method_scoping[:find] = f  
     1474            end                        
    14491475          end 
    14501476 
    14511477          # Merge scopings 
     
    14581484                      merge = hash[method][key] && params[key] # merge if both scopes have the same key 
    14591485                      if key == :conditions && merge 
    14601486                        hash[method][key] = [params[key], hash[method][key]].collect{ |sql| "( %s )" % sanitize_sql(sql) }.join(" AND ") 
    1461                       elsif key == :include && merge 
     1487                      elsif ([:include, :ar_joins].include?(key)) && merge 
    14621488                        hash[method][key] = merge_includes(hash[method][key], params[key]).uniq 
    14631489                      else 
    14641490                        hash[method][key] = hash[method][key] || params[key] 
     
    14731499              hash 
    14741500            end 
    14751501          end 
    1476  
     1502           
    14771503          self.scoped_methods << method_scoping 
    14781504 
    14791505          begin