開発中のエラーでよく出てくるActiveRecord::PendingMigrationError
について調べてみました。Railsのバージョンは5.1.5です。
開発環境のエラー画面でPendingMigrationErrorが発生するのは以下の設定がされていることが条件です。
config.active_record.migration_error = :page_load
ActiveRecord::RailtieのinitializerでRackミドルウェアにActiveRecord::Migration::CheckPendingを設定します。
module ActiveRecord
class Railtie < Rails::Railtie # :nodoc:
initializer "active_record.migration_error" do
if config.active_record.delete(:migration_error) == :page_load
config.app_middleware.insert_after ::ActionDispatch::Callbacks,
ActiveRecord::Migration::CheckPending
end
end
ActiveRecord::Migration::CheckPending#callはActiveRecord::Migration.check_pending!を呼び出します。
module ActiveRecord
class Migration
class CheckPending
def initialize(app)
@app = app
@last_check = 0
end
def call(env)
mtime = ActiveRecord::Migrator.last_migration.mtime.to_i
if @last_check < mtime
ActiveRecord::Migration.check_pending!(connection)
@last_check = mtime
end
@app.call(env)
end
private
def connection
ActiveRecord::Base.connection
end
end
check_pending!はActiveRecord::Migrator.needs_migration?を呼び出し、trueのときにActiveRecord::PendingMigrationErrorをraiseします。
class << self
def check_pending!(connection = Base.connection)
raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration?(connection)
end
needs_migration?ではdb/migratesディレクトリ内のマイグレーションファイル名から正規表現でバージョンを取得し、実際にマイグレーションしたバージョンが入るschema_migrationsのレコードと比較し、マイグレーションしていないファイルが存在すればtrueを返します。
MigrationFilenameRegexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/ # :nodoc:
class << self
def needs_migration?(connection = Base.connection)
(migrations(migrations_paths).collect(&:version) - get_all_versions(connection)).size > 0
end
def parse_migration_filename(filename) # :nodoc:
File.basename(filename).scan(Migration::MigrationFilenameRegexp).first
end
def migrations(paths)
paths = Array(paths)
migrations = migration_files(paths).map do |file|
version, name, scope = parse_migration_filename(file)
raise IllegalMigrationNameError.new(file) unless version
version = version.to_i
name = name.camelize
MigrationProxy.new(name, version, file, scope)
end
migrations.sort_by(&:version)
end
get_all_versionsはActiveRecord::SchemaMigration.all_versionsを呼び出します。schema_migrations_table_nameはschema_migrationsに設定されているため、ActiveRecord::SchemaMigrationはテーブル名がschema_migrationsのActiveRecordとして振る舞います。
module ActiveRecord
class SchemaMigration < ActiveRecord::Base # :nodoc:
class << self
def primary_key
"version"
end
def table_name
"#{table_name_prefix}#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}"
end
# ...
def all_versions
order(:version).pluck(:version)
end
まとめると、Rackミドルウェアを使ってマイグレーションファイルとDB内のマイグレーション情報を比較してActiveRecord::PendingMigrationErrorを出しています。