こんにちは、マッチング領域でバックエンドエンジニアをしているぽこひで ( @pokohide ) です。
前回はRails edgeでCIを回し始めた話を紹介しました。
今回は、実際に弊社でCIをRails edgeで回し始めた事で見つけたエラーの例を紹介していきます。記事公開時点(2023年7月)のバージョンは下記の通りです。
$ ruby -v ruby 3.2.2 (2023-03-30 revision e51014f9c0) +YJIT [aarch64-linux] $ rails -v Rails 7.0.6
ActiveRecord::DangerousAttributeError object_id is defined by Active Record
このエラーに関する参考記事はこちらです。
一部のモデルで object_id
というカラム名を定義していたため以下のようなエラーが発生し、そのレコードを生成しているテストが軒並み落ちました。
ActiveRecord::DangerousAttributeError: object_id is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name. # /usr/local/bundle/gems/factory_bot-6.2.1/lib/factory_bot/decorator/new_constructor.rb:9:in `new' # /usr/local/bundle/gems/factory_bot-6.2.1/lib/factory_bot/decorator.rb:16:in `send' ...
Objectクラスで、オーバーライドされると予期せぬ影響を与えうるメソッド名と同じ名前を利用できなくなったからでした。 object_id
に限らず dup
, freeze
, hash
, class
, clone
なども利用できなくなるのでそういったカラム名を使わないようにする必要があります。
このエラーは object_id
を object_identifier
とリネームする事で対応しました。
Rails7.0.5.1からの create_association
の挙動変化によるエラー
巷で話題のcreate_associationの挙動変更によるエラーです。既存レコードが存在する場合にユニーク制約によりバリデーションエラーが発生するといってテストケースがありましたが、そちらのテストが落ちました。Rails edgeを回し始めた頃はRails v7.0.4だったため、このエラーに遭遇しました。
Rails v7.0.5.1から create_association
の挙動が「別々のトランザクションでinsertしてからdeleteする」から「同一トランザクションでdeleteしてからinsertする」に変わりました。この影響により、DBのレコードに依存するバリデーション(例: validates_uniquness_of
)が効かなくなりました。この挙動変化に関する内容はこちらをご参考ください。
parser gemを利用してcreate_associationの呼び出し箇所を調査し、影響範囲を一つずつ確認、必要な箇所に条件文を追加するなどして対応しました。
既にリリースされている変更ではありますが、Rails edgeでCIを回し始めたことで早期に問題を発見でき、create_association削除する前に検証を挟む新しいオプションの提案をrails/railsへのPR*1を通して行うなどしました。
NoMethodError: undefined method `reading_role' for ActiveRecord::Base:Class
Rails7.1から ActiveRecord::Base.reading_role
がなくなるため、この記述を行っていた箇所のテストが落ちました。
irb(main):002:0> ActiveRecord::Base.reading_role {"severity":"WARN","message":"DEPRECATION WARNING: ActiveRecord::Base.reading_role is deprecated and will be removed in Rails 7.1.\nUse `ActiveRecord.reading_role` instead.\n (called from xxxxx)"} => :reading irb(main):003:0> ActiveRecord::Base.writing_role {"severity":"WARN","message":"DEPRECATION WARNING: ActiveRecord::Base.writing_role is deprecated and will be removed in Rails 7.1.\nUse `ActiveRecord.writing_role` instead.\n (called from xxxxx)"} => :writing
この問題に関しては元々出ていたWarningの内容に従い、ActiveRecord.reading_role
を代わりに使う事で対応しました。
partialsにインスタンス変数をlocalsとして渡す挙動が削除されたことによるエラー
弊社では請求書(PDF)を生成するために ActionController::Base#render_to_string
を用いてHTML文字列を取得しています。その処理の中でpartialsにインスタンス変数をlocalsとして渡していましたが、そこでエラーが発生しました。
今までWarningが出ていた内容ではありますが、Rails7.1からインスタンス変数をlocalsで渡せなくなったためです*2。
インスタンス変数ではなくローカル変数として渡すことで対応を予定しています。
before_type_castの返り値の型が変わったことによるエラー
# create_table :posts, force: true do |t| # t.integer :foo, default: 1, null: false # end class Post < ActiveRecord::Base enum foo: { x: 1, y: 2 } end
Integer型でEnumを定義しているカラム foo
を持つモデルのレコードに対して record.foo_before_type_cast
で参照すると、元々の環境ではInteger型で返っていたものが、Rails edge環境下ではString型で返るようになったため落ちているテストケースを見つけました。
record = Post.new(foo: :x) # 検証当時の環境(Rails v7.0.6) record.foo_before_type_cast => 1 # Rails edge record.foo_before_type_cast => "1"
今回のエラーを再現するコード*3を用意し、 git bisect
*4 を利用して二分探索で挙動が変わったをコミットを調査しました。その後のコミットやPRを追ったところ、rails v7.1からDBの型で値を取得する *_for_database
が追加されていることに気づいたため、その実装をバックポートして使うように修正することで対応を行いました。
# frozen_string_literal: true case Rails::VERSION::STRING when /^7\.0/ # 以下の定義を読み込むために何もしない。 when /^7\.1/ # v7.1 には含まれているため読み込まない。 return else # v7.2 以降で削除し忘れないように例外を投げる。 raise('Consider removing this patch') end module ActiveRecord module AttributeMethods module BeforeTypeCast # refs: https://github.com/rails/rails/pull/46283 def read_attribute_for_database(attr_name) name = attr_name.to_s name = self.class.attribute_aliases[name] || name attribute_for_database(name) end end end end
最後に
今回は、Rails edgeでCIを回すことによって見つけた将来動かなくなるコードの早期発見やその対応、原因についての簡単な解説を行いました。また紹介しきれていないですが、エラーだけでなくRails 7.2からの廃止予定を知らせるWarningもいくつか確認できました。
Rails edge導入当初は112個のテストケースが落ちましたが、徐々に対応を行なっていき落ちるテストケースは22件にまで減りました。こういった活動を継続することでRubyやRailsのコミュニティの進化に追随できるので引き続き頑張っていこうと思います。
タイミーでは一緒にサービスを成長させていく方を募集しています。もし少しでも興味を持っていただけたら、カジュアル面談受け付けておりますので是非お話ししましょう!