Reversing The Irreversible Migration
If you've been writing migrations for ActiveRecord for very long, you have eventually encountered this error when trying to rollback a migration.
== 20160607145211 Irreversible: reverting =====================================
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:
ActiveRecord::IrreversibleMigration
This typically occurs because the previous state is unknown when rolling back, so a reversal can't be generated. For example, we cannot rollback this migration.
class MyMigration < ActiveRecord::Migration
def change
change_column :children, :id, :bigint
end
end
Nothing in this migration indicates what the previous type was, so ActiveRecord cannot generate
the reverse statement. Fortunately, there are a couple of ways to fix this problem. First,
we could split the change
method out into up
and down
methods.
class MyMigration < ActiveRecord::Migration
def up
change_column :children, :id, :bigint
end
def down
change_column :children, :id, :integer
end
end
This works great in this simple example, but what if you have a longer migration and only part of it is irreversible?
class MyMigration < ActiveRecord::Migration
def change
# This is reversible
add_index :children, :parent_id
add_index :children, :active
# This is irreversible
change_column :children, :id, :bigint
end
end
You could rewrite this as up
and down
methods.
class MyMigration < ActiveRecord::Migration
def up
add_index :children, :parent_id
add_index :children, :active
change_column :children, :id, :bigint
end
def down
change_column :children, :id, :integer
remove_index :children, :active
remove_index :children, :parent_id
end
end
This will work, but is undesirable for a couple of reasons. First, the remove_index
calls
are duplicating functionality that ActiveRecord migrations already provide. This is a pretty
straightforward example, but for some other migration commands, it would be best to allow the
tested framework code to do this.
Also, by explicitly calling the add_index
and remove_index
statements,
we have introduced the possibility of a bug by typing the wrong table or column names.
Finally, in this case, the order of the statements is not significant, but if it were, this would
be a much more complex down
method to implement because we would need to call them in the
reverse order of the up
method.
Fortunately, ActiveRecord migrations already provide a solution for us. The reversible
method.
Basically, this method allows you to specify up and down behavior for part of the migration.
class Irreversible < ActiveRecord::Migration
def change
add_index :children, :parent_id
add_index :children, :active
reversible do |dir|
dir.up do
change_column :children, :id, :bigint
end
dir.down do
change_column :children, :id, :int
end
end
end
end
By using the reversible
method, we can define the up and down behavior in blocks that will
execute when the change method is migrating up or down. In addition to removing the duplication
and complexity of the previous example, this clearly specifies the fact that there is an
irreversible task occurring. Since this is often a task that should be inspected carefully
because of it's possible data loss effects, this is a nice benefit.
It's a good habit to test the rollback of every migration that you write to make sure that it
will work when you need it. I like to run my migration when I write it with rake db:migrate
and
then immediately re-run it with rake db:migrate:redo
to verify the rollback. And, now that you know how to
reverse "irreversible" migrations, you can save the exception for times when the migration truly cannot be reversed.
Opening a new tmux window in the current directory
If you are using tmux, you know about splitting windows and opening new windows. I do that all the time to get a new terminal to do things like run a dependency service or tail a log. Until recently, this involved having the terminal open in the initial directory where the session was started and then having to navigate to the directory of the project where I am working.
Well, it turns out that tmux supports opening a window where the terminal's directory
is the same as the directory of the current pane and it's easy to do! Simply use
the -c
option to set the working directory and the {pane_current_path}
variable to use
the current panes path for the value.
For example, to open a new terminal in a vertical split:
split-window -c '#{pane_current_path}'
I've configured my ~/.tmux.conf
file to change the split-window and new-window aliases to use this because I almost always want this
behavior.
# In ~/.tmux.conf
# Note that the binding to `"` needs to be escaped.
bind '"' split-window -c '#{pane_current_path}'
bind % split-window -h -c '#{pane_current_path}'
bind c new-window -c '#{pane_current_path}'
Remember, if you change your tmux configuration and want to reload the current server to use the configuration run the following command:
source-file ~/.tmux.conf
Using ActiveRecord's merge to access scopes in different models
One of the things that I love about ActiveRecord is that it allows me to build sql by using composition. By chaining together scopes, I have reusable snippets of sql code that can be combined to build a complex query. Anyone that has used ActiveRecord has seen this in action:
class Child < ActiveRecord::Base
def self.active
where(active: true)
end
def self.recent
where("created_at > ? ", Date.today - 5.days)
end
end
Child.active.recent
This generates a sql statement by composing the chained scopes.
SELECT "children".* FROM "children" WHERE "children"."active" = 't' AND (created_at > '2016-05-06')"
Ok, that's great, but what if you want to limit the children by some attribute of its parent? For example, what if instead of active children, we only want recent Child records where the parent is active?
First, we need to add the relationship to Child
.
class Child < ActiveRecord::Base
belongs_to :parent
def self.active
where(active: true)
end
def self.recent
where("created_at > ? ", Date.today - 5.days)
end
end
Next, we create the parent class.
class Parent < ActiveRecord::Base
has_many :children
def self.active
where(active: true)
end
end
If you've run into this before, you probably joined to (or included) the parent in the query and added a where clause for the
active=true
condition like this ...
Child.recent.
joins(:parent).
where(parents: { active: true })
This works and generates the sql
SELECT "children".* FROM "children" WHERE "children"."active" = 't' AND (created_at > '2016-05-06')" AND
"parents"."active" = 't'
But, besides being a little verbose, it doesn't reuse the existing active
scope on the Parent
class. What
happens if the definition of the parent's active
scope changes? How would we know that this code needs to be changed as well?
ActiveRecord#merge
to the rescue. Using merge
, you can just merge in the scope from the other class.
Child.recent.
joins(:parent).
merge(Parent.active)
This generates the exact same sql as above, but provides a number of advantages to the readability and
maintainability of our code. By reusing the existing scope on the Parent
class, we have DRYed up our
code and made it more intention-revealing. Also, we have now abstracted the implementation detail of
the parents table's schema.
One important thing to remember when using merge is that you need to either join or include the parent table
explicitly. Without it, you'll get an error like no such column: parents.active
.
ActiveRecord is a huge framework and there are a ton of great methods in it. I hope you find this one useful. I'm going to try to introduce more in future posts. Until next time ...