SolidState

Minuscule but solid state machine for Ruby classes. The only dependency is that your model responds to a getter and setter for state.


  # simplest example, using just an accessor
  class Post
    include SolidState

    attr_accessor :state
    states :draft, :published
  end

  # in its simplest form you just declare the possible states.
  # if it's a simple class you just get boolean methods for checking
  # whether the current status is X or Y.

  p = Post.new
  p.state      # => 'draft'
  p.draft?     # true
  p.published? # => false
  p.state = 'published'
  p.published? # => true

  # now, if the model class responds to validates_inclusion_of, it will
  # mark the record invalid if an unknown state is set.

  # let's assume this is actually an ActiveRecord class, and the
  # table contains a column named 'state'.

  class Post < ActiveRecord::Base
    include SolidState

    states :draft, :published
  end

  p = Post.new
  p.state = 'published'
  p.valid?  # => true
  p.state = 'deleted'
  p.valid?  # => false

  # ok, now let's gets get fancier. we're going to declare transitions
  # which will govern the possible directions in which an object's state
  # can move to.

  class Subscriber < ActiveRecord::Base
    include SolidState

    states :inactive, :active, :unsubscribed, :disabled do
      transitions :from => :inactive, :to => :active
      transitions :from => :active, :to => [:unsubscribed, :disabled]
      transitions :from => :unsubscribed, :to => :active
    end
  end

  s = Subscriber.new
  s.state     # => 'inactive'

  # since we declared transitions, we can now call #{state}! which
  # checks whether the instance can transition to that state and
  # if so, sets the new state and optionally saves the record.

  s.active!   # => true

  s.inactive! # => raises InvalidTransitionError

  # this also works outside transition methods, of course.
  s.reload    # => true
  s.active?   # => true

  s.state = 'inactive'
  s.valid?    # => false

  # the last trick this library does is that it optionally lets you
  # declare callback methods that are called whenever a transition
  # method succeeds. just define a method called #once_[state] in
  # your model.

  class Subscriber
    def once_unsubscribed
      puts "Sorry to see you go!"
    end
  end

  # ...
  s.unsubscribed! # => prints "Sorry to see you go!"

That's about it. For examples check the examples directory in this repo.

Contributions

You're more than welcome. Send a pull request, including tests, and make sure you don't break anything. That's it.

Author

Tomás Pollak

Copyright

(c) Fork Limited. MIT license.