migration steps change, up and down - ruby-on-rails

My question is really simple. I created this migration file and my mobile column didn't change BUT def change was created. Is it because rails ignored def up and def down? if so why?
def change
add_column :posts, :address, :string
end
def up
execute 'ALTER TABLE posts ALTER COLUMN mobile TYPE integer USING (mobile::integer)'
end
def down
execute 'ALTER TABLE posts ALTER COLUMN mobile TYPE text USING (mobile::text)'
end

Rails will not run both the change and up methods by design thus ignoring everything after the change method. When you need to run some specific logic like in your Up and Down methods you have two choices. You can either put the stuff in your change method into the up and down methods or you can put the Up and Down stuff into the change method. If you want to do this the "Rails4" way you should use change and the reversible method to get what you need:
class SomeMigration < ActiveRecord::Migration
def change
add_column :posts, :address, :string
reversible do |change|
change.up do
execute 'ALTER TABLE posts ALTER COLUMN mobile TYPE integer USING (mobile::integer)'
end
change.down do
execute 'ALTER TABLE posts ALTER COLUMN mobile TYPE text USING (mobile::text)'
end
end
end

Related

Add new column by comparing values of an existing column in Rails 5

I have a model that has a column its_holiday that is a boolean field in database. Now I want to add another column its_not_a_holiday that has a value opposite to that of the its_holiday column. How can I add this new column to the existing table and populate it with the values opposite to that of its_holiday column?
If your boss insists on having an its_not_holiday boolean (although other answers and comments have pointed out, you don't actually need to do that) you can do a migration as follows...
Create a migration
rails g migration AddItsNotAHolidayToMyModel its_not_a_holiday:boolean
Edit the migration as follows
class AddItsNotAHolidayToMyModel < ActiveRecord::Migration[5.1] # or whatever version you have
def up
add_column :my_model, :its_not_a_holiday, :boolean, default: false
MyModel.where(its_holiday: [false, nil]).update_all(its_not_a_holiday: true)
end
def down
remove_column :my_model, :its_not_a_holiday
end
end
You may want to modify the model to ensure the boolean is always set correctly when you save a record
class MyModel
before_save :update_its_not_a_holiday
private
def update_its_not_a_holiday
self.its_not_a_holiday = !its_holiday
end
end
As mentioned by #Nithin you can use negate value of its_holiday instead of new column. To follow good practice you should use the negate value. But if you still want to do that you can do it like this for existing records.
class AddItsNotAHolidayToModelName < ActiveRecord::Migration
def up
add_column :model_name, :its_not_a_holiday, :boolean
ModelName.find_each do |model_name|
model_name.its_not_a_holiday = !model_name.its_holiday
model_name.save
end
end
def down
remove_column :model_name, :its_not_a_holiday
end
end
and for new records
class ModelName
before_save :update_its_not_a_holiday
private
def add_its_not_a_holiday
self.its_not_a_holiday = !its_a_holiday
end
end
Hope it will work for you.
You can add a custom method in your model file like this;
def its_not_a_holiday
!its_holiday
end

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]')

How to make migration to update table RAILS

I am stuck with making migration which will update my table Users. I need to set country_code with 1 everywhere where i have "" or NULL for that column.
Thanks
class UpdateCountryCodeColumnUsers < ActiveRecord::Migration
def up
execute %Q(
UPDATE users
SET country_code = 1
WHERE country_code IS NULL OR country_code = ""
)
end
end
You probably shouldn't alter the data in a migration and only use it to alter the schema.
A lot of devs use rake db:reset which won't run this migration.
A better solution is to create a rake or thor task as a one off or simply just execute the SQL.
Sergei's answer is your best bet and will update the data (which would be needed first - and is very important) and it lets the database do the work. If you also need to set the default for going forward (after the update Sergei proposed) you can make a separate migration (to separate activities) and include the below...
You can also use the rails migration helper method change_column_default
change_column_default :users, :country_code, from: nil, to: 1
If you want to make it reversible just use change_column...
def up
change_column :users, :country_code, :string, default: 1
end
def down
change_column :users, :country_code, :string, default: nil
end
def UpdateCountryCodeForUsers < ActiveRecord::Migration
def up
Users.where("country_code = '' or country_code = NULL")
.update_attributes({country_code: 1})
end
end
On rails 5.2 , I needed to update some text attributes in a column & had to use update_all instead of update_attributes.
class UpdateTableColumn < ActiveRecord::Migration[5.2]
def up
TableName.where("my_column = 'my column old value'")
.update_all({keywords: 'my column new value'})
end
end

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

How do I create a reversible migration helper in rails?

I find myself having to execute very similar sql statements (with maybe 1 param besides the table name) on several tables on a rails app. As a result, I'm getting lots of similarly looking migrations, like this one:
class DoSomeSQLOnUser < ActiveRecord::Migration
def up
execute('some long sql that alters the user.field1')
execute('some long sql that alters the user.field2')
end
def down
execute('some sql that undoes the changes')
end
end
Then I have the same thing for clients, sales, etc.
I would like to extend ActiveRecord::Migration so that I can do this instead:
class DoSomeSQLOnUser < ActiveRecord::Migration
def change
do_custom_thing_on :users, :field1
do_custom_thing_on :users, :field2
end
end
How can I do that? I think I know how to do it when the operations are separated into up and down, like this:
class DoSomeSQLOnUser < ActiveRecord::Migration
def up
do_custom_thing_on :users, :field1
do_custom_thing_on :users, :field2
end
def down
undo_custom_thing_on :users, :field1
undo_custom_thing_on :users, :field2
end
end
But doing it so that the change is "reversible" escapes me.
It doesn't seem to be a official supported way to do this, so probably you'll need to open the class ActiveRecord::Migration::CommandRecorder and record the new method and its inverted version.
Find the definition of the class at activerecord/lib/active_record/migration/command_recorder.rb.
In Rails 4 there is a reversible helper method, which you can use like this:
def change
do_custom_thing_on :users, :field1
do_custom_thing_on :users, :field2
end
def do_custom_thing_on(table, field)
reversible do |dir|
dir.up { execute "some long sql to alter #{field} on #{table}"}
dir.down { execute "some long sql to undo #{field} on #{table}"}
end
end
Not sure if you need to do something else, but at least you should add a inverse_custom_thing method to ActiveRecord::Migration::CommandRecorder
Main purpose of change method it's adding, renaming columns but not removing. Change method 'knows' how to make reverse when the migration is rolled back. So if you want to do something, which is not removing, just do it in change method, ActiveRecord will reverse it itself, I think.
More information you can get from official documentation

Resources