我有一个带有text类型的朋友列的用户模型。运行此迁移以使用postgres的数组功能:
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
以下规范通过,直到我在调用user.reload
后添加#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
方法之前的状态。
使用Rails 4.0.0.rc2和pg 0.15.1
这可能是什么?
答案 0 :(得分:24)
我怀疑ActiveRecord没有注意到你的friends
数组已经改变了,因为当你执行时,底层数组引用不会改变:
self.friends.push(target)
这会改变数组的内容,但数组本身仍然是同一个数组。我知道这个问题与Rails3中的postgres_ext gem密切相关并且给出了this issue:
当字符串属性随
更改时,不会标记为脏<<
我希望Rails4的行为方式相同。
解决方案是创建一个新数组,而不是尝试就地修改数组:
update_attributes friends: self.friends + [ target ]
在向现有数组添加元素的同时,有很多方法可以创建新数组,请使用您喜欢的数组。
答案 1 :(得分:8)
看起来问题可能是您使用push
,它会修改数组。
我无法找到更多主要来源atm,但this post说:
在模型上与数组(或其他可变值)交互时需要注意的一件重要事项。 ActiveRecord目前不会跟踪“破坏性”或就地更改。这些包括数组
push
和pop
,advance
- DateTime对象。如果您想使用“破坏性”更新,则必须致电<attribute>_will_change!
让ActiveRecord知道您更改了该值。
答案 2 :(得分:0)
如果要使用Postgresql数组类型,则必须遵守其格式。从Postgresql docs开始,输入格式为
'{10000, 10000, 10000, 10000}'
这不是friends.to_s
将返回的内容。在红宝石中:
[1,2,3].to_s => "[1,2,3]"
即括号而不是括号。你必须自己做转换。
但是我更倾向于依赖ActiveRecord序列化(参见serialize)。数据库不需要知道该值实际上是一个数组,这是您的域模型泄漏到您的数据库中。让Rails做它的事情并封装那些信息;它已经知道如何序列化/反序列化该值。
注意:此回复适用于Rails 3,而不是4.我将离开此处,以防将来帮助某人。