開発中のエラーでよく出てくるActiveRecord::PendingMigrationError
について調べてみました。Railsのバージョンは5.1.5です。
開発環境のエラー画面でPendingMigrationErrorが発生するのは以下の設定がされていることが条件です。
1 |
config.active_record.migration_error = :page_load |
ActiveRecord::RailtieのinitializerでRackミドルウェアにActiveRecord::Migration::CheckPendingを設定します。
1 2 3 4 5 6 7 8 |
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!を呼び出します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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します。
1 2 3 4 |
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を返します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
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として振る舞います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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を出しています。
コメントを残す