DataMapperのdm-migrationsのマイグレーションファイルの通し番号は重複していても問題ない

dm-migrations-1.2.0の話です。

dm-railsで次のようにdm-migrationsのマイグレーションファイルを生成すると、001から始まる通し番号のマイグレーションファイルが作成されます。

$ bundle exec rails generate migration create_users
      invoke  data_mapper
      create    db/migrate/001_create_users.rb

複数人で開発を行なっていると、データベースのマイグレーションスクリプトの番号が衝突しがちです。これは困ったなーとはじめのうちは通し番号の調整を行なっていたのですが、無駄な作業だという事がわかりました。

dm-migrationsのマイグレーション情報の管理方法

マイグレーションの適用状況を管理するテーブルmigration_infoのテーブル定義は次の通りです。

CREATE TABLE `migration_info` (
  `migration_name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  UNIQUE KEY `migration_name` (`migration_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

migration_name、名前の方しか管理していません。実は通し番号はおまけで、名前が重要なのでした。

通し番号が重複した場合どうなるのか。

実際に重複している場合の動作を確認してみます。次のようなファイルを用意した場合のマイグレーションの適用状況を考えます。

  • db/migrate/001_create_users.rb
# -*- coding: utf-8 -*-

migration 1, :create_users do

  up do
    create_table :users do
      column :id, Integer, serial: true
      column :name, String
      column :created_at, DateTime
      column :updated_at, DateTime
    end
  end

  down do
    drop_table :users
  end

end
  • db/migrate/002_create_categories.rb
# -*- coding: utf-8 -*-

migration 2, :create_categories do

  up do
    create_table :categories do
      column :id, Integer, serial: true
      column :name, String, size: 20, not_null: true
      column :created_at, DateTime
      column :updated_at, DateTime
    end
  end

  down do
    drop_table :categories
  end

end
  • db/migrate/002_create_movies.rb
# -*- coding: utf-8 -*-

migration 2, :create_movies do

  up do
    create_table :movies do
      column :id, Integer, serial: true
      column :title, String, size: 255, not_null: true
      column :released_on, Date
      column :imdb_url, String, size: 255
      column :created_at, DateTime
      column :updated_at, DateTime
    end
  end

  down do
    drop_table :movies
  end

end
  • db/migrate/003_create_category_movies.rb
# -*- coding: utf-8 -*-

migration 3, :create_category_movies do

  up do
    create_table :category_movies do
      column :category_id, Integer, primary_key: true
      column :movie_id, Integer, primary_key: true
    end

    # auto_migrateではdm-constraintsの機能でFK制約が作成されるが
    # db-migrationsからその機能は利用できない。
    create_index :category_movies, :category_id
    create_index :category_movies, :movie_id
  end

  down do
    drop_table :category_movies
  end

end

bundle exec rake db:migrate

マイグレーションを実行してみましょう。

$ bundle exec rake db:migrate 
 == Performing Up Migration #1: create_users
   CREATE TABLE `users` (`id` SERIAL PRIMARY KEY, `name` VARCHAR(50), `created_at` DATETIME, `updated_at` DATETIME) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT=''
   -> 0.1809s
 -> 0.1855s
 == Performing Up Migration #2: create_categories
   CREATE TABLE `categories` (`id` SERIAL PRIMARY KEY, `name` VARCHAR(20), `created_at` DATETIME, `updated_at` DATETIME) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT=''
   -> 0.2658s
 -> 0.2682s
 == Performing Up Migration #2: create_movies
   CREATE TABLE `movies` (`id` SERIAL PRIMARY KEY, `title` VARCHAR(255), `released_on` DATE, `imdb_url` VARCHAR(255), `created_at` DATETIME, `updated_at` DATETIME) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT=''
   -> 0.1742s
 -> 0.1772s
 == Performing Up Migration #3: create_category_movies
   CREATE TABLE `category_movies` (`category_id` INTEGER, `movie_id` INTEGER) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT=''
   -> 0.1572s
   CREATE INDEX `index_category_movies_category_id` ON `category_movies` (`category_id`)
   -> 0.1088s
   CREATE INDEX `index_category_movies_movie_id` ON `category_movies` (`movie_id`)
   -> 0.1344s
 -> 0.4025s

全部適用されます。migration_infoは次のとおり。

> select * from migration_info;
+------------------------+
| migration_name         |
+------------------------+
| create_categories      |
| create_category_movies |
| create_movies          |
| create_users           |
+------------------------+
4 rows in set (0.00 sec)

bundle exec rake 'db:migrate:down[1]'

マイグレーションの状態を1に戻してみましょう

$ bundle exec rake 'db:migrate:down[1]'
 == Performing Down Migration #3: create_category_movies
   DROP TABLE `category_movies`
   -> 0.0310s
 -> 0.0312s
 == Performing Down Migration #2: create_movies
   DROP TABLE `movies`
   -> 0.0678s
 -> 0.0679s
 == Performing Down Migration #2: create_categories
   DROP TABLE `categories`
   -> 0.0429s
 -> 0.0430s

2が両方とも外されました。migration_infoは次のとおり。

> select * from migration_info;
+-------------------+
| migration_name    |
+-------------------+
| create_users      |
+-------------------+
1 rows in set (0.00 sec)

bundle exec rake 'db:migrate:up[2]'

2だけ適応してみましょう。

$ bundle exec rake 'db:migrate:up[2]'
 == Performing Up Migration #2: create_categories
   CREATE TABLE `categories` (`id` SERIAL PRIMARY KEY, `name` VARCHAR(20), `created_at` DATETIME, `updated_at` DATETIME) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT=''
   -> 0.1353s
 -> 0.1442s
 == Performing Up Migration #2: create_movies
   CREATE TABLE `movies` (`id` SERIAL PRIMARY KEY, `title` VARCHAR(255), `released_on` DATE, `imdb_url` VARCHAR(255), `created_at` DATETIME, `updated_at` DATETIME) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT=''
   -> 0.1231s
 -> 0.1254s

両方とも適用されました。migration_infoは次のとおり。

> select * from migration_info;
+-------------------+
| migration_name    |
+-------------------+
| create_categories |
| create_movies     |
| create_users      |
+-------------------+
3 rows in set (0.00 sec)

名前が重複していた場合どうなるか。

名前が重複したケースも試してみましょう。

$ cp db/migrate/003_create_category_movies.rb db/migrate/004_create_category_movies.rb
% bundle exec rake db:migrate
rake aborted!
Migration name conflict: 'create_category_movies'
/home/troter/.rvm/gems/ruby-1.9.3-p327/gems/dm-migrations-1.2.0/lib/dm-migrations/migration_runner.rb:43:in `migration'
db/migrate/004_create_category_movies.rb:3:in `<top (required)>'
/home/troter/.rvm/gems/ruby-1.9.3-p327/gems/activesupport-3.2.13/lib/active_support/dependencies.rb:245:in `load'
# ------------ 省略

コンフリクトという事でエラーになりました。

まとめ