AASMとcounter_cultureを同時に使うとno_direct_assignment出来ない
約1年半ぶりの記事となった
なんかAASMでno_direct_assignment設定したら同じ操作してもエラー発生する時としない時と起き始めて混乱してる。
— 天然ほっけ (@NaturalHokke) 2017年12月26日
原因がさっぱりわからんぞい・・・
AASM、no_direct_assignment設定した時にvalidation通さなければ確実に通るのは確認出来たけど、validation通した時になぜエラー発生してしまうのかがまだ謎
— 天然ほっけ (@NaturalHokke) 2017年12月29日
んあー、わかった・・・・
— 天然ほっけ (@NaturalHokke) 2017年12月29日
AASMとcounter_cultureの相性が悪いんだコレ・・・
えぇ・・・これcounter_cultureがなんで変更前のモデル再現する必要があるのかまで追わないと行けないのか・・・?
— 天然ほっけ (@NaturalHokke) 2017年12月29日
つらすぎる・・・
aasm/no_direct_assignment/counter_culture
— 天然ほっけ (@NaturalHokke) 2017年12月29日
で検索して何も出ないって事は踏んだ奴おらんのか
微妙なエッジケース(?)に当たりAASMのコードから頑張ってステップ実行を繰り返しcounter_cultureに行き着いたというお話。
結論
AASMを利用しているクラスでcounter_cultureを利用(counter_culture :xxxx
を記述する)とAASMでno_direct_assignment
は設定出来ない。
ただしskip_validation
も併用すれば利用は可能。
以下説明
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!
の処理の流れを(ちょっと端折りつつ)順に追っていこう。
- まずはイベント発火のココ
- トランザクションを開始し内部処理へsuper
- イベント処理の実態のココ
- 素直に処理を進め状態遷移処理へ
- 状態遷移処理の中の実際に代入処理が行われるのがココ。そしてココ
- ココでついに状態が設定される
- そしてそのまま保存処理が始まるhttps://github.com/aasm/aasm/blob/v4.12.3/lib/aasm/persistence/orm.rb#L24 https://github.com/aasm/aasm/blob/v4.12.3/lib/aasm/persistence/active_record_persistence.rb#L68
ここで言う所のself
はarticle
自身。article.save
である。
おや?エラー起きないじゃないかと思いきやココからcounter_cultureへと話が進む。そうコールバックだ。
コメントが書かれているのがなにやら不安にさせてくれる。
どうやらカウンタキャッシュに変更が無いか確認するらしい。
- 変更前のデータを作ろうとして https://github.com/magnusvk/counter_culture/blob/master/lib/counter_culture/counter.rb#L261
- これである https://github.com/magnusvk/counter_culture/blob/master/lib/counter_culture/counter.rb#L267
そう、データ変更のあったカラムに変更前の値を代入している。わかりやすいコードだ。ん?
https://github.com/aasm/aasm/blob/master/lib/aasm/base.rb#L57
ほげぇぇぇぇぇぇぇぇ!
というわけで、counter_cultureによってno_direct_assignment設定が使えなくなる事を確認する旅はここで終わる。
回避方法としてはココでvalidationを無視してupdate_column
に処理を通す事。
もしくはAASMまたはcounter_cultureのどちらかの利用をやめることである。