add a database column with Rails migration and populate it based on another column

浪子不回头ぞ 提交于 2020-01-01 09:22:09

问题


I'm writing a migration to add a column to a table. The value of the column is dependent on the value of two more existing columns. What is the best/fastest way to do this? Currently I have this but not sure if it's the best way since the groups table is can be very large.

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

回答1:


It's generally a bad idea to reference your models from your migrations like this. The problem is that the migrations run in order and change the database state as they go, but your models are not versioned at all. There's no guarantee that the model as it existed when the migration was written will still be compatible with the migration code in the future.

For example, if you change the behavior of the is_active or is_live attributes in the future, then this migration might break. This older migration is going to run first, against the new model code, and may fail. In your basic example here, it might not crop up, but this has burned me in deployment before when fields were added and validations couldn't run (I know your code is skipping validations, but in general this is a concern).

My favorite solution to this is to do all migrations of this sort using plain SQL. It looks like you've already considered that, so I'm going to assume you already know what to do there.

Another option, if you have some hairy business logic or just want the code to look more Railsy, is to include a basic version of the model as it exists when the migration is written in the migration file itself. For example, you could put this class in the migration file:

class Group < ActiveRecord::Base
end

In your case, that alone is probably sufficient to guarantee that the model will not break. Assuming active and live are boolean fields in the table at this time (and thus would be whenever this migration was run in the future), you won't need any more code at all. If you had more complex business logic, you could include it in this migration-specific version of model.

You might even consider copying whole methods from your model into the migration version. If you do that, bear in mind that you shouldn't reference any external models or libraries in your app from there, either, if there's any chance that they will change in the future. This includes gems and even possibly some core Ruby/Rails classes, because API-breaking changes in gems are very common (I'm looking at you, Rails 3.0, 3.1, and 3.2!).




回答2:


I would highly suggest doing three total queries instead. Always leverage the database vs. looping over a bunch of items in an array. I would think something like this could work.

For the purposes of writing this, I'll assume is_active checks a field active where 1 is active. I'll assume live is the same as well.

Rails 3 approach

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

Feel free to review the documentation of update_all here.

Rails 2.x approach

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




回答3:


I would do this in a

after_create
# or
after_save

in your ActiveRecord model:

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

or in the migration you'd probably have to do some SQL like this:

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

Here is an example in the Rails guides:

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




回答4:


In a similar situation I ended up adding the column using add_column and then using direct SQL to update the value of the column. I used direct SQL and not the model per Jim Stewart's answer, since then it doesn't depend on the current state of the model vs. the current state of the table based on migrations being run.

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


来源:https://stackoverflow.com/questions/15284495/add-a-database-column-with-rails-migration-and-populate-it-based-on-another-colu

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!