Railsのエラー『ArgumentError: AfterCommit callback can only be defined on the root class』の解決方法

Railsのエラー『ArgumentError: AfterCommit callback can only be defined on the root class』の解決方法

Railsのモデルにafter_commitコールバックを定義する際に発生する「ArgumentError: AfterCommit callback can only be defined on the root class」エラーの原因と対策について説明します。このエラーは、継承関係のあるモデルで発生することが多く、適切な修正を行うことで解決できます。

エラーの発生条件

このエラーは、STI(Single Table Inheritance)を使用したモデルのサブクラスでafter_commitを定義しようとした場合に発生します。たとえば、以下のようなコードが原因になります。

class Parent < ApplicationRecord
end

class Child < Parent
  after_commit :some_method
  
  def some_method
    puts "After commit callback executed"
  end
end

Railsは、after_commitコールバックをルートクラス(最上位の親クラス)で定義することを前提としているため、サブクラスで定義するとエラーになります。

エラーメッセージの例

ArgumentError: AfterCommit callback can only be defined on the root class (Parent)

解決策1: ルートクラスで定義する

after_commitをSTIの親クラスで定義することでエラーを回避できます。

class Parent < ApplicationRecord
  after_commit :some_method
  
  def some_method
    puts "After commit callback executed"
  end
end

class Child < Parent
end

解決策2: コールバックを個別に適用

サブクラスごとに異なる処理をしたい場合は、カスタムメソッド内で処理を分岐させることができます。

class Parent < ApplicationRecord
  after_commit :common_after_commit
  
  private
  
  def common_after_commit
    if self.is_a?(Child)
      puts "Child-specific after_commit"
    else
      puts "Parent-specific after_commit"
    end
  end
end

解決策3: ActiveSupport::Concernを使用

モジュールを作成し、STIのサブクラスで適用する方法もあります。

module AfterCommitHandler
  extend ActiveSupport::Concern

  included do
    after_commit :some_method
  end

  def some_method
    puts "After commit callback executed"
  end
end

class Parent < ApplicationRecord
end

class Child < Parent
  include AfterCommitHandler
end

解決策4: コールバックの代わりにActiveJobを使用

コールバックを使用せず、非同期処理として実行する方法も有効です。

class SomeJob < ApplicationJob
  queue_as :default

  def perform(record_id)
    record = Parent.find(record_id)
    puts "After commit logic executed for #{record.id}"
  end
end

class Parent < ApplicationRecord
  after_commit -> { SomeJob.perform_later(self.id) }
end

まとめ

  • after_commitはSTIのルートクラスで定義する
  • サブクラスごとの処理を分岐させる場合はメソッド内で条件分岐
  • ActiveSupport::Concernを活用してモジュールとして適用する
  • コールバックの代わりにActiveJobを使用する方法も考慮する

これらの方法を活用することで、「ArgumentError: AfterCommit callback can only be defined on the root class」エラーを回避し、適切にafter_commitを活用できます。