更改 attibutes 方法生成出来的 hash 对象再 save 以后,会改变数据库里实际所存储的值
class Request < ActiveRecord::Base
serialize :options
end
request = Request.last
=> options: {level: 7}
request_hash = request.attributes
request_hash['options'] = {level: 0}
request.save
request.reload
request => options: {level: 0}
造成这个问题的原因是 attributes 方法得到的 hash 里,key options 所指向的 obj 和 ActiveRecord::Attribute::FromDatabase 实例里 value 变量所指向的 obj 是同一个 obj
request.options.equal?(request.attributes['options'])
=> true
AD 的 instance 里用 attributes 变量暂存 record 的值
# From: /opt/rubies/ruby-2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.0.0.1/lib/active_record/attribute_methods.rb @ line 275:
def attributes
@attributes.to_hash
end
它是一个 ActiveRecord::AttributeSet 的实例
每一个 column 的 value 用 ActiveRecord::Attribute::FromDatabase 的实例来存储
request.instance_variable_get(:@attributes)
=> #<ActiveRecord::AttributeSet:0x00000008d8e930
@attributes=
#<ActiveRecord::LazyAttributeHash:0x00000008d8e980
@additional_types={},
@delegate_hash=
{"options"=>
#<ActiveRecord::Attribute::FromDatabase:0x00000008d86de8
@name="options",
@original_attribute=nil,
@type=#<ActiveModel::Type::Text:0x00000000f73668 @limit=nil, @precision=nil, @scale=nil>,
@value=
{:level => 0}
}
当使用 attributes 方法时,只是取出 ActiveRecord::Attribute::FromDatabase 实例的 value 变量指向的对象,没有进行 dup,所以产生了上述问题
# From: /opt/rubies/ruby-2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.0.0.1/lib/active_record/attribute_set.rb @ line 21:
def to_hash
initialized_attributes.transform_values(&:value)
end