新数据不会持久化到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目前不跟踪“破坏性”,或就地更改。 这些包括arrayspushpopadvancedate时间对象。 如果你想使用“破坏性”更新,你必须调用<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.我会离开这里,以防将来帮助别人。