rails migration setting boolean field doesn't work - ruby-on-rails

I have a migration which adds a boolean column and sets value for some of the rows. The new value doesn't get saved to the database when the model is saved. Here is a simplified version of the code:
class AddSmartToStudent
def change
add_column :students, :smart, :boolean
Student.where(grade: 'A').each do |student|
student.smart = true
student.save!
end
end
end
Thanks in advance!

In your migration, you add a boolean column, and right after that use it in the model. Not sure it is possible - when migration is not ended yet the transaction is not committed. The Student model might not have smart field yet.
As Luis Silva suggests you could use reset_column_information method to refresh info about columns for Student. But the problem is migrations are not for manipulating with data. If you want to change some data it's better to do in a rake task.
If for some reason you HAVE TO do it in migration, you can do it in plain SQL query. For PostgreSQL it will be:
execute "UPDATE students SET smart='t' WHERE grade='A'"

Try to reset the cached information about columns, which will cause them to be reloaded on the next request.
Execute this line before your clause where
Student.reset_column_information
reset_column_information

There are two issues that are clear to me right of the bet.
As stated by others you're trying to use an attribute that you add in that same migration. The safe thing to do is to reset the column information like explained in the answer of Luis Silva.
The second issue has to do with the fact that you use def change where some of the content isn't reversible. Everything in the change method should be reversible. Otherwise def up and def down should be used.
Here are two options that might solve your issue:
Using def up and def down.
class AddSmartToStudent
def up
add_column :students, :smart, :boolean
Student.reset_column_information
Student
.where(grade: 'A')
.find_each { |student| student.update!(smart: true) }
end
def down
remove_column :students, :smart
end
end
Using reversible.
class AddSmartToStudent
def change
add_column :students, :smart, :boolean
reversible do |change|
change.up do
Student.reset_column_information
Student
.where(grade: 'A')
.find_each { |student| student.update!(smart: true) }
end
end
end
end
If you don't care about Rails callbacks, validations, etc. you could also use
Student.where(grade: 'A').update_all(smart: true)
as replacement for
Student.where(grade: 'A').find_each { |student| student.update!(smart: true) }
This updates all the records with a single query but doesn't instantiate the records, meaning Rails callbacks, validations, etc. won't run. For more info see update_all.

Related

Rails Postgres migration without downtime

Let's say I want to add a column on my users table running the following migration
class AddVersionHistoryToUsers < ActiveRecord::Migration
def change
add_column :users, :versions, :string, array: true, default: '{}'
User.find_each do |user|
if user.app_version?
user.versions << user.app_version.to_s
user.save!
end
end
end
end
My aim is to insert the current app_version of each user into the versions array. How can I execute a migration without a lock in the users table due to the default value?
To wrap this up: Looping and querying the database for every User is highly inefficient. You should use update_all for these tasks.
And since your data already exists in the same table you can simply get it from there.
User.where.not(app_version: nil).update_all('versions = ARRAY[app_version]')

Is there any difference in using up/down vs change/reversible methods in Rails migrations?

