• Stars
    star
    522
  • Rank 84,811 (Top 2 %)
  • Language
    Ruby
  • License
    MIT License
  • Created over 15 years ago
  • Updated over 7 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Minimal Finite State Machine

MicroMachine

Minimal Finite State Machine.

Description

There are many finite state machine implementations for Ruby, and they all provide a nice DSL for declaring events, exceptions, callbacks, and all kinds of niceties in general.

But if all you want is a finite state machine, look no further: this has less than 50 lines of code and provides everything a finite state machine must have, and nothing more.

Usage

require 'micromachine'

machine = MicroMachine.new(:new) # Initial state.

# Define the possible transitions for each event.
machine.when(:confirm, :new => :confirmed)
machine.when(:ignore, :new => :ignored)
machine.when(:reset, :confirmed => :new, :ignored => :new)

machine.trigger(:confirm)  #=> true
machine.state              #=> :confirmed

machine.trigger(:ignore)   #=> false
machine.state              #=> :confirmed

machine.trigger(:reset)    #=> true
machine.state              #=> :new

machine.trigger(:ignore)   #=> true
machine.state              #=> :ignored

The when helper is syntactic sugar for assigning to the transitions_for hash. This code is equivalent:

machine.transitions_for[:confirm] = { :new => :confirmed }
machine.transitions_for[:ignore]  = { :new => :ignored }
machine.transitions_for[:reset]   = { :confirmed => :new, :ignored => :new }

You can also ask if an event will trigger a change in state. Following the example above:

machine.state              #=> :ignored

machine.trigger?(:ignore)  #=> false
machine.trigger?(:reset)   #=> true

# And the state is preserved, because you were only asking.
machine.state              #=> :ignored

If you want to force an Exception when trying to trigger a event from a non compatible state use the trigger! method:

machine.trigger?(:ignore)  #=> false
machine.trigger!(:ignore)  #=> MicroMachine::InvalidState raised

It can also have callbacks when entering some state:

machine.on(:confirmed) do
  puts "Confirmed"
end

Or callbacks on any transition:

machine.on(:any) do
  puts "Transitioned..."
end

Note that :any is a special key. Using it as a state when declaring transitions will give you unexpected results.

You can also pass any data as the second argument for trigger and trigger! which will be passed to every callback as the second argument too:

machine.on(:any) do |_status, payload|
  puts payload.inspect
end

machine.trigger(:cancel, from: :user)

Finally, you can list possible events or states:

# All possible events
machine.events #=> [:confirm, :ignore, :reset]

# All events triggerable from the current state
machine.triggerable_events #=> [:confirm, :ignore]

# All possible states
machine.states #=> [:new, :confirmed, :ignored]

Check the examples directory for more information.

Adding MicroMachine to your models

The most popular pattern among Ruby libraries that tackle this problem is to extend the model and transform it into a finite state machine. Instead of working as a mixin, MicroMachine's implementation is by composition: you instantiate a finite state machine (or many!) inside your model and you are in charge of querying and persisting the state. Here's an example of how to use it with an ActiveRecord model:

class Event < ActiveRecord::Base
  before_save :persist_confirmation

  def confirm!
    confirmation.trigger(:confirm)
  end

  def cancel!
    confirmation.trigger(:cancel)
  end

  def reset!
    confirmation.trigger(:reset)
  end

  def confirmation
    @confirmation ||= begin
      fsm = MicroMachine.new(confirmation_state || "pending")

      fsm.when(:confirm, "pending" => "confirmed")
      fsm.when(:cancel, "confirmed" => "cancelled")
      fsm.when(:reset, "confirmed" => "pending", "cancelled" => "pending")

      fsm
    end
  end

private

  def persist_confirmation
    self.confirmation_state = confirmation.state
  end
end

This example asumes you have a :confirmation_state attribute in your model. This may look like a very verbose implementation, but you gain a lot in flexibility.

An alternative approach, using callbacks:

class Event < ActiveRecord::Base
  def confirm!
    confirmation.trigger(:confirm)
  end

  def cancel!
    confirmation.trigger(:cancel)
  end

  def reset!
    confirmation.trigger(:reset)
  end

  def confirmation
    @confirmation ||= begin
      fsm = MicroMachine.new(confirmation_state || "pending")

      fsm.when(:confirm, "pending" => "confirmed")
      fsm.when(:cancel, "confirmed" => "cancelled")
      fsm.when(:reset, "confirmed" => "pending", "cancelled" => "pending")

      fsm.on(:any) { self.confirmation_state = confirmation.state }

      fsm
    end
  end
end

Now, on any transition the confirmation_state attribute in the model will be updated.

Installation

$ sudo gem install micromachine

License

Copyright (c) 2009 Michel Martens

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

More Repositories

1

cuba

Rum based microframework for web development.
Ruby
1,438
star
2

ohm

Object-Hash Mapping for Redis
Ruby
1,390
star
3

