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.

Direct Known Subclasses

PolymorphicReflection

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from AbstractReflection

#alias_candidate, #build_association, #chain, #check_validity_of_inverse!, #class_name, #counter_cache_column, #counter_must_be_updated_by_has_many?, #has_cached_counter?, #inverse_of, #inverse_updates_counter_in_memory?, #inverse_which_updates_counter_cache, #primary_key_type, #quoted_table_name, #table_name

Constructor Details

#initialize(delegate_reflection) ⇒ ThroughReflection

Returns a new instance of ThroughReflection.


713
714
715
716
717
# File 'activerecord/lib/active_record/reflection.rb', line 713

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:


709
710
711
# File 'activerecord/lib/active_record/reflection.rb', line 709

def delegate_reflection
  @delegate_reflection
end

Instance Method Details

#add_as_polymorphic_through(reflection, seed) ⇒ Object


954
955
956
# File 'activerecord/lib/active_record/reflection.rb', line 954

def add_as_polymorphic_through(reflection, seed)
  collect_join_reflections(seed + [PolymorphicReflection.new(self, reflection)])
end

#add_as_source(seed) ⇒ Object


950
951
952
# File 'activerecord/lib/active_record/reflection.rb', line 950

def add_as_source(seed)
  collect_join_reflections seed
end

#add_as_through(seed) ⇒ Object


958
959
960
# File 'activerecord/lib/active_record/reflection.rb', line 958

def add_as_through(seed)
  collect_join_reflections(seed + [self])
end

#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.


852
853
854
855
856
# File 'activerecord/lib/active_record/reflection.rb', line 852

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

#association_primary_key_typeObject


858
859
860
# File 'activerecord/lib/active_record/reflection.rb', line 858

def association_primary_key_type
  klass.type_for_attribute(association_primary_key)
end

#check_validity!Object


912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
# File 'activerecord/lib/active_record/reflection.rb', line 912

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

#clear_association_scope_cacheObject

This is for clearing cache on the reflection. Useful for tests that need to compare SQL queries on associations.


787
788
789
790
791
# File 'activerecord/lib/active_record/reflection.rb', line 787

def clear_association_scope_cache # :nodoc:
  delegate_reflection.clear_association_scope_cache
  source_reflection.clear_association_scope_cache
  through_reflection.clear_association_scope_cache
end

#collect_join_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>]

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

def collect_join_chain
  collect_join_reflections [self]
end

#collect_join_reflections(seed) ⇒ Object


962
963
964
965
966
967
968
969
# File 'activerecord/lib/active_record/reflection.rb', line 962

def collect_join_reflections(seed)
  a = source_reflection.add_as_source seed
  if options[:source_type]
    through_reflection.add_as_polymorphic_through self, a
  else
    through_reflection.add_as_through a
  end
end

#constraintsObject


944
945
946
947
948
# File 'activerecord/lib/active_record/reflection.rb', line 944

def constraints
  scope_chain = source_reflection.constraints
  scope_chain << scope if scope
  scope_chain
end

#has_scope?Boolean

Returns:

  • (Boolean)

834
835
836
837
838
# File 'activerecord/lib/active_record/reflection.rb', line 834

def has_scope?
  scope || options[:source_type] ||
    source_reflection.has_scope? ||
    through_reflection.has_scope?
end

#join_id_for(owner) ⇒ Object

:nodoc:


908
909
910
# File 'activerecord/lib/active_record/reflection.rb', line 908

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

#join_keys(association_klass) ⇒ Object


840
841
842
# File 'activerecord/lib/active_record/reflection.rb', line 840

def join_keys(association_klass)
  source_reflection.join_keys(association_klass)
end

#klassObject


723
724
725
# File 'activerecord/lib/active_record/reflection.rb', line 723

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)

845
846
847
# File 'activerecord/lib/active_record/reflection.rb', line 845

def nested?
  source_reflection.through_reflection? || through_reflection.through_reflection?
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.


812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
# File 'activerecord/lib/active_record/reflection.rb', line 812

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_optionsObject


900
901
902
# File 'activerecord/lib/active_record/reflection.rb', line 900

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">


744
745
746
# File 'activerecord/lib/active_record/reflection.rb', line 744

def source_reflection
  through_reflection.klass._reflect_on_association(source_reflection_name)
end

#source_reflection_nameObject

:nodoc:


877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
# File 'activerecord/lib/active_record/reflection.rb', line 877

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

  names = [name.to_s.singularize, name].collect(&: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]


873
874
875
# File 'activerecord/lib/active_record/reflection.rb', line 873

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

#through_optionsObject


904
905
906
# File 'activerecord/lib/active_record/reflection.rb', line 904

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">


760
761
762
# File 'activerecord/lib/active_record/reflection.rb', line 760

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

#through_reflection?Boolean

Returns:

  • (Boolean)

719
720
721
# File 'activerecord/lib/active_record/reflection.rb', line 719

def through_reflection?
  true
end