In a Rails migration, does it make any difference, if I do this:
def up
foo
end
def down
bar
end
or this:
def change
reversible do |direction|
direction.up { foo }
direction.down { bar }
end
end
?
I think that it's better to use the change method if part of the migration includes reversible methods, such as create_table, add_column etc.. Other than that, is there any difference?
As you show it, there is no advantage. The main advantage is that a lot of the time you don't need to write the down method / block at all, eg
class SomeMigration < ActiveRecord::Migration
def change
create_table :articles do |t|
...
end
end
end
The reversible method is mostly used when there is a small part of a migration that activerecord doesn't know how to reverse (eg a raw SQL statement)
The change method works for many cases where the reversible logic can be derived easily and ActiveRecord migrations can easily handle it. However, in some cases you may need to do something specific inside of a migration to insure it will run without errors. There may also be something complex would be an irreversible migration.
See Rails: Is it bad to have an irreversible migration?
In such cases it is best to use the up down method but there is also away of checking if the current direction of the migration which is simply reverting?
Here's an example where you might use this in place of the 2 up/down methods. In this example, there is also a complex data method not show which caused a migration error without the conditional logic. I've ommited as it is not relevant to your question:
def change
add_column :languages, :iso_639_2t, :string
add_column :languages, :iso_639_2b, :string
add_column :languages, :iso_639_3, :string
add_index :languages, :iso_639_2t
unless reverting?
ActiveRecord::Base.connection
.execute("ALTER SEQUENCE languages_id_seq RESTART WITH #{Language.count+1}")
end
end
If complex data manipulation is used inside of a migration via migration_data gem for example, you might want to add a def check_data method and raise an exception if the data_check fails.
If the migration is really irreversible (destroys data), you may want to also use up/down for example:
def up
remove_column :signup, :date
end
def down
raise ActiveRecord::IrreversibleMigration, "Date field dropped in previous migration. Data unrecoverable"
end

Does change handle migration / rolling back of schema

In the new preferred way, there is only 1 method which seems to add a column. Does that mean you don't need to have a method to remove columns?
# the old way
class AddNameToPerson < ActiveRecord::Migration
def up
add_column :persons, :name, :string
end
def down
remove_column :person, :name
end
end
# the new prefered way
class AddNameToPerson < ActiveRecord::Migration
def change
add_column :persons, :name, :string
end
end
That's the magic of Rails 3.1 and later. Rails knows how to migrate your database and reverse it when the migration is rolled back without the need to write a separate down method.
Rails keep track of migration. So it will have the implementation for reverse the changes added by the migration when you roll back. Earlier we use to have two methods up and down. On Rails 3.1 and later they changed migration to have change method.
In case of add_coulmn reverse method is remove_column and same for other methods. Using tracked detail it will call appropriate action. So you don't need two methods(up and down). You can see the methods supported by change method: here. If you have other then these method you need to use up and down.
If you are confused with using change method then I suggest you to use up and down. Once you thorough with migration you can start using change method.
You are right, You don't need a separate method to remove columns.
You can even try 'rake db:rollback' and then again 'rake db:migrate' if you don't have any data to lose.
It works perfectly. :)

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

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
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!).
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
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
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

Migrating Data in a Rails Migration

Let's say you have "lineitems" and you used to define a "make" off of a line_item.
Eventually you realize that a make should probably be on its own model, so you create a Make model.
You then want to remove the make column off of the line_items table but for every line_item with a make you want to find_or_create_by(line_item.make).
How would I effectively do this in a rails migration? I'm pretty sure I can just run some simple find_or_create_by for each line_item but I'm worried about fallback support so I was just posting this here for any tips/advice/right direction.
Thanks!
I guess you should check that the Make.count is equal to the total unique makes in lineitems before removing the column, and raise an error if it does not. As migrations are transactional, if it blows up, the schema isn't changed and the migration isn't marked as executed. Therefore, you could do something like this:
class CreateMakesAndMigrateFromLineItems < ActiveRecord::Migration
def self.up
create_table :makes do |t|
t.string :name
…
t.timestamps
end
makes = LineItem.all.collect(:&make).uniq
makes.each { |make| Make.find_or_create_by_name make }
Make.count == makes.length ? remove_column(:line_items, :make) : raise "Boom!"
end
def self.down
# You'll want to put logic here to take you back to how things were before. Just in case!
drop_table :makes
add_column :line_items, :make
end
end
You can put regular ruby code in your migration, so you can create the new table, run some code across the old model moving the data into the new model, and then delete the columns from the original model. This is even reversible so your migration will still work in both directions.
So for your situation, create the Make table and add a make_id to the lineitem. Then for each line item, find_or_create with the make column on lineitem, setting the returned id to the new make_id on lineitem. When you are done remove the old make column from the lineitem table.

Resources