sardaukar/strict_machine

View on GitHub
README.md

Summary

Maintainability
Test Coverage
# StrictMachine

[![Code Climate](https://codeclimate.com/github/sardaukar/strict_machine/badges/gpa.svg)](https://codeclimate.com/github/sardaukar/strict_machine)

Easily add state-machine functionality to your Ruby classes.

## Installation

Add this line to your application's Gemfile:

```ruby
gem 'strict_machine'
```

And then execute:

    $ bundle

Or install it yourself as:

    $ gem install strict_machine

## Usage

You can use this gem in two ways - either by embedding a state machine onto
your class, or by mounting a separate class.

### Embedded

```ruby
class A < StrictMachine::Base
  strict_machine do
    state :initial do
      on hop: :middle
    end
    state :middle do
      on hop: :final
    end
    state :final
    on_transition do |from, to, trigger_event, duration|
      log from, to, trigger_event, duration
    end
  end

  def log(from, to, trigger_event, duration)
   # ...
  end
end
```

### Mounting

(using same class A as previous example, minus the `log` method)

```ruby
class B
  include StrictMachine::MountStateMachine

  mount_state_machine A

  def log(from, to, trigger_event, duration)
   # ...
  end
end
```

**NOTE**: when mounting, the methods referenced should always be on the class doing the mounting, not on the state machine one!

### Object extensions

Whether embedding or mounting, an object instance will have the following
methods added to it:

- `#state` returns the current state's name
- `#trigger(*transitions)` triggers the given transition(s) by name
- `#state_attr` the name of the state attribute being used
- `#states` list of `State` objects in the machine's definition

and an object's class will have:

- `#strict_machine_attr` the name of the attribute to store state in
- `#strict_machine_class` either the class containing the state machine, when
mounting, or `self` when embedding

Also, the target class (`self` when embedding, or the state machine class when mounting) will have:

- `#strict_machine` the DSL hook
- `#definition` the DSL evaluation context object

### Internals

The gem works by storing, at the object's class level, the state machine's
definition and the name of the state storage attribute. Every instance of the class embedding or mounting a state machine will have its own state (stored in said attribute (by default `state`), and upon transitions, the current state and the transition requested will be passed to `#change_state` in
`InstanceMethods` and a new state will be reached (and `true` returned), or
an exception will be raised. Existing exceptions:

- `StateNotFoundError` tried to transition to a non-existing state
- `TransitionNotFoundError` the transition requested does not exist
- `GuardedTransitionError` the guard condition for the transition was not met

You can bypass the guard condition on a transition by adding a bang `!` to the
transition's name, i.e.,

```ruby
  obj.trigger('transition!') # bypass guards
```

## The DSL

Here's a complete example:

```ruby
strict_machine do
  state :new do
    on submit: :awaiting_review
  end

  state :awaiting_review do
    on review: :under_review
  end

  state :under_review do
    on_entry { |previous, trigger| some_method(previous, trigger) }
    on accept: :accepted, if: :cool_article?
    on reject: :rejected, if: :bad_article?
  end

  state :accepted
  state :rejected

  on_transition do |from, to, trigger_event, duration|
    log from, to, trigger_event, duration
  end
end
```

Guards (the `:if`s on `on` calls) must return `true` in order to pass.

When mounting, you can specify the state attribute's name with:

`mount_state_machine SomeClass, state: "meh"`

And when embedding:

`strict_machine("meh") do ....`

## Development

After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `strict_machine.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).

## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/sardaukar/strict_machine.


## License

The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).