How does rails knows that migration is pending?
Published: 2024-05-11 19:03:17

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.



![Image showing pending migration error in browser](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9yti0wik645vgf27wgly.png)


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.


![Image showing data from schema_migration table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8awi3vd3haqiarutscnt.png)


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](https://github.com/rails/rails/blob/main/activerecord/lib/active_record/schema_migration.rb) 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](https://github.com/rails/rails/blob/b13a5cb83ea00d6a3d71320fd276ca21049c2544/activerecord/lib/active_record/migration.rb#L1079) class in activerecord gem there is class available [CheckPending](https://github.com/rails/rails/blob/b13a5cb83ea00d6a3d71320fd276ca21049c2544/activerecord/lib/active_record/migration.rb#L548C6-L548C22)

_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](https://github.com/rails/rails/blob/b13a5cb83ea00d6a3d71320fd276ca21049c2544/activerecord/lib/active_record/migration.rb#L130C9-L130C30) 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.