Rails 移行検証の過程で既存のテーブルを操作してみようとしたら,一部のテーブルで .find
がエラーになってハマったのでメモ程度に残しておく.原因としては id
と別のカラムの複合プライマリーキーになっていることで,Rails は複合プライマリーキーを許容していなかった.そんなこと知らないよー!って感じ.
単一プライマリーキーの場合
サンプルとして id
をプライマリーキーとした teams
テーブルを作成する.スキーマが雑だけど許して!
CREATE TABLE `teams` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `name` varchar(255) NOT NULL, `created_at` datetime DEFAULT NULL, `updated_at` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
適当にデータを投入して ActiveRecord から .find
で取得してみる.当たり前だけど取得できる.
pry(main)> Team.find(1) Team Load (0.8ms) SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 1 LIMIT 1 #<Team:0x007fee2ddc8d18> { :id => 1, :user_id => 1, :name => "A", :created_at => Thu, 01 Jan 2015 00:00:00 UTC +00:00, :updated_at => Thu, 01 Jan 2015 00:00:00 UTC +00:00 }
複合プライマリーキーの場合
次に id
と user_id
をプライマリーキーとした teams
テーブルを作成する.
CREATE TABLE `teams` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `name` varchar(255) NOT NULL, `created_at` datetime DEFAULT NULL, `updated_at` datetime DEFAULT NULL, PRIMARY KEY (`id`, `user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
複合プライマリーキーだと ActiveRecord で .find
が使えないことがわかる.
pry(main)> Team.find(1) ActiveRecord::UnknownPrimaryKey: Unknown primary key for table teams in model Team.
.where(id: 1).first
で回避することもできるけど微妙過ぎる.
pry(main)> Team.where(id: 1).first Team Load (0.8ms) SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 1 LIMIT 1 #<Team:0x007fee3018efb8> { :id => 1, :user_id => 1, :name => "A", :created_at => Thu, 01 Jan 2015 00:00:00 UTC +00:00, :updated_at => Thu, 01 Jan 2015 00:00:00 UTC +00:00 }
解決策1 : composite_primary_keys
composite_primary_keys
を使って複合プライマリーキーに対応できた.ただコードに違和感があってメリットが感じられなかった.
お決まりのように Gem を追加する.
gem 'composite_primary_keys'
モデルに primary_keys
を定義する.
class Team < ActiveRecord::Base self.primary_keys = :id, :user_id end
.find([:key1, :key2])
という構文で使えるようになる.嫌だー!絶対に嫌だー!!!
[16] pry(main)> Team.find([1, 1]) Team Load (0.7ms) SELECT `teams`.* FROM `teams` WHERE (`teams`.`id` = 1 AND `teams`.`user_id` = 1) LIMIT 1 #<Team:0x007fee29b6a020> { :id => [ [0] [ [0] 1, [1] 1 ], [1] 1 ], :user_id => 1, :name => "A", :created_at => Thu, 01 Jan 2015 00:00:00 UTC +00:00, :updated_at => Thu, 01 Jan 2015 00:00:00 UTC +00:00 }
解決策2 : スキーマをリファクタリングして単一プライマリーキーにしちゃう
技術的負債を抱えながら開発を進めるぐらいなら先に解消しちゃおう!っていう考え方.
プライマリーキーを削除しようとすると auto_increment
だから消せないよって怒られた.
mysql> ALTER TABLE teams DROP PRIMARY KEY; ERROR 1075 (42000): Incorrect table definition; there can be only one auto column and it must be defined as a key
プライマリーキーの削除と追加を1個の SQL で書けばできる.
mysql> ALTER TABLE teams DROP PRIMARY KEY, ADD PRIMARY KEY(id); Query OK, 2 rows affected (0.02 sec) Records: 2 Duplicates: 0 Warnings: 0
これで幸せになれそう!
pry(main)> Team.find(1) Team Load (0.6ms) SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 1 LIMIT 1 #<Team:0x007fee2d951140> { :id => 1, :user_id => 1, :name => "A", :created_at => Thu, 01 Jan 2015 00:00:00 UTC +00:00, :updated_at => Thu, 01 Jan 2015 00:00:00 UTC +00:00 }
複合プライマリーキーを持ったテーブル一覧を検索する
参考情報だけど,こんな SQL で調べられるはず!
mysql> SELECT TABLE_NAME, COUNT(*) -> FROM information_schema.KEY_COLUMN_USAGE -> WHERE -> CONSTRAINT_SCHEMA = 'xxx' AND -> CONSTRAINT_NAME = 'PRIMARY' -> GROUP BY TABLE_NAME -> HAVING COUNT(*) > 1;