跟踪下read_attribute干什么,顺便对比下:[]和直接调用attr_reader那样的方法

[11] pry(main)> binding.trace_tree(htmp: 'ar/read_attr'){ ss.read_attribute(:gender) }
[17] pry(main)> binding.trace_tree(htmp: 'ar/read_attr_bracket'){ mary[:gender] }
[21] pry(main)> binding.trace_tree(htmp: 'ar/reader'){ mary.gender }


得调用栈如下

20171104_214303_741_read_attr.html

20171104_215958_375_read_attr_bracket.html

20171104_223106_686_reader.html

可见最终都会调到_read_attribute。

而:[]形式会传一个block,以使在字段名找不到时报ActiveModel::MissingAttributeError,但根据ActiveRecord::Attribute#value的做法,此block被忽略(至少rails 5.1.2是这样)

而attr_reader形式,来自以下定义

# We want to generate the methods via module_eval rather than
# define_method, because define_method is slower on dispatch.
# Evaluating many similar methods may use more memory as the instruction
# sequences are duplicated and cached (in MRI).  define_method may
# be slower on dispatch, but if you're careful about the closure
# created, then define_method will consume much less memory.
#
# But sometimes the database might return columns with
# characters that are not allowed in normal method names (like
# 'my_column(omg)'. So to work around this we first define with
# the __temp__ identifier, and then use alias method to rename
# it to what we want.
#
# We are also defining a constant to hold the frozen string of
# the attribute name. Using a constant means that we do not have
# to allocate an object on each call to the attribute method.
# Making it frozen means that it doesn't get duped when used to
# key the @attributes in read_attribute.
def define_method_attribute(name)
  safe_name = name.unpack("h*".freeze).first
  temp_method = "__temp__#{safe_name}"

  ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name

  generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
    def #{temp_method}
      name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
      _read_attribute(name) { |n| missing_attribute(n, caller) }
    end
  STR

  generated_attribute_methods.module_eval do
    alias_method name, temp_method
    undef_method temp_method
  end
end


此做法有以下要点:可能存在有些数据库字段的名字,如“my_column(omg)”,不能用def关键字来定义方法名,只能用define_method;但define_method性能不及def;为了使用def,需先将字段名转换成合适的形式(safe_name,再temp_method),然后def,再用alias_method和undef_method来重命名为原本的字段名(当然,对于“my_column(omg)”,要读写还是只能send);为避免每次调用attr_reader形式都做safe_name转换,要把它设成constant

基本上,write的也是类似,如下

20171104_232553_053_write_attr.html

20171104_232919_635_write_attr_bracket.html

20171104_232818_854_writer.html