Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails 7: touchやupdate_column(s)によるreadonlyモデル更新を修正(翻訳)

概要

元サイトの許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

参考: 週刊Railsウォッチ20220829前編 touchupdate_columnupdate_columnsがreadonlyレコードでエラーを出すよう修正

この#44845Rails 7.0.3反映済みです(Changelogには記載されていません)。

Rails 7.0.3、6.1.6、6.0.5、5.2.8がリリースされました

Rails 7.1: touchやupdate_column(s)によるreadonlyモデル更新を修正(翻訳)

Railsのモデルには、非常にシンプルな方法でデータベースにアクセスできるActive Recordのメソッドがたくさんあります。モデルの作成、更新、削除はきわめて簡単です。

しかし、場合によってはモデルを"readonly"とマーキングすることで更新や削除を不可能にし、読み取りだけを許可したいことがあります。このような状況は、大規模なリファクタリングで起きることがあります。使わなくなったモデルがあったとしても、すぐ消してよいとは限らず、読み取りだけは行う必要があるかもしれません。

改修前

これを行うには、そのモデルにreadonly?メソッドを追加してtrueを返すようにします。

class Rocket
  ...

  def readonly?
    true
  end
end

これで、モデルでupdatedestroyを実行すると期待通りエラーが発生するようになります。

irb(main):001:0> rocket.update name: "A1"
  TRANSACTION (5.4ms)  BEGIN
  User Load (6.2ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  TRANSACTION (1.8ms)  ROLLBACK
Traceback (most recent call last):
/Users/swaathi/.rbenv/versions/2.7.5/lib/ruby/gems/2.7.0/bundler/gems/rails-08af60e01dcf/activerecord/lib/active_record/persistence.rb:1147:in `_raise_readonly_record_error': Rocket is marked as readonly (ActiveRecord::ReadOnlyRecord)

irb(main):002:0> rocket.destroy
Traceback (most recent call last):
/Users/swaathi/.rbenv/versions/2.7.5/lib/ruby/gems/2.7.0/bundler/gems/rails-08af60e01dcf/activerecord/lib/active_record/persistence.rb:1147:in `_raise_readonly_record_error': Rocket is marked as readonly (ActiveRecord::ReadOnlyRecord)

どちらの場合もActiveRecord::ReadOnlyRecordエラーが発生します。

しかし、モデルでtouchメソッドを実行してもエラーが発生しません。

irb(main):002:0> rocket.touch
  TRANSACTION (0.5ms)  BEGIN
  Rocket Update (14.2ms)  UPDATE `rockets` SET `rockets`.`updated_at` = '2022-08-14 06:40:24.291104' WHERE `rockets`.`id` = 1
  TRANSACTION (5.6ms)  COMMIT
=> true

readonlyモデルでtouchメソッドを呼び出してもさほど被害はありませんが、update_columnsを呼び出してしまったときの被害は深刻です。残念ながらモデルをreadonlyにしても、モデルの更新をあらゆる方法で防げるわけではありません。readonlyモデルが不当に更新されてしまうと、データが破損してしまいます。

改修後

この問題が#44839で指摘されると、ただちにRailsチームが#44845でパッチを当てました。

この問題は、Actrive Recordのupdate_columnstouchを更新する形で修正されました。

# activerecord/lib/active_record/persistence.rb
...
module ActiveRecord
  module Persistence
    extend ActiveSupport::Concern

    module ClassMethods
      def update_columns
        ...
        _raise_readonly_record_error if readonly?
        ...
      end
    end
  end
end

これで、update_columnstouchを呼び出してもreadonlyモデルが更新されることはなくなりました。

関連記事

Rails 7: リレーションの結果が空になる計算でクエリ送信を回避する(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。