LazyAttributeHash这东西,是在研究dirty.rb的时候发现的

20170717_224156_650_attr_changed_question.html

可以看到,ActiveRecord::AttributeSet#[]方法还委托到了ActiveRecord::LazyAttributeHash#[]。从名字来推测,可能是延迟转型。搜索源码,看哪里会用到它

$ gems git:(master) ag LazyAttributeHash
activerecord-5.1.2/lib/active_record/attribute_set/builder.rb
19:        attributes = LazyAttributeHash.new(types, values, additional_types, &default)
25:  class LazyAttributeHash # :nodoc:
74:      if other.is_a?(LazyAttributeHash)


范围很小,仅仅就在ActiveRecord::AttributeSet::Builder,而且从方法名build_from_database来看,应该是在新建model时产生的,然后塞到AttributeSet中

module ActiveRecord
  class AttributeSet
    class Builder
      attr_reader :types, :always_initialized, :default

      def initialize(types, always_initialized = nil, &default)
        @types = types
        @always_initialized = always_initialized
        @default = default
      end

      def build_from_database(values = {}, additional_types = {})
        if always_initialized && !values.key?(always_initialized)
          values[always_initialized] = nil
        end

        attributes = LazyAttributeHash.new(types, values, additional_types, &default)
        AttributeSet.new(attributes)
      end
    end
  end


于是可从数据库读取model,并跟踪看看怎样到达build_from_database这里

[43] pry(main)> binding.trace_tree(htmp: 'rails/new_model_from_database'){Student.first}
  Student Load (30.1ms)  SELECT  "students".* FROM "students" ORDER BY "students"."no" ASC LIMIT ?  [["LIMIT", 1]]
=> #<student:0x007f243bf57808 no:="" "1",="" name:="" "b",="" created_at:="" sun,="" 02="" jul="" 2017="" 04:10:00="" utc="" +00:00,="" updated_at:="" tue,="" 11="" jul="" 2017="" 14:11:44="" utc="" +00:00,="" gender:="" "male",="" grade:="" 2,="" alias_names:="" ["jay",="" "jj"]=""></student:0x007f243bf57808>


完整调用栈如下

20170717_221712_352_new_model_from_database.html

关键如下


而对于不是从数据库读出的新造model,理论上应该是不需要LazyAttributeHash的,验证一下:

[41] pry(main)> s.instance_variable_get(:@attributes).instance_variable_get(:@attributes).class
=> Hash
[42] pry(main)> s1.instance_variable_get(:@attributes).instance_variable_get(:@attributes).class
=> ActiveRecord::LazyAttributeHash


显而易见,不是从数据库读出的新造model,构造AttributeSet时直接就是用Hash

那么LazyAttributeHash是如何做转型的呢?再跟踪一下

[57] pry(main)> s2 = Student.first;binding.trace_tree(htmp: 'rails/read_attr'){s2.alias_names}
  Student Load (0.1ms)  SELECT  "students".* FROM "students" ORDER BY "students"."no" ASC LIMIT ?  [["LIMIT", 1]]
=> ["jay", "jj"]


完整如下

20170717_231826_419_read_attr.html

从调用栈可发现,转型还不是在LazyAttributeHash中直接处理,而是由它来生成一个Attribute的子类FromDatabase的对象。而Attribute的子类除了FromDatabase还有FromUser、Null,它们都有自己一套type_cast方法