【Rails】 state_machineを使って状態を管理する

https://github.com/pluginaweek/state_machine

今回はstate_machineを使ってUserモデルに状態を管理する機能を追加します。

インストールする

# Gemfile

gem 'state_machine'
$ bundle install

使い方

# app/models/user.rb

class User < ActiveRecord::Base
  state_machine :status, initial: :inactive do
    state :active # 有効
    state :inactive # 無効

    # 有効になった時にメールを送信する
    after_transition on: :activate, do: :send_mail

    # 失敗した場合にメールを送信する
    after_failure on: :activate, do: :send_mail

    event :activate do
      transition :inactive => :active
    end

    event :inactivate do
      transition :active => :inactive
    end
  end

  def send_mail
    p "メールを送信しました"
  end
end
> user = User.new(name: "hoge")

> user.active? # => false
> user.inactive? # => true

> user.activate! # => "メールを送信しました"
> user.status # => "active" 

> user.inactivate!
> user.status # => "inactive"

日本語化する

#  config/locales/state_machine_ja.yml

ja:
  activerecord:
    state_machines:
      states:
        nil: ""
      user:
        status:
          states:
            active: 有効
            inactive: 無効
# human_カラム名_name
> user = User.new(name: "hoge")
> user.human_status_name # => "無効"

セレクトボックスのタグの情報にstate_machineの状態を使う

# app/models/user.rb

class User < ActiveRecord::Base
  ...

  def self.status_options
    states = state_machines[:status].states
    states.map(&:human_name).zip(states.map(&:value))
  end
end
# app/views/users/index.html.erb

<%= f.select :status, User.status_options %>

specを書く

# spec/models/user_spec.rb

require 'spec_helper'

describe User do
  let(:user) { FactoryGirl.build(:user) }

  describe "state_machine" do
    describe "status" do
      it "should be an initial state" do
        expect(user.status).to eq("inactive")
      end

      context "status is active" do
        before { user.status = "active" }

        context "to inactive" do
          before { user.inactivate! }
          it { expect(user.status).to eq("inactive") }
        end
      end

      context "status is inactive" do
        before { user.status = "inactive" }

        context "to active" do
          before { user.activate! }
          it { expect(user.status).to eq("active") }
        end
      end
    end
  end
end