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

Ticket #10061: correct_ar_joins.diff

File correct_ar_joins.diff, 16.1 kB (added by protocool, 10 months ago)
  • 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>: 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. 
     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. 
    2320      # * <tt>:include</tt>: Named associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer 
    2421      #   to already defined associations. When using named associations count returns the number DISTINCT items for the model you're counting. 
    2522      #   See eager loading under Associations. 
     
    112109      #   Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for everyone with a last name other than 'Drake' 
    113110      #   Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) # Selects the minimum age for any family without any minors 
    114111      def calculate(operation, column_name, options = {}) 
    115         options, ar_joins = *extract_ar_join_from_options(options) 
    116112        validate_calculation_options(operation, options) 
    117         options[:ar_joins] = ar_joins if ar_joins 
    118113        column_name     = options[:select] if options[:select] 
    119114        column_name     = '*' if column_name == :all 
    120115        column          = column_for column_name 
     
    154149          operation = operation.to_s.downcase 
    155150          options = options.symbolize_keys 
    156151 
    157           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 
     152          scope           = scope(:find) 
    163153          merged_includes = merge_includes(scope ? scope[:include] : [], options[:include]) 
    164           merged_includes = merge_includes(merged_includes, options[:ar_joins]) 
    165154          aggregate_alias = column_alias_for(operation, column_name) 
    166155 
    167156          if operation == 'count' 
     
    184173          sql << " FROM (SELECT DISTINCT #{column_name}" if use_workaround 
    185174          sql << " FROM #{connection.quote_table_name(table_name)} " 
    186175          if merged_includes.any? 
    187             join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, options[:joins], options[:ar_joins]
     176            join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, options[:joins]
    188177            sql << join_dependency.join_associations.collect{|join| join.association_join }.join 
    189178          end 
    190179          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     # 
    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, the 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     # 
     489    #  
    546490    # == Table Aliasing 
    547491    # 
    548492    # ActiveRecord uses table aliasing in the case that a table is referenced multiple times in a join.  If a table is referenced only once, 
     
    11771121         
    11781122        def find_with_associations(options = {}) 
    11791123          catch :invalid_query do 
    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]) 
     1124            join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins]) 
    11871125            rows = select_all_rows(options, join_dependency) 
    11881126            return join_dependency.instantiate(rows) 
    11891127          end 
     
    14371375        class JoinDependency # :nodoc: 
    14381376          attr_reader :joins, :reflections, :table_aliases 
    14391377 
    1440           def initialize(base, associations, joins, ar_joins = nil
     1378          def initialize(base, associations, joins
    14411379            @joins                 = [JoinBase.new(base, joins)] 
    1442             @ar_joins              = ar_joins 
    14431380            @associations          = associations 
    14441381            @reflections           = [] 
    14451382            @base_records_hash     = {} 
     
    14631400              unless @base_records_hash[primary_id] 
    14641401                @base_records_in_order << (@base_records_hash[primary_id] = join_base.instantiate(row)) 
    14651402              end 
    1466               construct(@base_records_hash[primary_id], @associations, join_associations.dup, row) unless @ar_joins 
     1403              construct(@base_records_hash[primary_id], @associations, join_associations.dup, row) 
    14671404            end 
    1468             remove_duplicate_results!(join_base.active_record, @base_records_in_order, @associations) unless @ar_joins 
     1405            remove_duplicate_results!(join_base.active_record, @base_records_in_order, @associations) 
    14691406            return @base_records_in_order 
    14701407          end 
    14711408 
     
    15071444                  reflection = parent.reflections[associations.to_s.intern] or 
    15081445                  raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?" 
    15091446                  @reflections << reflection 
    1510                   @joins << (@ar_joins ? ARJoinAssociation : JoinAssociation).new(reflection, self, parent) 
     1447                  @joins << build_join_association(reflection, parent) 
    15111448                when Array 
    15121449                  associations.each do |association| 
    15131450                    build(association, parent) 
     
    15221459              end 
    15231460            end 
    15241461 
     1462            # overridden in InnerJoinDependency subclass 
     1463            def build_join_association(reflection, parent) 
     1464              JoinAssociation.new(reflection, self, parent) 
     1465            end 
     1466 
    15251467            def construct(parent, associations, joins, row) 
    15261468              case associations 
    15271469                when Symbol, String 
     
    17861728 
    17871729              def interpolate_sql(sql) 
    17881730                instance_eval("%@#{sql.gsub('@', '\@')}@")  
    1789               end 
     1731              end  
    17901732 
    1791            private 
     1733            private 
     1734 
    17921735              def join_type 
    17931736                "LEFT OUTER JOIN" 
    17941737              end 
    1795  
    17961738          end 
    1797           class ARJoinAssociation < JoinAssociation 
     1739        end 
     1740 
     1741        class InnerJoinDependency < JoinDependency # :nodoc: 
     1742          protected 
     1743            def build_join_association(reflection, parent) 
     1744              InnerJoinAssociation.new(reflection, self, parent) 
     1745            end 
     1746 
     1747          class InnerJoinAssociation < JoinAssociation 
    17981748            private 
    17991749              def join_type 
    18001750                "INNER JOIN" 
    18011751              end 
    18021752          end 
    18031753        end 
     1754 
    18041755    end 
    18051756  end 
    18061757end 
  • 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>: 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. 
     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. 
    386385      #   Pass :readonly => false to override. 
    387       #   See adding joins for associations under Association. 
    388386      # * <tt>:include</tt>: Names associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer 
    389387      #   to already defined associations. See eager loading under Associations. 
    390388      # * <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 
     
    430428      #   end 
    431429      def find(*args) 
    432430        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) 
    441431        validate_find_options(options) 
    442432        set_readonly_option!(options) 
    443         options[:ar_joins] = ar_joins if ar_joins 
    444433 
    445434        case args.first 
    446435          when :first then find_initial(options) 
     
    10311020          find_every(options).first 
    10321021        end 
    10331022 
    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  
    10431023        def find_every(options) 
    1044           records = scoped?(:find, :include) || options[:include] || scoped?(:find, :ar_joins) || (options[:ar_joins])
     1024          records = scoped?(:find, :include) || options[:include]
    10451025            find_with_associations(options) :  
    10461026            find_by_sql(construct_finder_sql(options)) 
    10471027 
     
    12391219        def add_joins!(sql, options, scope = :auto) 
    12401220          scope = scope(:find) if :auto == scope 
    12411221          join = (scope && scope[:joins]) || options[:joins] 
    1242           sql << " #{join} " if join 
     1222          case join 
     1223          when Symbol, Hash, Array 
     1224            join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, join, nil) 
     1225            sql << " #{join_dependency.join_associations.collect{|join| join.association_join }.join} " 
     1226          else 
     1227            sql << " #{join} " 
     1228          end 
    12431229        end 
    12441230 
    12451231        # Adds a sanitized version of +conditions+ to the +sql+ string. Note that the passed-in +sql+ string is changed. 
     
    14651451 
    14661452          if f = method_scoping[:find] 
    14671453            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) 
    14701454            set_readonly_option! f 
    1471             if ar_joins 
    1472               f[:ar_joins] = ar_joins 
    1473               method_scoping[:find] = f 
    1474             end 
    14751455          end 
    14761456 
    14771457          # Merge scopings 
     
    14841464                      merge = hash[method][key] && params[key] # merge if both scopes have the same key 
    14851465                      if key == :conditions && merge 
    14861466                        hash[method][key] = [params[key], hash[method][key]].collect{ |sql| "( %s )" % sanitize_sql(sql) }.join(" AND ") 
    1487                       elsif ([:include, :ar_joins].include?(key)) && merge 
     1467                      elsif key == :include && merge 
    14881468                        hash[method][key] = merge_includes(hash[method][key], params[key]).uniq 
    14891469                      else 
    14901470                        hash[method][key] = hash[method][key] || params[key]