Rails 使用 serialize 在 db 中储存 hash 对象时遇到的一个小问题

adamshen · 2016年12月12日 · 2202 次阅读

更改 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
adamshen 关闭了讨论。 12月13日 11:26
需要 登录 后方可回复, 如果你还没有账号请 注册新账号