Class: ActiveRecord::Reflection::ThroughReflection

Inherits:
AbstractReflection 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.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from AbstractReflection

#build_association, #check_validity_of_inverse!, #class_name, #inverse_of, #primary_key_type, #quoted_table_name, #table_name

Constructor Details

#initialize(delegate_reflection) ⇒ ThroughReflection

Returns a new instance of ThroughReflection.


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

def initialize(delegate_reflection)
  @delegate_reflection = delegate_reflection
  @klass         = delegate_reflection.options[:anonymous_class]
  @source_reflection_name = delegate_reflection.options[:source]
end

Instance Attribute Details

#delegate_reflectionObject (readonly)

:nodoc:


639
640
641
# File 'activerecord/lib/active_record/reflection.rb', line 639

def delegate_reflection
  @delegate_reflection
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.


780
781
782
783
784
# File 'activerecord/lib/active_record/reflection.rb', line 780

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: @delegate_reflection=#<ActiveRecord::Reflection::HasManyReflection: @name=:tags...>,
      <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @options={}, @active_record=Post>]

707
708
709
710
711
712
713
714
715
# File 'activerecord/lib/active_record/reflection.rb', line 707

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


836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
# File 'activerecord/lib/active_record/reflection.rb', line 836

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

  if through_reflection.polymorphic?
    if has_one?
      raise HasOneAssociationPolymorphicThroughError.new(active_record.name, self)
    else
      raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
    end
  end

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

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

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

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

  check_validity_of_inverse!
end

#join_id_for(owner) ⇒ Object

:nodoc:


832
833
834
# File 'activerecord/lib/active_record/reflection.rb', line 832

def join_id_for(owner) # :nodoc:
  source_reflection.join_id_for(owner)
end

#join_keys(assoc_klass) ⇒ Object


758
759
760
# File 'activerecord/lib/active_record/reflection.rb', line 758

def join_keys(assoc_klass)
  source_reflection.join_keys(assoc_klass)
end

#klassObject


649
650
651
# File 'activerecord/lib/active_record/reflection.rb', line 649

def klass
  @klass ||= delegate_reflection.compute_class(class_name)
end

#nested?Boolean

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

Returns:

  • (Boolean)

773
774
775
# File 'activerecord/lib/active_record/reflection.rb', line 773

def nested?
  chain.length > 2
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.


736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
# File 'activerecord/lib/active_record/reflection.rb', line 736

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.map(&:dup)

    if options[:source_type]
      type = foreign_type
      source_type = options[:source_type]
      through_scope_chain.first << lambda { |object|
        where(type => 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


763
764
765
766
767
768
769
770
# File 'activerecord/lib/active_record/reflection.rb', line 763

def source_macro
  ActiveSupport::Deprecation.warn(<<-MSG.squish)
    ActiveRecord::Base.source_macro is deprecated and will be removed
    without replacement.
  MSG

  source_reflection.source_macro
end

#source_optionsObject


824
825
826
# File 'activerecord/lib/active_record/reflection.rb', line 824

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::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">


670
671
672
# File 'activerecord/lib/active_record/reflection.rb', line 670

def source_reflection
  through_reflection.klass._reflect_on_association(source_reflection_name)
end

#source_reflection_nameObject

:nodoc:


801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
# File 'activerecord/lib/active_record/reflection.rb', line 801

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 \
      "Ambiguous source reflection for through association.  Please " \
      "specify a :source directive on your declaration like:\n" \
      "\n" \
      "  class #{active_record.name} < ActiveRecord::Base\n" \
      "    #{macro} :#{name}, #{example_options}\n" \
      "  end"
  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]


797
798
799
# File 'activerecord/lib/active_record/reflection.rb', line 797

def source_reflection_names
  options[:source] ? [options[:source]] : [name.to_s.singularize, name].uniq
end

#through_optionsObject


828
829
830
# File 'activerecord/lib/active_record/reflection.rb', line 828

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::HasManyReflection: @name=:taggings, @active_record=Post, @plural_name="taggings">


686
687
688
# File 'activerecord/lib/active_record/reflection.rb', line 686

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