食べられません

プログラミングとか漫画とか生活とか

AASMとcounter_cultureを同時に使うとno_direct_assignment出来ない

約1年半ぶりの記事となった

微妙なエッジケース(?)に当たりAASMのコードから頑張ってステップ実行を繰り返しcounter_cultureに行き着いたというお話。

結論

AASMを利用しているクラスでcounter_cultureを利用(counter_culture :xxxxを記述する)とAASMでno_direct_assignmentは設定出来ない。
ただしskip_validationも併用すれば利用は可能。

以下説明

github.com github.com

class Article < ApplicationRecord
  belongs_to :area
  counter_culture :area
  aasm do
    state :draft, initial: true
    state :published

    event :publish do
      transitions from: :draft, to: :published, after: -> { self.published_at = Time.current }
    end
  end
end

AASMはクラスの状態管理gemで、no_direct_assignmentは状態の直接設定をエラーとし、イベントによる状態遷移を強制するための設定である。(という認識)
また、counter_cultureはRails標準のcounter_cacheの高機能版gem。

例示のイメージとしては、下書きと公開済みのステータスをもち、何らかのエリアに紐づく記事クラスである。
エリアで記事数のカウントをキャッシュするためにcounter_cultureを設定している。
また、publishイベントにより公開された際に記事の公開日時を保存するようにしているとする。

さて、このコードの段階ではまだ何も問題はない。
ここで、状態を直接設定される(直接公開状態にされる)と公開日時が設定されないので
イベント発火による公開しか出来ないようno_direct_assignmentを設定しておこう、となったとする(なった)。以下が修正後。

no_direct_assignment設定で行われる内容はこんな感じ
わかりやすく、aasmのカラム名に対して直接代入しようとする部分を書き直してエラー処理を追加している。

class Article < ApplicationRecord
  belongs_to :area
  counter_culture :area
  aasm no_direct_assignment: true do
    state :draft, initial: true
    state :published

    event :publish do
      transitions from: :draft, to: :published, after: -> { self.published_at = Time.current }
    end
  end
end

この設定を追加したことにより、このクラスはイベント発火後のデータ保存のタイミングで必ず死ぬようになる。
article.publish!の処理の流れを(ちょっと端折りつつ)順に追っていこう。

ここで言う所のselfarticle自身。article.saveである。
おや?エラー起きないじゃないかと思いきやココからcounter_cultureへと話が進む。そうコールバックだ。

  • counter_culture :areaによりココに話が飛ぶ
  • そして問題のコレ

コメントが書かれているのがなにやら不安にさせてくれる。
どうやらカウンタキャッシュに変更が無いか確認するらしい。

そう、データ変更のあったカラムに変更前の値を代入している。わかりやすいコードだ。ん?
https://github.com/aasm/aasm/blob/master/lib/aasm/base.rb#L57
ほげぇぇぇぇぇぇぇぇ!

というわけで、counter_cultureによってno_direct_assignment設定が使えなくなる事を確認する旅はここで終わる。

回避方法としてはココでvalidationを無視してupdate_columnに処理を通す事。
もしくはAASMまたはcounter_cultureのどちらかの利用をやめることである。