我正在将应用程序升级到Rails 4.2,并且遇到了一个问题,即序列化为数组的字段中的nil值被解释为空数组。有没有办法让Rails 4.2区分nil和serialized-as-Array属性的空数组?
顶级问题演示:
#[old_app]
> Rails.version
=> "3.0.3"
> a = AsrProperty.new; a.save; a.keeps
=> nil
#[new_app]
> Rails.version
=> "4.2.3"
> a = AsrProperty.new; a.save; a.keeps
=> []
但是我的代码区分nil和[]很重要,所以这是一个问题。
模特:
class AsrProperty < ActiveRecord::Base
serialize :keeps, Array
#[...]
end
我认为问题在于Rails决定通过在数据库中将该类型的空实例存储为nil,将序列化为特定类型(例如Array)的属性的快捷方式。通过查看每个应用程序中执行的SQL语句可以看出这一点:
[old_app]:INSERT INTO
asr_properties
(lock_version
,keeps
) VALUES(0,NULL)
请注意,为清楚起见,上面的日志行已经过编辑;由于旧的Rails行为,还有其他序列化属性正在写入。
[new_app]:INSERT INTO
asr_properties
(lock_version
) 价值观(0)
有一种解决方法:通过删除序列化中的“数组”声明,Rails被强制保存[]和{}不同:
class AsrProperty < ActiveRecord::Base
serialize :keeps #NOT ARRAY
#[...]
end
将保存[]时生成的语句更改为:
INSERT INTO
asr_properties
(keeps
,lock_version
)VALUES('--- [] \ n',0)
允许:
> a = AsrProperty.new; a.save; a.keeps
=> nil
我现在将使用此解决方法,但是: (1)我觉得声明类型可能会提高效率,并通过明确禁止存储错误的数据类型来防止错误 (2)如果Rails确实允许的话,我真的想找出“正确”的方法。
那么:可以告诉Rails 4.2将[]作为自己的东西存储在serialized-as-Array属性中吗?
答案 0 :(得分:2)
您遇到的是由于Rails 4如何处理serialize
来电的第二个参数。它根据参数可以具有的三个不同值来更改其行为(在解决方案中更多内容)。最后一个分支here是我们感兴趣的分支,因为当您传递Array
类时,它会被传递给创建的ActiveRecord::Coders::YAMLColumn
实例。 load
方法从数据库接收YAML并尝试将其转换回Ruby对象here。如果编码器未在Object
列的情况下被赋予默认类yaml
且nil
参数为null
,则它将返回该类的新实例因此是空数组。
似乎没有一种简单的Rails-y方式可以说,&#34;嘿,如果数据库中有null
,请给我nil
。&#34 ;但是,查看第二个分支here,我们发现我们可以传递任何实现load
和dump
方法的对象或我称之为基本编码器协议的对象。
我团队的一名成员构建了这个简单的类来处理这种情况。
class NullableSerializer < ActiveRecord::Coders::YAMLColumn
def load(yaml)
return nil if yaml.nil?
super
end
end
此类继承自YAMLColumn
提供的相同ActiveRecord
类,因此它已处理load
和dump
方法。我们不需要对dump
进行任何修改,但我们希望稍微处理不同的加载。我们只是告诉它在数据库列为空时返回nil
,否则调用super
就好像我们没有做其他修改一样。
要使用它,只需使用您想要的序列化类进行实例化,并使用上面的命名传递给Rails serialize
方法,如下所示:
class AsrProperty < ActiveRecord::Base
serialize :keeps, NullableSerializer.new(Array)
# …
end
完成任务并获取代码是最重要的,我希望这对您有所帮助。毕竟,如果代码没有被使用并且做得好,谁会关心它的理想程度呢?
我认为Rails&#39;在这种情况下,方法是正确的方法,特别是考虑到Ruby的“最小惊喜原则”的哲学。当一个属性可能是一个数组时,它应该总是返回该类型,即使是空的,以避免不得不经常使用特殊情况nil
。对于任何可以合理默认的数据库列,我都会争论相同(即t.integer :anything_besides_a_foreign_key, default: 0
)。每当我遇到意外的NoMethodError: undefined method 'whatever' for nil:NilClass
时,我总是感谢过去的Aaron记住这个时间。我nil
的特殊情况几乎总是提供合理的默认值。
这在很大程度上取决于您,您的团队,您的应用以及您的应用及其需求,因此它永远不会变得困难和快速。当我正在处理某些事情并想知道amount
是否可以默认为0
或者是否有某些事情时,我发现的这些东西可以帮助我极大地帮助我为什么需要能够nil
隐藏在代码或队友心中的原因。