新数据不会持久化到Postgres上的Rails数组列
我有一个types为text的朋友列的用户模型。 这个迁移被运行到与postgres一起使用数组function:
add_column :users, :friends, :text, array: true
用户模型有这个方法:
def add_friend(target) #target would be a value like "1234" self.friends = [] if self.friends == nil update_attributes friends: self.friends.push(target) end
下面的规范通过,直到我调用#add_friend
后添加#add_friend
:
it "adds a friend to the list of friends" do user = create(:user, friends: ["123","456"]) stranger = create(:user, uid: "789") user.add_friend(stranger.uid) user.reload #turns the spec red user.friends.should include("789") user.friends.should include("123") end
这也发生在开发中。 模型实例被更新并且在数组中具有新的uid,但是一旦在不同的动作中重新加载或重新加载用户,它就恢复到调用add_friend
方法之前的add_friend
。
使用Rails 4.0.0.rc2和pg 0.15.1
这可能是什么?
我怀疑ActiveRecord没有注意到你的friends
数组已经改变了,因为当你:底层数组引用不会改变。
self.friends.push(target)
这将改变数组的内容 ,但数组本身仍然是相同的数组。 我知道这个问题在Rails3中遇到了postgres_ext gem ,并给出了这个问题 :
string属性没有被标记为脏,当它改变
<<
我期望Rails4行为相同的方式。
解决scheme是创build一个新的数组,而不是试图就地修改数组:
update_attributes friends: self.friends + [ target ]
有很多方法可以创build一个新的数组,而添加一个元素到现有的数组,使用你喜欢的任何一个。
看起来这个问题可能是你使用了push
,这个修改了这个数组。
我找不到更多的主要来源atm,但这个职位说:
在模型上与数组(或其他可变值)进行交互时需要注意一点。 ActiveRecord目前不跟踪“破坏性”,或就地更改。 这些包括arrays
push
和pop
,advance
date时间对象。 如果你想使用“破坏性”更新,你必须调用<attribute>_will_change!
让ActiveRecord知道你改变了这个价值。
如果你想使用Postgresql数组types,你必须遵守它的格式。 从Postgresql文档的input格式是
'{10000, 10000, 10000, 10000}'
这不是什么friends.to_s
会返回。 在ruby:
[1,2,3].to_s => "[1,2,3]"
那就是括号,而不是括号。 你必须自己做转换。
不过,我宁愿依靠ActiveRecord序列化(请参阅序列化 )。 数据库不需要知道该值实际上是一个数组,这是您的域模型泄漏到您的数据库。 让Rails做它的事情,并封装这些信息; 它已经知道如何序列化/反序列化值。
注意:这个回应适用于Rails 3,而不是4.我会离开这里,以防将来帮助别人。