You might have enountered an condition of migration pending when you are trying to access a route, we may wonder sometime how does rails knows that migrations are pending.
Rails keeps the track of every migration that has been ran in a table schema_migration
. This table has only one column version
that store the version of migration files.
Versions are the creation timestamp of migration file, this version makes every migration unique from each other. So every time we runs a migration, corresponding version are stored in this table and whenever we runs a rollback corresponding version are removed from the table.
How schema_migration table is created
If we check the source of code of activerecord gem, we can see SchemaMigration class. This class is reponsible for table creation and all related operation like insertion and deletion in the table.
Some important method from SchemaMigration class
class SchemaMigration # :nodoc:
class NullSchemaMigration # :nodoc:
end
attr_reader :arel_table
def initialize(pool)
@pool = pool
@arel_table = Arel::Table.new(table_name)
end
def create_version(version)
im = Arel::InsertManager.new(arel_table)
im.insert(arel_table[primary_key] => version)
@pool.with_connection do |connection|
connection.insert(im, "#{self.class} Create", primary_key, version)
end
end
def delete_version(version)
dm = Arel::DeleteManager.new(arel_table)
dm.wheres = [arel_table[primary_key].eq(version)]
@pool.with_connection do |connection|
connection.delete(dm, "#{self.class} Destroy")
end
def create_table
@pool.with_connection do |connection|
unless connection.table_exists?(table_name)
connection.create_table(table_name, id: false) do |t|
t.string :version, **connection.internal_string_options_for_primary_key
end
end
end
end
end
In above code, create_table
create a table in database and create_version
inserts version in table and delete_version
removes version from table. Other method are also available in source code.
How does rails raise pending migration error
In Migration class in activerecord gem there is class available CheckPending
call method from CheckPending class
def call(env)
mtime = ActiveRecord::Base.connection.migration_context.last_migration.mtime.to_i
if @last_check < mtime
ActiveRecord::Migration.check_pending!(connection)
@last_check = mtime
end
@app.call(env)
end
This call
method in CheckPending class calls an another method of Migration class check_pending!
Some methods from Migration class
def check_pending!(connection = Base.connection)
raise ActiveRecord::PendingMigrationError if connection.migration_context.needs_migration?
end
def needs_migration?
(migrations.collect(&:version) - get_all_versions).size > 0
end
def get_all_versions
if SchemaMigration.table_exists?
SchemaMigration.all_versions.map(&:to_i)
else
[]
end
end
In above code, check_pending! calls the need_migration? method that calls the get_all_versions method. get_all_versions method with the help of SchemaMigration give all version to need_migration? method which check if number of migration files are equal to number of versions available in schema_migration table. If schema_migration table have less numbers of version it means there are some pending migration.
Migration class also have an another class PendingMigrationError which have details of error for pending migrations.
CheckPending and PendingMigrationError classes are accessed from railtie class during initialization.
Conclusion
In conclusion, ruby on rails are designed with detailing to ensure its smoothness and thats what makes it different from other frameworks.
I would love to know your reviews on this article. If you liked it or if you have any suggestion please put a comment.
Top comments (1)
@iamronakgupta
Great article! It's a must-read for anyone working with Rails. Keep up the good work!