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.

Posted by on June 07, 2016

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

Posted by on May 17, 2016

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

Posted by Craig Israel on May 12, 2016