Class: ActiveRecord::Reflection::ThroughReflection

Inherits:
AssociationReflection show all
Defined in:
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, #parent_reflection, #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?, #constructable?, #counter_cache_column, #foreign_key, #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.



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

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.



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

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


575
576
577
578
579
580
581
582
583
# File 'lib/active_record/reflection.rb', line 575

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



693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
# File 'lib/active_record/reflection.rb', line 693

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)


632
633
634
# File 'lib/active_record/reflection.rb', line 632

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.



604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
# File 'lib/active_record/reflection.rb', line 604

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



627
628
629
# File 'lib/active_record/reflection.rb', line 627

def source_macro
  source_reflection.source_macro
end

#source_optionsObject



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

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


536
537
538
539
540
# File 'lib/active_record/reflection.rb', line 536

def source_reflection
  if source_reflection_name
    through_reflection.klass._reflect_on_association(source_reflection_name)
  end
end

#source_reflection_nameObject

:nodoc:



660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
# File 'lib/active_record/reflection.rb', line 660

def source_reflection_name # :nodoc:
  return @source_reflection_name.to_sym 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]


656
657
658
# File 'lib/active_record/reflection.rb', line 656

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

#through_optionsObject



689
690
691
# File 'lib/active_record/reflection.rb', line 689

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


554
555
556
# File 'lib/active_record/reflection.rb', line 554

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