добавить столбец базы данных с миграцией Rails и заполнить его на основе другого столбца

Я пишу миграцию, чтобы добавить столбец в таблицу. Значение столбца зависит от значения еще двух существующих столбцов. Каков наилучший/быстрый способ сделать это? В настоящее время у меня есть это, но я не уверен, что это лучший способ, поскольку таблица групп может быть очень большой.

class AddColorToGroup < ActiveRecord::Migration
  def self.up
    add_column :groups, :color, :string
    Groups = Group.all.each do |g|
      c = "red" if g.is_active && is_live 
      c = "green" if g.is_active
      c = "orange"
      g.update_attribute(:type, c)
    end
  end

  def self.down

  end
end

person user1404536    schedule 08.03.2013    source источник
comment
Почему движение вниз меняет то, чего не делало движение вверх?   -  person Robert    schedule 08.03.2013
comment
это просто опечатка, которую я сделал при редактировании здесь;)   -  person user1404536    schedule 08.03.2013


Ответы (4)


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

Например, если в будущем вы измените поведение атрибутов is_active или is_live, эта миграция может прерваться. Эта старая миграция будет выполняться первой для нового кода модели и может завершиться ошибкой. В вашем базовом примере это может не появиться, но это сожгло меня при развертывании до того, как поля были добавлены, а проверки не могли выполняться (я знаю, что ваш код пропускает проверки, но в целом это проблема).

Мое любимое решение — выполнять все миграции такого рода с помощью простого SQL. Похоже, вы уже подумали об этом, поэтому я предполагаю, что вы уже знаете, что там делать.

Другой вариант, если у вас есть какая-то запутанная бизнес-логика или вы просто хотите, чтобы код выглядел более Railsy, ​​— включить базовую версию модели в том виде, в каком она существует на момент написания миграции, в сам файл миграции. Например, вы можете поместить этот класс в файл миграции:

class Group < ActiveRecord::Base
end

В вашем случае этого, вероятно, достаточно, чтобы гарантировать, что модель не сломается. Предполагая, что active и live являются булевыми полями в таблице в настоящее время (и, таким образом, будут всегда, когда эта миграция будет выполняться в будущем), вам вообще не потребуется никакого дополнительного кода. Если у вас есть более сложная бизнес-логика, вы можете включить ее в эту версию модели для миграции.

Вы даже можете подумать о том, чтобы скопировать целые методы из вашей модели в миграционную версию. Если вы это сделаете, имейте в виду, что вы также не должны ссылаться на какие-либо внешние модели или библиотеки в своем приложении, если есть шанс, что они изменятся в будущем. Сюда входят гемы и, возможно, даже некоторые базовые классы Ruby/Rails, потому что изменения в гемах, нарушающие API, очень распространены (я смотрю на вас, Rails 3.0, 3.1 и 3.2!).

person Jim Stewart    schedule 18.03.2013

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

В целях написания этого я предполагаю, что is_active проверяет поле, активное, где 1 активно. Я предполагаю, что вживую тоже самое.

Подход к Rails 3

class AddColorToGroup < ActiveRecord::Migration
  def self.up
    add_column :groups, :color, :string
    Group.where(active: 1, live: 1).update_all(type: "red")
    Group.where(active: 1, live: 0).update_all(type: "green")
    Group.where(active: 0, live: 0).update_all(type: "orange")
   end
 end

Не стесняйтесь просматривать документацию update_all здесь.

Подход Rails 2.x

class AddColorToGroup < ActiveRecord::Migration
  def self.up
    add_column :groups, :color, :string
    Group.update_all("type = red", "active = 1 AND live = 1")
    Group.update_all("type = red", "active = 1 AND live = 0")
    Group.update_all("type = red", "active = 0 AND live = 0")
   end
 end

документация по Rails 2

person Robert    schedule 08.03.2013
comment
:( мое приложение не использует Rails 3. Спасибо! - person user1404536; 08.03.2013
comment
Большое спасибо, Роберт. Это то, что мне нужно. - person user1404536; 08.03.2013

я бы сделал это в

after_create
# or
after_save

в вашей модели ActiveRecord:

class Group < ActiveRecord::Base
  attr_accessor :color

  after_create :add_color

  private

  def add_color
    self.color = #the color (wherever you get it from)
  end

end

или при миграции вам, вероятно, придется сделать такой SQL:

execute('update groups set color = <another column>')

Вот пример в руководствах по Rails:

http://guides.rubyonrails.org/migrations.html#using-the-up-down-methods

person AdamT    schedule 08.03.2013
comment
спасибо, но столбец должен быть предварительно заполнен для всех существующих групп. Это будет работать только для вновь созданных групп. - person user1404536; 08.03.2013

В аналогичной ситуации я добавил столбец, используя add_column, а затем использовал прямой SQL для обновления значения столбца. Я использовал прямой SQL, а не модель согласно ответу Джима Стюарта, поскольку тогда он не зависит от текущего состояния модель по сравнению с текущим состоянием таблицы на основе выполняемых миграций.

class AddColorToGroup < ActiveRecord::Migration
  def up
    add_column :groups, :color, :string
    execute "update groups set color = case when is_active and is_live then 'red' when is_active then 'green' else 'orange' end"
  end

  def down
    remove_column :groups, :color
  end
end
person M. Justin    schedule 20.09.2017