Upgrade to Pro — share decks privately, control downloads, hide ads and more …





May 15, 2015


  1. STATESMAN STATESMAN @kbaba1001 Powered by Rabbit 2.1.6 and COZMIXNG

  2. 自己紹介 馬場(kbaba1001) ESM 4 年目

  3. 今日の話

  4. is 何? ステートマシンを作る gem

  5. 特徴 新しい (2013/10/23〜) aasm 風 DSL 状態遷移の履歴をとる仕組みが ある

  6. 基本的な構成 State Machine Class Model Transition Model

  7. State Machine Class class TaskStateMachine include Statesman::Machine state :unstarted, initial:

    true state :started state :finished event :start do transition from: :unstarted, to: :started end event :finish do transition from: :started, to: :finished end end
  8. Model class Task < ActiveRecord::Base include Statesman::Adapters::ActiveRecordQueries has_many :task_transitions, inverse_of:

    :task delegate :current_state, :trigger!, :available_events, to: :state_machine def state_machine @state_machine ||= TaskStateMachine.new(self, transition_class: TaskTransition) end private def self.transition_class; TaskTransition end def self.initial_state; :unstarted end end
  9. Transition Model # create_table "task_transitions", force: true do |t| #

    t.string "to_state", null: false # t.text "metadata", default: "{}" # t.integer "sort_key", null: false # t.integer "task_id", null: false # t.boolean "most_recent", null: false # t.datetime "created_at" # t.datetime "updated_at" # end # class TaskTransition < ActiveRecord::Base include Statesman::Adapters::ActiveRecordTransition belongs_to :task, inverse_of: :task_transitions end
  10. 使い方 task = Task.first task.current_state # => "unstarted" task.trigger!(:start) #

    => true/exception task.current_state # => "started" task.available_events # => [:finish, :deliver] Task.in_state(:started) # => [#<Task id: "123">] Task.not_in_state(:unstarted) # => [#<Task id: "123">]
  11. 使ってみた際の工夫 初期状態を Transition Model に 記録 current_state を Model に定義

  12. 工夫1 初期状態を Transition Model に記 録

  13. 初期状態の記録 task = Task.create task.current_state # => "unstarted" このときの current_state

    は Transition Model に記録されない
  14. after_createで作る class Task < ActiveRecord::Base has_many :task_transitions, inverse_of: :task after_create

    :create_transition! private def create_transition! task_transitions.create!( to_state: TaskStateMachine.initial_state, sort_key: 0, metadata: {} ) end end
  15. 工夫2 current_state を Model に定義

  16. current_state とは Statesman::Machine#current _state Taskに紐づくTaskTransitionの 最新一件のstate名を取得する

  17. つらみ N+1 in_state, not_in_state が使い にくい (v1.2.0で改善された)

  18. 対策 current_state を Model のカラ ムとして定義した。 多くの場合、今の状態名がわか れば良かった。

  19. コードの改修 # migration add_column :tasks, :current_state, :string # Task Model

    enumerize :current_state, in: TaskStateMachine.states, default: TaskStateMachine.initial_state scope :in_state, ->(*states) { where(current_state: states) } scope :not_in_state, ->(*states) { where.not(current_state: states) } # State Machine Class (TaskStateMachine) after_transition do |task, transition| task.update_column(:current_state, transition.to_state) end
  20. 良くなった点 N+1を考えなくて良い in_state, not_in_stateのSQLが シンプルに enumerizeによりI18n対応

  21. 感想 DSL、履歴が便利 拡張性もある でも細かい所が未成熟 Powered by Rabbit 2.1.6 and COZMIXNG