ActiveRecord::Enumを使って状態遷移するやつを自作した話
RailsにはAASMというモリモリした状態遷移ライブラリがあるみたいなんだけど、
ActiveRecord::Enum(4.1から)と一部被ってるし多機能すぎる印象があったので作ってみた。
自分の欲しかった機能
- ある状態の時に遷移する時は、特定の状態からでしか遷移できないという条件をつけたい
- かつ、状態名を渡すと遷移できるようにしたい
環境
rails4.1.4
ActiveRecord::Enumをすごく使ってる
使い方
要は、#state_update("#{state_name}")と#can_#{state_name}?が使えるようになる。
モデルにActiveRecord::Enum の宣言をする。
この例だと請求書ステータスモデルのstatusカラムをEnumに使うと書いてる。
class InvoiceStatus < ActiveRecord::Base enum status: [ :status_published, :status_ng, :status_checked, :status_wating_send ] # 〜後述するクラスマクロのコードがここにある〜 # 〜後述するクラスマクロのコードがここにある〜 # 〜後述するクラスマクロのコードがここにある〜 # 今回自作したクラスマクロの宣言例 # InvoiceStatus#update_status(:status_published)を実行したらこのブロックが実行される ai_status :status_published, from: [:status_ng] do |record| # can_status_published? が生える. :status_ngの時にいるならブロックが実行される。 // 何かする record.status_published! # AR#Enumのメソッド // 何かする end ai_status :status_ng, from: [:status_published] do |record| // 何かする record.status_ng! // 何かする end ai_status :status_checked, from: [:status_published] do |record| // 何かする record.status_checked! // 何かする end end
ステータスを変更する時
@invoice_status = InvoiceStatus.take # 定義通り状態遷移できるケース @invoice_status.status # => :status_ng @invoice_status.update_status(:status_published) # => true @invoice_status.status # => :status_published @invoice_status.status # => :status_published @invoice_status.update_status(:status_ng) # => true @invoice_status.status # => :status_ng # 定義に書いていない遷移をしようとしたケース @invoice_status.status # => :status_checked @invoice_status.update_status(:status_ng) # => 例外を投げるので どっかでキャッチすればいいと思う @invoice_status.status # => :status_checked
viewでボタンの表示非表示をする時とかに使いそう
@invoice_status = InvoiceStatus.take @invoice_status.status # => :status_ng @invoice_status.can_status_published? # => true @invoice_status.can_status_checked? # => false link_to_if(@invoice_status.can_status_published?, "ボタンは表示されます", "/update_state?state=status_published")
ソース
def self.ai_status(status_name, from: , &block) define_method "can_#{status_name}?" do # can_hoge?が定義される from.map(&:to_s).include?(self.status) ? true : false end class_variable_set("@@_block_#{status_name}", block) end def update_status(status_name) raise('想定していな遷移だ') unless send("can_#{status_name}?") self.class.class_variable_get("@@_block_#{status_name}").call(self) end
所感
状態遷移の条件に一覧性ないけどDRYだしいいんじゃないの。
コールバック用のブロックとかないしシンプル。