Class: ActiveRecord::Reflection::ThroughReflection

Inherits:
AssociationReflection show all
Defined in:
activerecord/lib/active_record/reflection.rb

Overview

Holds all the meta-data about a :through association as it was specified in the Active Record class.

Constant Summary

Constants inherited from AssociationReflection

AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS, AssociationReflection::VALID_AUTOMATIC_INVERSE_MACROS

Instance Attribute Summary

Attributes inherited from AssociationReflection

#foreign_type, #type

Attributes inherited from MacroReflection

#active_record, #macro, #name, #options, #plural_name, #scope

Instance Method Summary collapse

Methods inherited from AssociationReflection

#active_record_primary_key, #association_class, #association_foreign_key, #belongs_to?, #build_association, #check_validity_of_inverse!, #collection?, #counter_cache_column, #foreign_key, #has_and_belongs_to_many?, #has_inverse?, #inverse_of, #join_table, #klass, #polymorphic?, #polymorphic_inverse_of, #primary_key_column, #quoted_table_name, #table_name, #validate?

Methods inherited from MacroReflection

#==, #autosave=, #class_name, #klass

Constructor Details

#initialize(macro, name, scope, options, active_record) ⇒ ThroughReflection

Returns a new instance of ThroughReflection


477
478
479
480
# File 'activerecord/lib/active_record/reflection.rb', line 477

def initialize(macro, name, scope, options, active_record)
  super
  @source_reflection_name = options[:source]
end

Instance Method Details

#association_primary_key(klass = nil) ⇒ Object

We want to use the klass from this reflection, rather than just delegate straight to the source_reflection, because the source_reflection may be polymorphic. We still need to respect the source_reflection's :primary_key option, though.


597
598
599
600
601
# File 'activerecord/lib/active_record/reflection.rb', line 597

def association_primary_key(klass = nil)
  # Get the "actual" source reflection if the immediate source reflection has a
  # source reflection itself
  actual_source_reflection.options[:primary_key] || primary_key(klass || self.klass)
end

#chainObject

Returns an array of reflections which are involved in this association. Each item in the array corresponds to a table which will be part of the query for this association.

The chain is built by recursively calling #chain on the source reflection and the through reflection. The base case for the recursion is a normal association, which just returns

self

as its #chain.

class Post < ActiveRecord::Base
  has_many :taggings
  has_many :tags, through: :taggings
end

tags_reflection = Post.reflect_on_association(:tags)
tags_reflection.chain
# => [<ActiveRecord::Reflection::ThroughReflection: @macro=:has_many, @name=:tags, @options={:through=>:taggings}, @active_record=Post>,
      <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @options={}, @active_record=Post>]

536
537
538
539
540
541
542
543
544
# File 'activerecord/lib/active_record/reflection.rb', line 536

def chain
  @chain ||= begin
    a = source_reflection.chain
    b = through_reflection.chain
    chain = a + b
    chain[0] = self # Use self so we don't lose the information from :source_type
    chain
  end
end

#check_validity!Object


651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
# File 'activerecord/lib/active_record/reflection.rb', line 651

def check_validity!
  if through_reflection.nil?
    raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
  end

  if through_reflection.options[:polymorphic]
    raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
  end

  if source_reflection.nil?
    raise HasManyThroughSourceAssociationNotFoundError.new(self)
  end

  if options[:source_type] && source_reflection.options[:polymorphic].nil?
    raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
  end

  if source_reflection.options[:polymorphic] && options[:source_type].nil?
    raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
  end

  if macro == :has_one && through_reflection.collection?
    raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
  end

  check_validity_of_inverse!
end

#nested?Boolean

A through association is nested if there would be more than one join table

Returns:

  • (Boolean)

590
591
592
# File 'activerecord/lib/active_record/reflection.rb', line 590

def nested?
  chain.length > 2 || through_reflection.has_and_belongs_to_many?
