環境
- Ruby 2.5.1
- Rails 5.2.1
- Mysql2 0.5.1
前提
そもそも、Rails において複合主キーの Model を扱うことはサポート外になります。複合主キーを使うと下記のような警告も出力されます。
WARNING: Active Record does not support composite primary key.
Composite primary key is ignored.
また、 find
, update
, save
, delete
メソッドの使用が不可能になります。これらのメソッドを呼び出すと下記のようなエラーを出力します。
Model
- MultiPkeyModel という複合主キーの Model があったとします
- pkey_1, pkey_2 カラムが主キーとします
find
MultiPkeyModel.find(1) # 数値は適当
# ActiveRecord::UnknownPrimaryKey:
# Unknown primary key for table multi_pkey_models in model MultiPkeyModel.
そもそも id カラムを持たないので find の引数に何も指定しようがないですが、引数有無問わず上記のエラーが発生することを確認しました。
update, save, delete
record = MultiPkeyModel.find_by(pkey_1: somevalue1, pkey_2: somevalue2)
# update
record.update(foo: 'bar')
# ActiveRecord::StatementInvalid: Mysql2::Error:
# Unknown column 'multi_pkey_models.' in 'where clause':
# UPDATE `multi_pkey_models` SET `foo` = 'bar' WHERE `multi_pkey_models`.`` IS NULL
# save
record.foo = 'bar'
record.save # => update と同様のエラーのため省略
# delete
record.delete
# ActiveRecord::StatementInvalid: Mysql2::Error:
# Unknown column 'multi_pkey_models.' in 'where clause':
# DELETE FROM `multi_pkey_models` WHERE `multi_pkey_models`.`` IS NULL
いづれの場合も、 SQL 文の WHERE 句が WHERE `multi_pkey_models`.`` IS NULL
というおかしな状態になっているため失敗していると思われます。 Model クラスが id カラムを持たないため、本来 id カラムが指定されうる箇所が空になってしまっています。
本題
では、どうするべきか。そもそも複合主キーによるテーブルを使った運用自体をよしとするかどうかという話もありますが、本項ではそこまで壮大な話は扱わず、 複合主キーテーブルを使わざるをえない状況 という前提で、苦肉の策を共有してみたいと思います。
find の代わり
これは find_by
ですみます。レコードの更新を伴わない場合はすぐには困ることもなさそうです。
MultiPkeyModel.find_by(pkey_1: somevalue1, pkey_2: somevalue2)
update, delete の代わり
身も蓋もないですが、SQL文を直接発行したほうが手っ取り早いと思います。
下記は update を行う場合のコードの記述例です。
sql = <<-SQL
UPDATE `multi_pkey_models` SET `foo` = :foo
WHERE `pkey_1` = :pkey_1 AND `pkey_2` = :pkey_2
SQL
ActiveRecord::Base.connection.execute(ActiveRecord::Base.send(
:sanitize_sql_array,
[
sql,
foo: 'bar',
pkey_1: someValue1,
pkey_2: someValue2
]
))
どうしても Active Record を使いたい場合は update_all
という手もあります。
MultiPkeyModel.where(pkey_1: someValue1, pkey_2: someValue2).update_all(foo: 'bar')
ただ、update_all は本来は複数のレコードをまとめて更新するためのメソッドです。 pkey_1, pkey_2 の組み合わせを持つレコードは必ず1つであることがテーブルの制約上保証されてはいますが、そういった背景を知らない人がコードを読んだ際に、コード上は複数のレコードを更新しにいっているように見えるので、あまり読み手に優しくないと思います。
ただ、「なんでここSQL文直接書いてるんだろう?」と思う人もいるかもしれないのでSQL文生書きにしていることの理由などをコメントで補足しておくといいとおもいます。あれ、update_all 呼んでコメント書くのも一緒か とも思いましたが、そこはエンジニア各位のご判断にお任せしたいとおもいます。
まとめ
複合主キーを持つテーブルを扱う際には、各種 CRUD メソッドが想定どおり機能しないケースがあるのでご注意ください。
ちなみに
gem でなんとかしている例もけっこうあるようです。実際問題、筆者も含め複合主キーってけっこう使いますよね。。。
- composite-primary-keys/composite_primary_keys: Composite Primary Keys support for Active Record
- 「composite_primary_keys」の検索結果 - Qiita
識者からのご指摘、ご感想ありましたらぜひコメントいただけるとうれしいです。