$30 off During Our Annual Pro Sale. View Details »

STATESMAN

 STATESMAN

kbaba1001

May 15, 2015
Tweet

More Decks by kbaba1001

Other Decks in Technology

Transcript

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

    View Slide

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

    View Slide

  3. 今日の話

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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.not_in_state(:unstarted) # => [#]

    View Slide

  11. 使ってみた際の工夫
    初期状態を Transition Model に
    記録
    current_state を Model に定義

    View Slide

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

    View Slide

  13. 初期状態の記録
    task = Task.create
    task.current_state # => "unstarted"
    このときの current_state は
    Transition Model に記録されない

    View Slide

  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

    View Slide

  15. 工夫2
    current_state を Model に定義

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  20. 良くなった点
    N+1を考えなくて良い
    in_state, not_in_stateのSQLが
    シンプルに
    enumerizeによりI18n対応

    View Slide

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

    View Slide