clac

Command-line, stack-based calculator with postfix notation
C
350
star
4

map

Map lines from stdin to commands
C
221
star
5

mote

Minimum Operational Template
Ruby
217
star
6

nest

Generate nested namespaced keys for key-value databases.
Ruby
185
star
7

ost

Redis based queues and workers.
Ruby
166
star
8

toro

Tree oriented routing
Crystal
144
star
9

syro

Simple router for web applications
Ruby
135
star
10

cargo

Require libraries without cluttering your namespace.
Ruby
127
star
11

scrivener

Validation frontend for models.
Ruby
124
star
12

clap

Command line argument parsing
Ruby
90
star
13

ohm-crystal

Ohm for Crystal
Crystal
71
star
14

disque-rb

Disque client for Ruby
Ruby
68
star
15

gs

Gemset management
Ruby
67
star
16

totp

Time-based One-Time Passwords
Ruby
44
star
17

mailcat

Fake SMTP server that prints emails to stdout
C
38
star
18

chen

Change directory entries with your text editor
C
37
star
19

spawn

A ridiculously simple fixtures replacement for your web framework of choice.
Ruby
36
star
20

finist

Redis based Finite State Machine
Ruby
36
star
21

redisurl

Connect to Redis using a REDIS_URL and the redigo client.
Go
33
star
22

hart

Hash router
Ruby
24
star
23

stal-ruby

Set algebra solver for Redis
Ruby
22
star
24

redisent

Sentinels aware Redis client.
Ruby
22
star
25

relay

Relay commands over SSH
Ruby
21
star
26

resp

Lightweight RESP client for Lua
Lua
21
star
27

lomo

Apply a lomo filter to your pictures from the command line using ImageMagick.
Ruby
19
star
28

terco

Obstinate DNS
Ruby
16
star
29

override

The as-simple-as-possible-but-not-simpler stubbing library.
Ruby
16
star
30

ox

Skeleton for a Cuba-based JSON API.
Ruby
16
star
31

nido

Structured keys helper
Ruby
14
star
32

syro-demo

Demo application with Syro
Ruby
14
star
33

resp-crystal

Lightweight RESP client
Crystal
14
star
34

mt

Mail tester daemon.
Ruby
13
star
35

stringent

Generate a string with a target entropy
Ruby
12
star
36

rediscan

Scanner for Redis keyspace
Ruby
12
star
37

hypertext

Hypertext authoring with Ruby
Ruby
11
star
38

filmo

A single page presentation tool.
11
star
39

cuba-book

Cuba Book
11
star
40

sc

List of HTTP status codes.
Ruby
11
star
41

finist.lua

Redis based Finite State Machine
Lua
10
star
42

drawer

Ultra slim file-based cache
Ruby
10
star
43

nest-crystal

Object Oriented Keys for Redis
Crystal
9
star
44

basica

Basic authentication library.
Ruby
8
star
45

rino

Remove a file by its inode number
C
7
star
46

seg.rb

Segment matcher for paths
Ruby
7
star
47

seg

Segment matcher for paths
Crystal
7
star
48

trim

Read from stdin and remove a prefix from each line
C
7
star
49

tas

Trees as strings
Ruby
6
star
50

stal

Set algebra solver for Redis
Lua
6
star
51

pac

Package management for Lua libraries.
Shell
6
star
52

prep

Read from stdin and prepend a string to each line while preserving identation.
C
6
star
53

m4s2

Static Site Generator
HTML
5
star
54

contract

Contract helper
Ruby
5
star
55

stal-crystal

Set algebra solver for Redis
Crystal
5
star
56

loco

Lines of code counter
C
4
star
57

ook

Object Oriented Keys for Redis
Ruby
4
star
58

walk

Walk a directory tree and print the name of every regular file
C
4
star
59

rel

Ruby client for the Bandicoot database.
Ruby
4
star
60

textorama

Text slides for the terminal
Shell
4
star
61

homebrew-tools

Formulae for Homebrew
Ruby
4
star
62

rediscan.lua

Scanner for Redis keyspace in Lua
Lua
3
star
63

seg.go

Segment matcher for paths
Go
3
star
64

look

Add a vendor directory to your load path.
Ruby
3
star
65

app

Cuba application template for gn, the file generator.
Ruby
2
star
66

packer

Require different versions of the same Cargo-compatible gem
Ruby
2
star
67

tele.sh

2
star
68

ers

AWK script easy replacements in templates
Awk
2
star
69

soveran.github.io

2
star
70

filter

Workflow template for gn.
Ruby
1
star
71

remoto

Ruby
1
star
72

gem

Gem template for gn, the file generator.
Ruby
1
star
73

ohm-scripts

Lua scripts for Ohm compatible libraries
Lua
1
star
74

ostgo

Ost client.
Go
1
star
75

miga

Breadcrumb name of the working directory
C
1
star