trace一下belongs_to

class Comment < ApplicationRecord
  binding.trace_tree(html: true, tmp: ['rails', 'belongs_to.html']) do
    belongs_to :article
  end
end


基本上它干的就是两件事build reflection,然后add reflection


源码如下

def belongs_to(name, scope = nil, options = {})
  reflection = Builder::BelongsTo.build(self, name, scope, options)
  Reflection.add_reflection self, name, reflection
end


require是来自于autoload(activerecord-5.0.2/lib/active_record/associations.rb)

module Builder #:nodoc:
  autoload :Association,           'active_record/associations/builder/association'
  autoload :SingularAssociation,   'active_record/associations/builder/singular_association'
  autoload :CollectionAssociation, 'active_record/associations/builder/collection_association'

  autoload :BelongsTo,           'active_record/associations/builder/belongs_to'
  autoload :HasOne,              'active_record/associations/builder/has_one'
  autoload :HasMany,             'active_record/associations/builder/has_many'
  autoload :HasAndBelongsToMany, 'active_record/associations/builder/has_and_belongs_to_many'
end


build继承自Assosiation,最后根据macro辗转返回BelongToReflection

module ActiveRecord::Associations::Builder
  class Association
    def self.build(model, name, scope, options, &block)
      #...
      reflection = create_reflection model, name, scope, options, extension
      define_accessors model, reflection
      #...
      reflection
    end

    def self.create_reflection(model, name, scope, options, extension = nil)
      #...
      ActiveRecord::Reflection.create(macro, name, scope, options, model)
    end

    def self.define_accessors(model, reflection)
      mixin = model.generated_association_methods
      name = reflection.name
      define_readers(mixin, name)
      define_writers(mixin, name)
    end
  end

  class SingularAssociation < Association
  end

  class BelongsTo < SingularAssociation #:nodoc:
    def self.macro
      :belongs_to
    end
  end

end


create根据macro来决定用哪种Reflection(那为什么不直接传具体Reflection呢……为什么要macro而不是klass呢……)

module ActiveRecord
  # = Active Record Reflection
  module Reflection # :nodoc:

    def self.create(macro, name, scope, options, ar)
      klass = case macro
              when :composed_of
                AggregateReflection
              when :has_many
                HasManyReflection
              when :has_one
                HasOneReflection
              when :belongs_to
                BelongsToReflection
              else
                raise "Unsupported Macro: #{macro}"
              end

      reflection = klass.new(name, scope, options, ar)
      options[:through] ? ThroughReflection.new(reflection) : reflection
    end


现在看回define_accessors


它分为三层,分别在BelongsTo,SingularAssociation,Association,每次都先super。

先看看Association的

def self.define_accessors(model, reflection)
  mixin = model.generated_association_methods
  name = reflection.name
  define_readers(mixin, name)
  define_writers(mixin, name)
end

def self.define_readers(mixin, name)
  mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
    def #{name}(*args)
      association(:#{name}).reader(*args)
    end
  CODE
end

def self.define_writers(mixin, name)
  mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
    def #{name}=(value)
      association(:#{name}).writer(value)
    end
  CODE
end


generated_association_methods是每次都返回同一个module GeneratedAssociationMethods的,以便Association的每个层次的子类都能在其中定义accessors让model上溯得到

def generated_association_methods
  @generated_association_methods ||= begin
    mod = const_set(:GeneratedAssociationMethods, Module.new)
    include mod
    mod
  end
end


定义完comment.article和comment.article=后,到SingularAssociation了。这里产生build_article,create_article,create_article!

def self.define_accessors(model, reflection)
  super
  mixin = model.generated_association_methods
  name = reflection.name

  define_constructors(mixin, name) if reflection.constructable?

  mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
    def reload_#{name}
      association(:#{name}).force_reload_reader
    end
  CODE
end

# Defines the (build|create)_association methods for belongs_to or has_one association
def self.define_constructors(mixin, name)
  mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
    def build_#{name}(*args, &block)
      association(:#{name}).build(*args, &block)
    end

    def create_#{name}(*args, &block)
      association(:#{name}).create(*args, &block)
    end

    def create_#{name}!(*args, &block)
      association(:#{name}).create!(*args, &block)
    end
  CODE
end


最后是BelongsTo

def self.define_accessors(mixin, reflection)
  super
  add_counter_cache_methods mixin
end


再回到Reflection.add_reflection,所做的就是给model的_reflections加入刚才生成的reflection

def self.add_reflection(ar, name, reflection)
  ar.clear_reflections_cache
  ar._reflections = ar._reflections.merge(name.to_s => reflection)
end


完整trace如下

belongs_to.html