Rails 3.1: невозможно записать в столбец в той же миграции, которая его добавляет

У меня была миграция add_column, которая работала нормально. Однако, запустив его и запустив консоль, я обнаружил бы, что столбцы first_name и last_name совершенно пусты. Вместо этого я попытался использовать save!, и это имело тот же эффект - никаких сообщений об ошибках. Вот оригинал:

class UserAddFirstNameAndLastName < ActiveRecord::Migration
  def change
    # add column first name, last name string
    add_column :users, :first_name, :string
    add_column :users, :last_name, :string

    User.all.each do |u|
      u.first_name = 'first name'
      u.last_name = 'last name'
      u.save
    end
  end
end

Я также подумал, что это может быть какая-то проблема с загрузкой класса, поэтому я вставил строку User, чтобы заставить пользовательский класс перезагрузиться перед циклом. Нет игральных костей.

Когда я разделил это на две миграции, желаемый эффект был достигнут. У кого-нибудь есть объяснение этому? Клянусь, я даже делал это в том же проекте с прошлыми миграциями.

Другие примечания: Разработка для пользовательского движка, добавление новых столбцов в attr_accessible в классе User перед запуском миграции.


comment
mu слишком короткий имеет фантастический ответ, чтобы объяснить, что происходит и что делать, но я бы также рекомендовал разделить эти два на разные миграции. Создайте столбцы в одном, заполните в другом. Это предотвращает проблему, которую вы видите, и делает откат менее подверженным ошибкам.   -  person Joshua Pinter    schedule 11.01.2015


Ответы (1)


Вы загружаете класс Users где-то перед запуском миграции, поэтому User немного запутался в своей собственной структуре. Решение состоит в вызове reset_column_information после добавления столбца:

Сбрасывает всю кэшированную информацию о столбцах, что приводит к их перезагрузке при следующем запросе.

Наиболее распространенный шаблон использования этого метода, вероятно, при миграции, когда сразу после создания таблицы вы хотите заполнить ее некоторыми значениями по умолчанию.

Раздел Использование моделей при миграции документа Руководство по миграции тоже стоит посмотреть.

Попробуйте откатиться и использовать миграцию следующим образом:

def change
  # add column first name, last name string
  add_column :users, :first_name, :string
  add_column :users, :last_name, :string

  User.reset_column_information

  User.all.each do |u|
    u.first_name = 'first name'
    u.last_name = 'last name'
    u.save
  end
end

Я проверил это с помощью трех таких миграций:

# 1: Don't touch Model before the new columns.
def change
  add_column :models, :some_column, :string
  Model.all.each { |m| m.some_column = 'pancakes'; m.save }
end

# 2: Pull in Model before adding the new columns.
def change
  puts Model.all.count
  add_column :models, :some_column, :string
  Model.all.each { |m| m.some_column = 'pancakes'; m.save }
end

# 3: Pull in Model before adding the new columns but use reset_column_information
def change
  puts Model.all.count
  add_column :models, :some_column, :string
  Model.reset_column_information
  Model.all.each { |m| m.some_column = 'pancakes'; m.save }
end

Первый работает нормально, второй добавляет some_column, но оставляет значения NULL, третий тоже работает.

Я предполагаю, что что-то в инициализации вашего приложения (возможно, из Devise) вызывает загрузку пользователя и его схемы, а затем вы добавляете столбец. Но, по-видимому, пользователь знает о новом столбце лишь частично, так как вызов u.first_name работает, но что-то кэшируется внутри пользователя, чтобы предотвратить запись атрибута в базу данных.

person mu is too short    schedule 20.01.2012
comment
Подтвердил, что это была проблема с одной оговоркой. Я только что использовал ваш первый и третий примеры вместе в одной миграции. Первый не сохранился в базу данных. Я проверил нашу папку БД и был прав раньше — у меня есть старые миграции с такими сохранениями. Что-то, что мы добавили в наш проект, вызывает предварительную загрузку моделей перед миграцией... Я подозреваю, что это может быть драгоценный камень active_reload. Мы еще не обновились до Rails 3.2, но я заметил, что теперь это стандартная функция. Я надеюсь подтвердить/опровергнуть это тестированием, если у меня будет время. - person Eric Hu; 21.01.2012
comment
@Eric: Что-то в вашей инициализации предварительно загружает класс вашей модели. (1) сработало для меня, потому что в моей инициализации ничего не загружается; puts Model.all.count в (2) и (3) — это простой способ имитировать предварительную загрузку, происходящую в вашей среде. active_reload звучит как вероятный подозреваемый. Добавление reset_column_information, если вы изменяете и используете модель в одной миграции, вероятно, является хорошей привычкой. - person mu is too short; 21.01.2012
comment
@KeesBriggs Я только что проверил подход reset_column_information с Rails 4.2.4, все работает нормально. Что происходит для вас? - person mu is too short; 02.10.2015