rails で id を BIGINT 型 + primary key + AUTO_INCREMENT する
イントロ
rails のモデルは自動で id というカラムを作ってくれますが、これは常に INT 型。
BIGINT
型にしようとしたら、結構ハマったのでメモする。
使っている database はmysql
です。
(postgresql
だとこの罠は回避できるのだろうか?)
tl;dr
- 普通に頑張ると...
db:migrate
とdb:setup
の実行結果が異なる- migration ファイルの内容が、
schema.rb
に(一部)反映されないため
- migration ファイルの内容が、
db:migrate
とdb:setup
の実行結果が異なると、db:rollback
ができなくなるので、辛くなりそう。db:migrate
かdb:setup
どちらかを使わない、という運用方式もある- だが、面倒くさいので、そういうことを意識したくない
- 解決方法は 2 つ
format_schema = :sql
- kamipo/activerecord-mysql-awesome を使う
普通にやってみる
Rails探訪 ~ create_table 編 ~ | マネーフォワード エンジニアブログ の素晴らしい参考記事によると、こんな感じ。
(ところで参考記事を見ると、色々地雷を踏み抜いてこの結論なので、この時点で既に普通でない気もする)
class CreateUsers < ActiveRecord::Migration def change create_table :users, id: false do |t| t.column :id, 'BIGINT PRIMARY KEY AUTO_INCREMENT' t.string :name t.timestamps null: false end end end
rake db:migrate
% rake db:drop db:create db:migrate
mysql> desc users; +------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+----------------+ | id | bigint(20) | NO | PRI | NULL | auto_increment | | name | varchar(255) | YES | | NULL | | | created_at | datetime | NO | | NULL | | | updated_at | datetime | NO | | NULL | | +------------+--------------+------+-----+---------+----------------+ 4 rows in set (0.00 sec)
無事成功。
rake db:setup
一見うまくいってそうだが、実はこれは問題がある。
db/scheme.rb
に BIGINT
型にしたよ、という内容が反映されていないために、db:setup
をやると db:migrate
の結果と異なったものになってしまう。
% rake db:setup
mysql> desc users; +------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | name | varchar(255) | YES | | NULL | | | created_at | datetime | NO | | NULL | | | updated_at | datetime | NO | | NULL | | +------------+--------------+------+-----+---------+----------------+ 4 rows in set (0.01 sec)
INT型
に戻ってしまった。
じゃあどうするの?と言うと、schema_format = :sql
方式か activerecord-mysql-awesome
方式の二通りがある。
schema_format = :sql
よく見かける解決法。
デフォルトでは schema_format
は ruby なので、これを sql にする。
:ruby
では db/schema.rb
を生成するが、:sql
にすると db/structure.sql
を生成してくれる。
config/application.rb
等に予め設定しておく。
config.active_record.schema_format = :sql
rake db:migrate
migration ファイルはここと同じものを使う。
mysql> desc users; +------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+----------------+ | id | bigint(20) | NO | PRI | NULL | auto_increment | | name | varchar(255) | YES | | NULL | | | created_at | datetime | NO | | NULL | | | updated_at | datetime | NO | | NULL | | +------------+--------------+------+-----+---------+----------------+ 4 rows in set (0.01 sec)
bigint 型になっている。ここまでは良い
rake db:setup
% rake db:setup
mysql> desc users; +------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+----------------+ | id | bigint(20) | NO | PRI | NULL | auto_increment | | name | varchar(255) | YES | | NULL | | | created_at | datetime | NO | | NULL | | | updated_at | datetime | NO | | NULL | | +------------+--------------+------+-----+---------+----------------+ 4 rows in set (0.00 sec)
今度は migrate
と setup
の結果が同じになりました。
activerecord-mysql-awesome
kamipo/activerecord-mysql-awesome
を使う。
この場合 schema_format
は :ruby
のままで良い。
class CreateUsers < ActiveRecord::Migration def change create_table :users, id: :bigint do |t| t.string :name t.timestamps null: false end end end
のような migration ファイルを作る
rake db:migrate
% rake db:drop db:create db:migrate
mysql> desc users; +------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+----------------+ | id | bigint(20) | NO | PRI | NULL | auto_increment | | name | varchar(255) | YES | | NULL | | | created_at | datetime | NO | | NULL | | | updated_at | datetime | NO | | NULL | | +------------+--------------+------+-----+---------+----------------+ 4 rows in set (0.00 sec)
rake db:setup
% rake db:setup
mysql> desc users; +------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+----------------+ | id | bigint(20) | NO | PRI | NULL | auto_increment | | name | varchar(255) | YES | | NULL | | | created_at | datetime | NO | | NULL | | | updated_at | datetime | NO | | NULL | | +------------+--------------+------+-----+---------+----------------+ 4 rows in set (0.00 sec)
migrate
と setup
の結果が同じになった。
change_column 編
create_table
ではなく、change_column
でやったらどうなるか。
create_table
の時点で INT
を BIGINT
にしようと思うことが少ないので、実は change_column
で対応できるかどうかが重要。
create_table
の時と違って、今度は db:rollback
が成功するかどうかもチェックしなければならない。
activerecord-mysql-awesome / schema_format = :sql
sql の確認コマンドは鬱陶しいので省略。
class ChangeIdColumnToUsers < ActiveRecord::Migration def change reversible do |dir| dir.up do change_column :users, :id, :bigint, auto_increment: true end dir.down do change_column :users, :id, :int, auto_increment: true end end end end
この migration ファイルで
rake db:drop db:create db:migrate
rake db:setup
rake db:rollback
が成功する。ばっちり。
activerecord-mysql-awesome
でも schema_format = :sql
でも同じ結果になったので、change_column
の時はこうすれば良さそう。
普通にやる
勿論これも activerecord-mysql-awesome
か schema_format = :sql
をせずに migration しても、 db:setup
で id が INT型
になってしまう。
おまけ
https://github.com/rails/rails/pull/18220 がマージされているので、現時点での rails の master では対応済みのよう。
なので、 第三の選択「もうしばらく待つ」 も有りかも。
少なくとも Rails 4.2.1
では修正は含まれていなかったです。