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

Ticket #6461: arbitrary_has_many_through.diff

File arbitrary_has_many_through.diff, 8.7 kB (added by obrie, 2 years ago)

Initial attempt.

  • C:/Projects/workspace/rails/activerecord/lib/active_record/associations/has_many_through_association.rb

    old new  
    155155 
    156156        # Build SQL conditions from attributes, qualified by table name. 
    157157        def construct_conditions 
    158           table_name = @reflection.through_reflection.table_name 
    159           conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value| 
     158          reflection, table_id = find_deepest_through(@reflection.through_reflection) 
     159          table_name = reflection.table_name 
     160          table_name += "_#{table_id}" if table_id 
     161 
     162          conditions = construct_quoted_owner_attributes(reflection).map do |attr, value| 
    160163            "#{table_name}.#{attr} = #{value}" 
    161164          end 
    162165          conditions << sql_conditions if sql_conditions 
     
    163166          "(" + conditions.join(') AND (') + ")" 
    164167        end 
    165168 
     169        def find_deepest_through(reflection, table_ids = {@reflection.table_name => 1, reflection.table_name => 1}) 
     170          # Don't overwrite the original hash since this only a look-ahead 
     171          table_ids = table_ids.dup 
     172 
     173          if through_reflection = reflection.through_reflection 
     174            table_name = through_reflection.table_name 
     175            table_ids[table_name] = (table_ids[table_name] || 0) + 1 
     176 
     177            find_deepest_through(through_reflection, table_ids) 
     178          else 
     179            table_id = table_ids[reflection.table_name] || 1 
     180            return reflection, (table_id > 1 ? table_id : nil) 
     181          end 
     182        end 
     183 
    166184        def construct_from 
    167185          @reflection.table_name 
    168186        end 
     
    171189          selected = custom_select || @reflection.options[:select] || "#{@reflection.table_name}.*" 
    172190        end 
    173191 
    174         def construct_joins(custom_joins = nil) 
     192        def construct_joins(custom_joins = nil, reflection = @reflection, table_ids = {@reflection.table_name => 1}) 
     193          prepended_joins = '' 
     194          appended_joins = '' 
     195 
     196          through_reflection = reflection.through_reflection 
     197          source_reflection = reflection.source_reflection 
     198          reflection_table_name = reflection.table_name 
     199          parent_table_id = table_ids[reflection_table_name] 
     200 
     201          through_table_name = through_reflection.table_name 
     202          through_table_name_alias = through_table_name 
     203          if table_ids[through_table_name] 
     204            table_id = table_ids[through_table_name] += 1 
     205            through_table_name_alias += "_#{table_id}" 
     206          else 
     207            table_ids[through_table_name] = 1 
     208          end 
     209 
     210          if through_reflection.through_reflection 
     211            appended_joins = ' ' + construct_joins(nil, through_reflection, table_ids) 
     212          elsif source_reflection.through_reflection 
     213            source_reflection, parent_table_id = find_deepest_through(source_reflection, table_ids) 
     214            reflection_table_name = source_reflection.table_name 
     215 
     216            prepended_joins = construct_joins(nil, reflection.source_reflection, table_ids) + ' ' 
     217          end 
     218          reflection_table_name += "_#{parent_table_id}" if parent_table_id && parent_table_id > 1 
     219 
    175220          polymorphic_join = nil 
    176           if @reflection.through_reflection.options[:as] || @reflection.source_reflection.macro == :belongs_to 
    177             reflection_primary_key = @reflection.klass.primary_key 
    178             source_primary_key     = @reflection.source_reflection.primary_key_name 
     221          if through_reflection.options[:as] || source_reflection.macro == :belongs_to 
     222            reflection_primary_key = reflection.klass.primary_key 
     223            source_primary_key     = source_reflection.primary_key_name 
    179224          else 
    180             reflection_primary_key = @reflection.source_reflection.primary_key_name 
    181             source_primary_key     = @reflection.klass.primary_key 
    182             if @reflection.source_reflection.options[:as] 
     225            reflection_primary_key = source_reflection.primary_key_name 
     226            source_primary_key     = reflection.klass.primary_key 
     227 
     228            if source_reflection.options[:as] 
    183229              polymorphic_join = "AND %s.%s = %s" % [ 
    184                 @reflection.table_name, "#{@reflection.source_reflection.options[:as]}_type", 
    185                 @owner.class.quote_value(@reflection.through_reflection.klass.name) 
     230                reflection_table_name, "#{source_reflection.options[:as]}_type", 
     231                ActiveRecord::Base.quote_value(through_reflection.klass.name) 
    186232              ] 
    187233            end 
    188234          end 
     
    187233            end 
    188234          end 
    189235 
    190           "INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [ 
    191             @reflection.through_reflection.table_name, 
    192             @reflection.table_name, reflection_primary_key, 
    193             @reflection.through_reflection.table_name, source_primary_key, 
     236          "#{prepended_joins}INNER JOIN %s %sON %s.%s = %s.%s %s #{reflection.options[:joins]} #{custom_joins}#{appended_joins}" % [ 
     237            through_table_name, 
     238            through_table_name != through_table_name_alias ? "AS #{through_table_name_alias} " : '', 
     239            reflection_table_name, reflection_primary_key, 
     240            through_table_name_alias, source_primary_key, 
    194241            polymorphic_join 
    195242          ] 
    196243        end 
  • C:/Projects/workspace/rails/activerecord/lib/active_record/reflection.rb

    old new  
    168168          if through_reflection.nil? 
    169169            raise HasManyThroughAssociationNotFoundError.new(active_record.name, self) 
    170170          end 
    171            
     171 
    172172          if source_reflection.nil? 
    173173            raise HasManyThroughSourceAssociationNotFoundError.new(self) 
    174174          end 
    175            
     175 
    176176          if source_reflection.options[:polymorphic] 
    177177            raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection) 
    178178          end 
    179            
    180           unless [:belongs_to, :has_many].include?(source_reflection.macro) && source_reflection.options[:through].nil? 
     179 
     180          unless [:belongs_to, :has_many].include?(source_reflection.macro) 
    181181            raise HasManyThroughSourceAssociationMacroError.new(self) 
    182182          end 
    183183        end 
  • C:/Projects/workspace/rails/activerecord/test/associations/join_model_test.rb

    old new  
    336336    end 
    337337  end 
    338338 
    339   def test_has_many_through_has_many_through 
    340     assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).tags } 
     339  def test_has_many_through_multiple_levels 
     340    author = authors(:david) 
     341 
     342    assert_equal [tags(:general)], author.tags.uniq.sort_by { |t| t.id } 
     343    assert_equal [taggings(:welcome_general), taggings(:thinking_general), taggings(:fake)], author.tag_taggings.uniq.sort_by { |t| t.id } 
     344    assert_equal [tags(:general)], author.tag_tagging_tags.uniq.sort_by { |t| t.id } 
    341345  end 
    342346 
    343347  def test_has_many_through_habtm 
  • C:/Projects/workspace/rails/activerecord/test/fixtures/author.rb

    old new  
    5656 
    5757  has_many :tagging,  :through => :posts # through polymorphic has_one 
    5858  has_many :taggings, :through => :posts, :source => :taggings # through polymorphic has_many 
    59   has_many :tags,     :through => :posts # through has_many :through 
     59  has_many :tags,     :through => :posts # through remote has_many :through 
     60  has_many :tag_taggings, :through => :tags, :source => :taggings # through 2 local has_many :through, 1 remote 
     61  has_many :tag_tagging_tags, :through => :tag_taggings, :source => :tag # multiple table references 
     62 
    6063  has_many :post_categories, :through => :posts, :source => :categories 
    6164 
    6265  belongs_to :author_address