【问题标题】:add a database column with Rails migration and populate it based on another column使用 Rails 迁移添加一个数据库列并基于另一列填充它
【发布时间】:2013-03-08 00:20:37
【问题描述】:

我正在编写迁移以向表中添加列。该列的值取决于另外两个现有列的值。最好/最快的方法是什么? 目前我有这个但不确定这是否是最好的方法,因为组表可能非常大。

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

【问题讨论】:

  • 为什么向下反转了向上没有做的事情?
  • 这只是我在这里编辑时打错了;)

标签: ruby-on-rails activerecord migration rails-activerecord rails-migrations


【解决方案1】:

像这样从迁移中引用模型通常是个坏主意。问题是迁移按顺序运行并随时更改数据库状态,但您的模型根本没有版本控制。无法保证编写迁移时存在的模型将来仍与迁移代码兼容。

例如,如果您将来更改 is_activeis_live 属性的行为,则此迁移可能会中断。这种较旧的迁移将首先针对新模型代码运行,并且可能会失败。在您的基本示例中,它可能不会突然出现,但是在添加字段并且无法运行验证之前,这已经让我在部署中烧毁了(我知道您的代码正在跳过验证,但总的来说这是一个问题)。

我最喜欢的解决方案是使用纯 SQL 进行所有此类迁移。看起来你已经考虑过了,所以我假设你已经知道该怎么做。

另一个选择,如果您有一些复杂的业务逻辑或只是希望代码看起来更像 Railsy,则在迁移中包含模型的基本版本在编写迁移时它存在文件本身。例如,您可以将此类放在迁移文件中:

class Group < ActiveRecord::Base
end

就您而言,仅此一项就足以保证模型不会损坏。假设此时 activelive 是表中的布尔字段(因此将来无论何时运行此迁移),您根本不需要任何代码。如果您有更复杂的业务逻辑,则可以将其包含在此特定于迁移的模型版本中。

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

【讨论】:

    【解决方案2】:

    我强烈建议您总共进行三个查询。始终利用数据库而不是循环遍历数组中的一堆项目。我认为这样的事情可以奏效。

    为了写这篇文章,我假设 is_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 here 的文档。

    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。谢谢!
    • 非常感谢罗伯特。这是我需要的。
    【解决方案3】:

    我会这样做

    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

    【讨论】:

    • 谢谢,但应为所有现有组预先填充该列。这仅适用于新创建的组。
    【解决方案4】:

    在类似的情况下,我最终使用add_column 添加列,然后使用直接 SQL 更新列的值。我使用了直接 SQL,而不是 Jim Stewart's answer 中的模型,因为它不依赖于模型的当前状态与基于正在运行的迁移的表的当前状态。

    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
    

    【讨论】:

      猜你喜欢
      • 2023-03-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-08-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多