Reflection 是 ar 用来记录 model 的 associations & aggregations 配置的一个类,不同的方法会创建不同的 Reflection 类型
composed_of 方法会创建一个 AggregateReflection 实例,而 has_many 方法会创建一个 AssociationReflection 实例
# Holds all the methods that are shared between MacroReflection and ThroughReflection.
#
# AbstractReflection
# MacroReflection
# AggregateReflection
# AssociationReflection
# HasManyReflection
# HasOneReflection
# BelongsToReflection
# HasAndBelongsToManyReflection
# ThroughReflection
# PolymorphicReflection
# RuntimeReflection
Reflection 由统一的一个 builder 来进行实例化,下面的 create 方法创建一个 Reflection,它会把一条 dsl 语句 tokenize 成各种属性集,用来对 association 进行实际的配置
module ActiveRecord
# = Active Record Reflection
module Reflection # :nodoc:
extend ActiveSupport::Concern
included do
class_attribute :_reflections, instance_writer: false
class_attribute :aggregate_reflections, instance_writer: false
self._reflections = {}
self.aggregate_reflections = {}
end
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
...
end
比如下面这条语句会生成的 Reflection 如下,然后在创建 association 方法的时候,会调取这个 Reflection 来查询配置
has_many :tracks, -> { order("position") }, dependent: :destroy
#<ActiveRecord::Reflection::HasManyReflection:0x007fc4ccd7e310
@active_record=
CheckCard(id: integer, name: string, title: string, level: integer, submit_times: integer, finish_times: integer, created_at: datetime, updated_at: datetime, user_id: integer, period: string),
@association_scope_cache={},
@automatic_inverse_of=nil,
@constructable=true,
@foreign_type="tracks_type",
@klass=nil,
@name=:tracks,
@options={:dependent=>:destroy},
@plural_name="tracks",
@scope=#<Proc:0x007fc4ccd7e338@/usr/local/lib/ruby/gems/2.4.0/gems/activerecord-5.0.1/lib/active_record/associations/builder/association.rb:55>,
@scope_lock=#<Thread::Mutex:0x007fc4ccd7e158>,
@type=nil>]
在 association 设置完毕后,这个 Reflection 会被加入 model 的 Reflections 列表里,可以用 reflect_on_all_associations 方法打印出这个表,有什么用呢?文档上是这么说的
This information, for example, can be used in a form builder that takes an Active Record object and creates input fields for all of the attributes depending on their type and displays the associations to other objects.
Reflection 的实例除了保存配置以外,还存在一个 cache 和一个互斥锁。当进行一次 association 查询以后,相应的 statement 就会存到这个 cache 里,所以重新进行相同的 association 查询时要进行 reload 操作
module Associations # :nodoc
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
module ClassMethod
def has_many(name, scope = nil, options = {}, &extension)
reflection = Builder::HasMany.build(self, name, scope, options, &extension)
Reflection.add_reflection self, name, reflection
end
end
end
has_many 方法只有两行,它的实现方式全部都被封装到了 ActiveRecord::Associations::Builder::HasMany 类里
Builder::HasMany.build 方法不仅创建了 reflection,还同时对 association 进行了配置
这里类的继承关系是 Builder::HasMany < Builder::CollectionAssociation < Builder::Association
# The hierarchy is defined as follows:
# Association
# - SingularAssociation
# - BelongsToAssociation
# - HasOneAssociation
# - CollectionAssociation
# - HasManyAssociation
Builder::HasMany.build 方法由父类 Builder::Association 类来实现,它创建了一个 Reflection,定义了各种 accessors 方法,以及 callback 和 validations
module ActiveRecord::Associations::Builder # :nodoc:
class Association #:nodoc:
def self.build(model, name, scope, options, &block)
if model.dangerous_attribute_method?(name)
raise ArgumentError, "You tried to define an association named #{name} on the model #{model.name}, but " \
"this will conflict with a method #{name} already defined by Active Record. " \
"Please choose a different association name."
end
extension = define_extensions model, name, &block
reflection = create_reflection model, name, scope, options, extension
define_accessors model, reflection
define_callbacks model, reflection
define_validations model, reflection
reflection
end
接下来逐步看一下它的运行流程,首先检查一下方法名称是否已经被定义,然后生成一个 extension。define_extensions 方法在 CollectionAssociation 类里
In Model...
class Author < ApplicationRecord
has_many :books do
def find_by_book_prefix(book_number)
find_by(category_id: book_number[0..2])
end
end
end
method...
def self.define_extensions(model, name)
if block_given?
extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
extension = Module.new(&Proc.new)
model.parent.const_set(extension_module_name, extension)
end
end
老实说这个方法,呃,十分有趣
首先假如 has_many 方法有 block,这个 block 会被传递给 Proc.new,然后再用&符号转成 block。也就是说,原来的 block 被通过一个 Proc 的中转交给了新建的 Module
这个新建的 Module 会立即在他的 scope 下运行这个 block。所以上面定义的这个 find_by_book_prefix 方法在新建的这个 Module 上被定义了
最后把新建的这个 Module 绑定在一个常量上,这个常量属于 model 的 parent 类,总之看起来既让人迷惑又觉得很酷
这个新建 Module 有什么用呢?其实和用 lambda 定义的 scope 作用是一样的,如果你同时用了 lambda 定义 scope,那么它们会呈链式调用
def self.wrap_scope(scope, mod)
if scope
if scope.arity > 0
proc { |owner| instance_exec(owner, &scope).extending(mod) }
else
proc { instance_exec(&scope).extending(mod) }
end
else
proc { extending(mod) }
end
end
接下来是创建 Reflection 的方法
def self.create_reflection(model, name, scope, options, extension = nil)
raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
if scope.is_a?(Hash)
options = scope
scope = nil
end
validate_options(options)
scope = build_scope(scope, extension)
ActiveRecord::Reflection.create(macro, name, scope, options, model)
end
首先检查参数,允许没有 lambda 的情况,options 提前。然后校验 options 的 keys 是否合法。合并 lambda 和 extension,最后创建 reflection
接下来是生成 accessor 方法
def self.define_accessors(model, reflection)
mixin = model.generated_association_methods
name = reflection.name
define_readers(mixin, name)
define_writers(mixin, name)
end
def generated_association_methods
@generated_association_methods ||= begin
mod = const_set(:GeneratedAssociationMethods, Module.new)
include mod
mod
end
end
这里生成的 accessor 方法并不是直接定义在 model 上,而是先新建一个 Module,include 到 model 里,然后再把各种方法定义到这个 Module 上。我猜这样做是为了可以让你自己在 model 定义同名方法,并使用 default_scope 调用原方法
Builder::Association 会定义类似 student.books 以及 student.books=这两个方法,Builder:: CollectionAssociation 扩展定义了_ids 方法
In super...
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
In child...
# Defines the setter and getter methods for the collection_singular_ids.
def self.define_readers(mixin, name)
super
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name.to_s.singularize}_ids
association(:#{name}).ids_reader
end
CODE
end
def self.define_writers(mixin, name)
super
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name.to_s.singularize}_ids=(ids)
association(:#{name}).ids_writer(ids)
end
CODE
end
再接下来是定义 callbacks,关于 callbacks 链可以见上一篇文章
这里没有做什么特别的事情,只是从 Reflection 中提取 callback 的配置,然后写到相对应的 callbacks 链中
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
def self.define_callbacks(model, reflection)
super
name = reflection.name
options = reflection.options
CALLBACKS.each { |callback_name|
define_callback(model, callback_name, name, options)
}
end
def self.define_callback(model, callback_name, name, options)
full_callback_name = "#{callback_name}_for_#{name}"
# TODO : why do i need method_defined? I think its because of the inheritance chain
model.class_attribute full_callback_name unless model.method_defined?(full_callback_name)
callbacks = Array(options[callback_name.to_sym]).map do |callback|
case callback
when Symbol
->(method, owner, record) { owner.send(callback, record) }
when Proc
->(method, owner, record) { callback.call(owner, record) }
else
->(method, owner, record) { callback.send(method, owner, record) }
end
end
model.send "#{full_callback_name}=", callbacks
end
最后 define_validations,Builder::HasMany 并没有覆写这个方法,所以这里什么也没干
声明 associations 的各个方法 belongs_to、has_many 的运行过程分两步。一是创建 Reflection,二是把 Reflection 当参数丢过去来进行实际配置。
运行过程由 Builder 控制,因为 associations 的 dsl 基本都需要创建读写命令和 callbacks,流程差不多。所以它们的配置方法由父类来定义,然后具体的实现细节由不同的子类来实现。