Подтвердить что ты не робот

Новые данные, не сохраняющиеся в столбце массива Rails в Postgres

У меня есть модель пользователя с колонкой друзей типа 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

Что это может быть?

4b9b3361

Ответ 1

Я подозреваю, что ActiveRecord не замечает, что ваш массив friends изменился, потому что ссылка на базовый массив не изменяется, если вы:

self.friends.push(target)

Это изменит содержимое массива, но сам массив все равно будет тем же самым массивом. Я знаю, что эта проблема возникает с postgres_ext gem в Rails3 и дается this вопрос:

Атрибут String не помечен как грязный, когда он изменяется с помощью <<

Я бы ожидал, что Rails4 будет вести себя одинаково.

Решение состоит в том, чтобы создать новый массив, а не пытаться изменить массив на месте:

update_attributes friends: self.friends + [ target ]

Существует множество способов создания нового массива при добавлении элемента в существующий массив, используйте тот, который вам нравится.

Ответ 2

Похоже, проблема может заключаться в использовании push, который изменяет массив на месте.

Я не могу найти более первичный источник atm, но этот пост говорит:

Важно отметить, что при взаимодействии с массивом (или другими изменяемыми значениями) на модели. В настоящее время ActiveRecord не отслеживает "деструктивные" или не приводит к изменениям. К ним относятся массивы push ing и pop ing, advance -ing объекты DateTime. Если вы хотите использовать "разрушительное" обновление, вы должны вызвать <attribute>_will_change!, чтобы ActiveRecord знал, что вы изменили это значение.

Ответ 3

Если вы хотите использовать тип массива Postgresql, вам придется соблюдать его формат. Из Postgresql docs формат ввода

'{10000, 10000, 10000, 10000}'

который не будет возвращен friends.to_s. В рубине:

[1,2,3].to_s => "[1,2,3]"

То есть, скобки вместо брекетов. Вам нужно будет сделать преобразование самостоятельно.

Однако я бы скорее полагался на сериализацию ActiveRecord (см. serialize). База данных не должна знать, что это значение фактически представляет собой массив, который ваша модель домена протекает в вашу базу данных. Пусть Rails сделает свое дело и инкапсулирует эту информацию; он уже знает, как сериализовать/десериализовать значение.

Примечание. Этот ответ применим к Rails 3, а не 4. Я останусь здесь, если он поможет кому-то в будущем.