ActiveRecord::AttributeMethods

ActiveRecord::Base是如何生成attributes_methods?
关于这个问题相信看过《Ruby元编程》一书的伙伴能了解到从,rails1到3的介绍。但是,rails发展至今已经到rails6了,整个实现也发生了天差地别的变化。话不多说,上代码:

追溯到代码的源头:ActiveRecord::Core
在ActiveRecor::Base实例初始化的时候,调用了define_attribute_methods:

def initialize(attributes = nil)
  @new_record = true
  @attributes = self.class._default_attributes.deep_dup

  init_internals
  initialize_internals_callback

  assign_attributes(attributes) if attributes

  yield self if block_given?
  _run_initialize_callbacks
end

def init_internals
  @primary_key              = self.class.primary_key
  @readonly                 = false
  @destroyed                = false
  @marked_for_destruction   = false
  @destroyed_by_association = nil
  @_start_transaction_state = nil
  @transaction_state        = nil

  self.class.define_attribute_methods
end

define_attribute_methods是ActiveRecord::AttributesRecords里的一个类方法:

def define_attribute_methods # :nodoc:
  return false if @attribute_methods_generated
  # Use a mutex; we don't want two threads simultaneously trying to define
  # attribute methods.
  generated_attribute_methods.synchronize do
    return false if @attribute_methods_generated
    superclass.define_attribute_methods unless base_class?
    super(attribute_names)
    @attribute_methods_generated = true
  end
end

代码很简单,在调用父类方法的基础上包装了,防止重复调用,且只能互斥锁的形式防止多线程调用。

然后我们看到父类的方法,也就是在ActiveModel::AttributeMethods里:

included do
  class_attribute :attribute_aliases, instance_writer: false, default: {}
  class_attribute :attribute_method_matchers, instance_writer: false, default: [ ClassMethods::AttributeMethodMatcher.new ]
end

首先在include父类的时候会创建一个attribute_method_matchers,来存放不同生成不同attributes的方法,且默认在数组里创建一条没有prefix和suffix的macher(就是每个示例的实行方法比如order.user_id)。

可以通过以下两个方法为matchers的数据添加不同规则的attribute methods:

def attribute_method_prefix(*prefixes)
  self.attribute_method_matchers += prefixes.map! { |prefix| AttributeMethodMatcher.new prefix: prefix }
  undefine_attribute_methods
end

def attribute_method_suffix(*suffixes)
  self.attribute_method_matchers += suffixes.map! { |suffix| AttributeMethodMatcher.new suffix: suffix }
  undefine_attribute_methods
end

在ActiveRecord::AttributeMethods里我们include了我们需要的methods生成模块:

included do
  initialize_generated_modules
  include Read
  include Write
  include BeforeTypeCast
  include Query
  include PrimaryKey
  include TimeZoneConversion
  include Dirty
  include Serialization

  delegate :column_for_attribute, to: :class
end

比如Writer模块里:

included do
  attribute_method_suffix "="
end

module ClassMethods # :nodoc:
  private

    def define_method_attribute=(name)
      ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
        generated_attribute_methods, name, writer: true,
      ) do |temp_method_name, attr_name_expr|
        generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
          def #{temp_method_name}(value)
            name = #{attr_name_expr}
            _write_attribute(name, value)
          end
        RUBY
      end
    end
end

include的时候会将 加入一个 suffix为=的matcher到matchers里。

最后就是遍历所有的matchers根据相应的规则生成对应的attribute methods,有generate_methods的会生成对应的方法,就像writer模块里的define_method_attribute=方法。

def define_attribute_methods(*attr_names)
  attr_names.flatten.each { |attr_name| define_attribute_method(attr_name) }
end

def define_attribute_method(attr_name)
  attribute_method_matchers.each do |matcher|
    method_name = matcher.method_name(attr_name)

    unless instance_method_already_implemented?(method_name)
      generate_method = "define_method_#{matcher.target}"

      if respond_to?(generate_method, true)
        send(generate_method, attr_name.to_s)
      else
        define_proxy_call true, generated_attribute_methods, method_name, matcher.target, attr_name.to_s
      end
    end
  end
  attribute_method_matchers_cache.clear
end

你可能感兴趣的:(ActiveRecord::AttributeMethods)