How do I create a reversible migration helper in rails? - ruby-on-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

Related

migration steps change, up and down

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

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

Renaming table in rails

I want to rename a table... (any table.)
I tried this line of code:
ActiveRecord::ConnectionAdapters::SchemaStatements.rename_table(old_name, new_name)
Here's the weird thing. I know I got it working the first time, but now I get this error: undefined method `rename_table' for ActiveRecord::ConnectionAdapters::SchemaStatements:Module
Was there something I need to set?
Remember that in Rails >= 3.1 you can use the change method.
class RenameOldTableToNewTable < ActiveRecord::Migration
def change
rename_table :old_table_name, :new_table_name
end
end
You would typically do this sort of thing in a migration:
class RenameFoo < ActiveRecord::Migration
def self.up
rename_table :foo, :bar
end
def self.down
rename_table :bar, :foo
end
end
.rename_table is an instance method, not a class method, so calling Class.method isn't going to work. Instead you'll have to create an instance of the class, and call the method on the instance, like this: Class.new.method.
[EDIT]
In this instance, ActiveRecord::ConnectionAdapters::SchemaStatements isn't even a class (as pointed out by cam), which means that you can't even create an instance of it as per what I said above. And even if you used cam's example of class Foo; include ActiveRecord::ConnectionAdapters::SchemaStatements; def bar; rename_table; end; end;, it still wouldn't work as rename_table raises an exception.
On the other hand, ActiveRecord::ConnectionAdapters::MysqlAdapter is a class, and it is likely this class you'd have to use to rename your table (or SQLite or PostgreSQL, depending on what database you're using). Now, as it happens, ActiveRecord::ConnectionAdapters::MysqlAdapter is already accessible through Model.connection, so you should be completely able to do Model.connection.rename_table, using any model in your application.
[/EDIT]
However, if you wish to permanently rename a table, I would suggest using a migration to do it. It's easy and the preferred way of manipulating your database structure with Rails. Here's how to do it:
# Commandline
rails generate migration rename_my_table
# In db/migrate/[timestamp]_rename_my_table.rb:
class RenameMyTable < ActiveRecord::Migration
def self.up
rename_table :my_table, :my_new_table
end
def self.down
rename_table :my_new_table, :my_table
end
end
Then, you can run your migration with rake db:migrate (which calls the self.up method), and use rake db:rollback (which calls self.down) to undo the migration.
ActiveRecord::Migration.rename_table(:old_table_name, :new_table_name)

How to cache tags with acts_as_taggable_on?

I have model with tag context:
class Product < ActiveRecord::Base
acts_as_taggable_on :categories
end
I'm trying to initialize tags caching:
class AddCachedCategoryListToProducts < ActiveRecord::Migration
def self.up
add_column :products, :cached_category_list, :string
Product.reset_column_information
products = Product.all
products.each { |p| p.save_cached_tag_list }
end
end
But cached_category_list does not initializing. What I'm doing wrong? Does anybody can use caching with this gem (my version is 2.0.6)?
Well, today I had the same problem.
I finally solved it, and my migration cached the desired tags.
The problem with your migration was two-fold:
The ActsAsTaggable code which sets up caching needs to run again after the column information is reset. Otherwise, the caching methods are not created (see https://github.com/mbleigh/acts-as-taggable-on/blob/v2.0.6/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb)
The method you are calling, save_cached_tag_list, does NOT automatically save the record, as it is installed as a before_save hook, and it doesn't want to create an infinite loop. So you must call save.
So, try replacing your migration with the following, and it should work:
class AddCachedCategoryListToProducts < ActiveRecord::Migration
def self.up
add_column :products, :cached_category_list, :string
Product.reset_column_information
# next line makes ActsAsTaggableOn see the new column and create cache methods
ActsAsTaggableOn::Taggable::Cache.included(Product)
Product.find_each(:batch_size => 1000) do |p|
p.category_list # it seems you need to do this first to generate the list
p.save! # you were missing the save line!
end
end
end
That should do it.
If you are using this in combination with owned tags, that might be the problem.
Looking at the code of the gem, it seems that the caching of owned tags isn't support
Hope this helps,
Best,
J

Resources