end

#scope_chainObject

Consider the following example:

class Person
  has_many :articles
  has_many :comment_tags, through: :articles
end

class Article
  has_many :comments
  has_many :comment_tags, through: :comments, source: :tags
end

class Comment
  has_many :tags
end

There may be scopes on Person.comment_tags, Article.comment_tags and/or Comment.tags, but only Comment.tags will be represented in the #chain. So this method creates an array of scopes corresponding to the chain.


565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
# File 'activerecord/lib/active_record/reflection.rb', line 565

def scope_chain
  @scope_chain ||= begin
    scope_chain = source_reflection.scope_chain.map(&:dup)

    # Add to it the scope from this reflection (if any)
    scope_chain.first << scope if scope

    through_scope_chain = through_reflection.scope_chain

    if options[:source_type]
      through_scope_chain.first <<
        through_reflection.klass.where(foreign_type => options[:source_type])
    end

    # Recursively fill out the rest of the array from the through reflection
    scope_chain + through_scope_chain
  end
end

#source_macroObject

The macro used by the source association


585
586
587
# File 'activerecord/lib/active_record/reflection.rb', line 585

def source_macro
  source_reflection.source_macro
end

#source_optionsObject


643
644
645
# File 'activerecord/lib/active_record/reflection.rb', line 643

def source_options
  source_reflection.options
end

#source_reflectionObject

Returns the source of the through reflection. It checks both a singularized and pluralized form for :belongs_to or :has_many.

class Post < ActiveRecord::Base
  has_many :taggings
  has_many :tags, through: :taggings
end

class Tagging < ActiveRecord::Base
  belongs_to :post
  belongs_to :tag
end

tags_reflection = Post.reflect_on_association(:tags)
tags_reflection.source_reflection
# => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags">

499
500
501
# File 'activerecord/lib/active_record/reflection.rb', line 499

def source_reflection
  through_reflection.klass.reflect_on_association(source_reflection_name)
end

#source_reflection_nameObject

:nodoc:


618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
# File 'activerecord/lib/active_record/reflection.rb', line 618

def source_reflection_name # :nodoc:
  return @source_reflection_name if @source_reflection_name

  names = [name.to_s.singularize, name].collect { |n| n.to_sym }.uniq
  names = names.find_all { |n|
    through_reflection.klass.reflect_on_association(n)
  }

  if names.length > 1
    example_options = options.dup
    example_options[:source] = source_reflection_names.first
    ActiveSupport::Deprecation.warn <<-eowarn
Ambiguous source reflection for through association.  Please specify a :source
directive on your declaration like:

  class #{active_record.name} < ActiveRecord::Base
    #{macro} :#{name}, #{example_options}
  end

    eowarn
  end

  @source_reflection_name = names.first
end

#source_reflection_namesObject

Gets an array of possible :through source reflection names in both singular and plural form.

class Post < ActiveRecord::Base
  has_many :taggings
  has_many :tags, through: :taggings
end

tags_reflection = Post.reflect_on_association(:tags)
tags_reflection.source_reflection_names
# => [:tag, :tags]

614
615
616
# File 'activerecord/lib/active_record/reflection.rb', line 614

def source_reflection_names
  (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }.uniq
end

#through_optionsObject


647
648
649
# File 'activerecord/lib/active_record/reflection.rb', line 647

def through_options
  through_reflection.options
end

#through_reflectionObject

Returns the AssociationReflection object specified in the :through option of a HasManyThrough or HasOneThrough association.

class Post < ActiveRecord::Base
  has_many :taggings
  has_many :tags, through: :taggings
end

tags_reflection = Post.reflect_on_association(:tags)
tags_reflection.through_reflection
# => <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @active_record=Post, @plural_name="taggings">

515
516
517
# File 'activerecord/lib/active_record/reflection.rb', line 515

def through_reflection
  active_record.reflect_on_association(options[:through])
end