Keywords: Ruby on Rails | Database Migrations | Single File Execution
Abstract: This technical paper provides an in-depth analysis of the migration system in Ruby on Rails, focusing on methods for executing individual migration files independently of version control. By comparing official rake tasks with direct Ruby code execution, it explains the tracking mechanism of the schema_migrations table, instantiation requirements for migration classes, and compatibility differences across Rails versions. The paper also discusses techniques for bypassing database records to enable re-execution and offers best practice recommendations for real-world application scenarios.
Fundamental Architecture of Migration Systems
The database migration system in Ruby on Rails employs a timestamp-based version control mechanism, where each migration file contains a unique version identifier. When executing the standard migration command rake db:migrate, the system processes all pending migrations in chronological order and records completed versions in the schema_migrations table. This design ensures incremental evolution of database structures, but in certain development scenarios, developers require more granular control capabilities.
Official Rake Task Approach
Rails provides specialized rake tasks for executing individual migration files. For newer Rails versions, the following command can be used:
rake db:migrate:up VERSION=20090408054532
The version number corresponds to the timestamp portion in the migration filename. This method automatically checks the schema_migrations table and rejects re-execution if the migration has already been run. To force re-execution of completed migrations, Rails introduced the redo command:
rake db:migrate:redo VERSION=20090408054532
This mechanism works by removing the corresponding record from the schema_migrations table, marking the migration file as "pending" again.
Direct Ruby Code Execution Method
When bypassing the version control system is necessary, migration code can be executed directly in the Rails console. First, launch the console:
rails console
Then load and execute the specific migration file:
>> require "db/migrate/20090408054532_add_foos.rb"
>> AddFoos.new.up
Several technical points require attention here. First, migration classes need to be instantiated before calling the up method, which is a requirement in modern Rails versions. For very old Rails versions, calling the class method AddFoos.up directly might be necessary. Second, if the migration uses a change method instead of up/down method pairs, AddFoos.new.change should be invoked.
Non-Interactive Execution Solution
For automation scripts or deployment scenarios, non-interactive execution can be achieved using script/runner:
script/runner 'require("db/migrate/20090408054532_add_foos.rb").first.constantize.up'
This code leverages Ruby's characteristic where the require method returns an array of loaded class names. first.constantize converts the class name string into an actual class object, then calls its up method. This approach does not update the schema_migrations table, making it suitable for scenarios requiring repeated testing of migration logic.
File Path Handling Considerations
When performing direct require operations, file path issues may arise. If Rails cannot locate the migration file, the following variants can be attempted:
require("./db/migrate/20090408054532_add_foos.rb")
Or using require_relative:
require_relative "db/migrate/20090408054532_add_foos.rb"
The specific choice depends on the current working directory and Rails environment configuration.
Advanced Techniques and Alternative Approaches
Beyond the core methods mentioned above, the community has developed several practical techniques. Modifying the timestamp portion of a migration filename can "trick" Rails into treating it as a new migration:
20151013131830_my_migration.rb -> 20151013131831_my_migration.rb
This method proves particularly useful during remote environment debugging. Another more direct approach involves manual database manipulation:
rails_c> q = "delete from schema_migrations where version = '20151013131830'"
rails_c> ActiveRecord::Base.connection.execute(q)
After executing this SQL statement, running rake db:migrate again will re-execute the corresponding migration.
Version Compatibility and Best Practices
Subtle differences exist in migration handling across various Rails versions. Rails 3.2 and later versions strengthened the checking mechanism of the schema_migrations table to prevent accidental re-execution. For migrations using the change method (introduced in Rails 3.1+), directly calling the up method might not work correctly; in such cases, the change method should be used instead.
In practical development, the following principles are recommended: use official rake tasks in standard development workflows; employ direct code execution for testing and debugging scenarios; avoid manual database record modifications in production environments; maintain idempotent design of migration files to ensure safe re-execution.