Rails4 db:migrateでid以外のカラムにプライマリキーの設定を行う
モチベーション
Railsでテーブルのidカラム以外にプライマリキーを設定したい。インターネットに転がっている情報で色々やってみたが上手く行かなったので自分なりに調べてまとめる。
背景
Railsはその設計からidをテーブルのプライマリキーに自動的に設定してくれる。しかし、時にはid以外のカラムにプライマリキーの設定をしたい場合もあるだろう。公式ドキュメントやブログ情報によると、そういう時はオプションを指定することで任意のカラムにプライマリキーの設定が可能とのこと。
当初は下に挙げたドキュメントやブログを参考にテーブルのプライマリキーを設定しようとした。しかし、idが自動でプライマリキーに設定されることはなくなったが、 肝心の任意のキーをプライマリキーにするという要件を満たせなかった。
- Ruby on Rails 4.1.8
- RailsGuides Active Record Basics
- 「ActiveRecord」の基本とデータの参照
- Railsで規約に沿わない古いデータを扱う
- Ruby on Rails - ActiveRecord で規約外のプライマリキーを使用する方法!
どうやら、公式ドキュメント通りだと思った通りの動作をしないようだ。公式ドキュメントが信頼出来ないとなると、どのように記述すればこちらの希望する処理になるかはフレームワークのコードを読まないとわからない。そうなると、フレームワークの意味が無いのだが・・・ただ、これだけ世で使われているフレームワークなので自分の勘違いの可能性もある。動作検証の結果と追加調査をを以下にまとめる。
検証してみた
環境
- MacOSX 10.9.4
- Ruby 2.1.2
- Ruby on Rails 4.18
作りたいテーブル
MySQLで以下のクエリを実行した時に作成されるテーブルと同様のものをrake db:migrate
で作成する。
CREATE TABLE books(
book_id INTEGER NOT NULL,
title VARCHAR(250) NOT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
PRIMARY KEY(book_id)
);
通常の手順で作成したスキーマの確認
まずは、何も考えずに普通の手順で作成したテーブルスキーマを確認する。 手順は以下のとおり。
$ rails new book -d mysql
$ rails g model book book_id:integer title:string
$ rake db:create
$ rake db:migrate
作成されたテーブルのスキーマは以下の通り。
mysql>describe books;
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| book_id | int(11) | YES | | NULL | |
| title | varchar(255) | YES | | NULL | |
| created_at | datetime | YES | | NULL | |
| updated_at | datetime | YES | | NULL | |
+------------+--------------+------+-----+---------+----------------+
5 rows in set (0.01 sec)
これを見ると、自動的にidカラムが作成されており、idカラムのみプライマリキーになっている。
公式ドキュメントの設定を検証する
Ruby on Rails 4.1.8には以下のように記述されている。
The options hash can include the following keys:
:id
Whether to automatically add a primary key column. Defaults to true. Join tables for has_and_belongs_to_many should set it to false.
:primary_key
The name of the primary key, if one is to be added automatically. Defaults to id. If :id is false this option is ignored.
Note that Active Record models will automatically detect their primary key. This can be avoided by using self.primary_key= on the model to define the key explicitly.
最後の2行に、”modelを定義しているファイルでself.primary_key
を設定とすると自動でActiveRecordがプライマリキーを検知する”と書かれているので、
以下のようにしてrake db:migrate
を実行する。
class Book < ActiveRecord::Base
self.primary_key = "book_id"
end
primary_keyオプションは任意のプライマリキーを指定するためにあり、デフォルトだとidがプライマリキーになる。
また、id: falseの設定が入っているとこのオプションは無視されるらしい。なので、idオプションをfalseにしないで、primary_keyの値を変更し、Modelの定義でself.primary_key = :book_id
を設定しrake db:migrate
を実行した。
➤ rake db:migrate
== 20141128014807 CreateBooks: migrating ======================================
-- create_table(:books, {:primary_key=>"book_id"})
rake aborted!
StandardError: An error has occurred, all later migrations canceled:
you can't redefine the primary key column 'book_id'. To define a custom primary key, pass { id: false } to create_table./Users/takayuki/Dropbox/002_Study/ruby_on_rails_study/test/books3/db/migrate/20141128014807_create_books.rb:4:in `block in change'
エラーがでてきた。なになに、idをfalseにしてください・・・? idをfalseにしたらprimary_keyオプション無視するんじゃないの?英語の解釈間違えているのだろうか?
マイグレーションファイルを書き換えて、
class CreateBooks describe books;
+------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+-------+
| book_id | int(11) | YES | | NULL | |
| title | varchar(255) | YES | | NULL | |
| created_at | datetime | YES | | NULL | |
| updated_at | datetime | YES | | NULL | |
+------------+--------------+------+-----+---------+-------+
4 rows in set (0.01 sec)
プライマリーキーが設定されてない!!!
プライマリキーを設定する方法の調査2
公式ドキュメントの方法では任意のカラムにプライマリキーを設定できなかった。 多くのドキュメントやブログでプライマリキーの設定方法を載せているが、ほとんど上で試した設定方法が紹介されている。
一方、以下の情報サイトでは違う方法でプライマリキーを設定している。
- StackOverFlow:Problems setting a custom primary key in a Rails 4 migration
- Specify custom primary key in migration
- Changing the Primary Key Type in Ruby on Rails Models
- Rails探訪 ~ create_table 編 ~
その中から、プライマリキーの設定が正常にできたものを2つ挙げる。
プライマリキーの設定が上手く行った例1
このパターンではexecute
によりSQL文を発行してプライマリーキーの設定を行っている。
試していないが、他のSQLの発行にも使えると思うのでSQL書くのに慣れている人はこの形式が良いのではないだろうか。
Migrationファイル
class CreateBooks < ActiveRecord::Migration
def change
create_table :books, id: false do |t|
t.integer :book_id
t.string :title
t.timestamps
end
execute "ALTER TABLE books ADD PRIMARY KEY (book_id);"
end
end
テーブルスキーマの確認
mysql> describe books;
+------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+-------+
| book_id | int(11) | NO | PRI | 0 | |
| title | varchar(255) | YES | | NULL | |
| created_at | datetime | YES | | NULL | |
| updated_at | datetime | YES | | NULL | |
+------------+--------------+------+-----+---------+-------+
プライマリキーの設定が上手く行った例2
このパターンではt.column
の後にカラム名とオプションを指定することで、
任意のカラムにプライマリーキーを紐付けている。しかし、直感的にわかりにくいので、個人的にはexecute
の後にプライマリキーを指定する方法を採用したい。
Migrationファイル
class CreateBooks < ActiveRecord::Migration
def change
create_table :books, id: false do |t|
t.column :book_id, 'INTEGER PRIMARY KEY AUTO_INCREMENT'
t.string :title
t.timestamps
end
end
end
テーブルスキーマの確認
mysql> describe books;
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| book_id | int(11) | NO | PRI | NULL | auto_increment |
| title | varchar(255) | YES | | NULL | |
| created_at | datetime | YES | | NULL | |
| updated_at | datetime | YES | | NULL | |
+------------+--------------+------+-----+---------+----------------+
4 rows in set (0.01 sec)
注意すべきなのはデータベースごとにSQLの書き方が異なるので、'INTEGER PRIMARY KEY AUTO_INCREMENT'
のように直接指定するときは意識すること。
例えば、オートインクリメントオプションを付けるときは、AUTOINCREMENT(sqlite3)なのかAUTO_INCREMENT(MySQL)なのかを意識すること。
結論
プライマリキーやauto_incrementの設定などはexecute "ALTER TABLE books ADD PRIMARY KEY (book_id);"
のようにSQLを実行させる方法を採用した方が間違いがない。
ちょっと後でコードを読んで処理内容を確認しよう。