使用Rails迁移添加数据库列,并根据另一个列填充它。

12

我正在撰写一个迁移脚本,目的是向表中添加一列。该列的值取决于另外两列的值。请问最佳和最快的方法是什么? 目前我已经有了以下代码,但不确定它是否是最好的方式,因为groups表可能非常大。

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

为什么向下滚动会出现向上没有出现的问题? - Robert
这只是我在编辑时犯的一个笔误 ;) - user1404536
我不喜欢看到像这样的空置 'down' 方法。这会给你一种虚假的安全感,认为当你回滚时确实已经回滚了,而实际上并非如此。'down' 方法应该把事情恢复到 'up' 运行之前的状态,如果你不能做到这一点,那么就完全留下它,这样当你尝试回滚时,你就明确地被告知不能这样做。 - Toby 1 Kenobi
4个回答

19
通常不建议在数据库迁移中引用模型,因为迁移按顺序执行并随着执行更改数据库状态,但是您的模型没有版本控制。不能保证当编写迁移时的模型在将来与迁移代码兼容。
例如,如果将来更改了is_activeis_live属性的行为,则此迁移可能会出现问题。这个旧的迁移将首先针对新的模型代码运行,并且可能失败。在您的基本示例中,这可能不会出现问题,但在部署过程中,当添加字段并且验证无法运行时,我曾经遇到过此类问题(我知道您的代码正在跳过验证,但总体上这是一个问题)。
我的首选解决方案是使用纯SQL执行此类所有迁移。看起来您已经考虑过这一点,因此我将假设您已经知道该怎么做。
如果您有一些复杂的业务逻辑或者只是希望代码看起来更像Rails,那么另一个选择是在迁移文件本身中包含模型的基本版本在编写迁移时存在的。例如,您可以将以下类放入迁移文件中:
class Group < ActiveRecord::Base
end

在您的情况下,这已经足够保证模型不会出错。假设activelive是表中的布尔字段(因此无论何时运行此迁移,它们都将是),您根本不需要任何其他代码。如果您有更复杂的业务逻辑,可以将其包含在此迁移特定版本的模型中。

甚至可以考虑将整个方法从您的模型复制到迁移版本中。如果您这样做,请记住,在那里不应引用应用程序中的任何外部模型或库,如果它们有可能在未来发生变化。这包括gem,甚至可能包括一些核心Ruby / Rails类,因为gem中的API破坏性更改非常常见(我看着你,Rails 3.0、3.1和3.2!)。


8
我建议您进行三个查询。始终利用数据库而不是在数组中循环一堆项目。我认为像这样的东西可以起作用。
为了写下这篇文章,我会假设is_active检查一个名为active的字段,其中1代表活动状态。我也会假设live也是一样的。
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 documentation


抱歉,我的应用程序没有使用Rails 3。还是谢谢! - user1404536
非常感谢你,Robert。这正是我需要的。 - user1404536

1
在类似的情况下,我最终使用 add_column 添加了该列,然后使用直接 SQL 更新了该列的值。如 Jim Stewart's answer 所述,我使用了直接 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

1

我会在一个

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


2
谢谢,但该列应为所有现有组预填充。这仅适用于新创建的组。 - user1404536

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接