LoginSignup
11
9

More than 5 years have passed since last update.

[Rails]複合主キーを持つテーブルのレコードを更新する(*非常用)

Posted at

環境

  • 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 でなんとかしている例もけっこうあるようです。実際問題、筆者も含め複合主キーってけっこう使いますよね。。。

識者からのご指摘、ご感想ありましたらぜひコメントいただけるとうれしいです。

11
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
9