21

我有一个带有文本类型的朋友列的用户模型。运行此迁移以将数组功能与 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

这可能是什么?

4

3 回答 3

25

我怀疑 ActiveRecord 没有注意到您的friends数组已更改,因为当您:

self.friends.push(target)

这将改变数组的内容,但数组本身仍然是同一个数组。我知道Rails3中的postgres_ext gem出现了这个问题,并给出了这个问题

字符串属性没有被标记为脏,当它改变时<<

我希望 Rails4 的行为方式相同。

解决方案是创建一个新数组,而不是尝试就地修改数组:

update_attributes friends: self.friends + [ target ]

有很多方法可以在向现有数组添加元素的同时创建新数组,使用您喜欢的任何一种。

于 2013-06-24T20:46:13.220 回答
9

看起来问题可能是您使用push,它修改了数组。

我找不到更主要的来源 atm 但这篇文章说:

与模型上的数组(或其他可变值)交互时要注意的一件重要事情。ActiveRecord 当前不跟踪“破坏性”或原地更改。这些包括 array pushing 和poping, advance-ing DateTime 对象。如果您想使用“破坏性”更新,您必须调用<attribute>_will_change!让 ActiveRecord 知道您更改了该值。

于 2013-06-25T06:04:29.597 回答
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。我会离开这里以防将来对某人有所帮助。

于 2013-06-24T20:34:07.037 回答