ex_machina 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +81 -5
- data/lib/ex_machina/adapter/active_record.rb +13 -0
- data/lib/ex_machina/adapter/memory.rb +13 -0
- data/lib/ex_machina/adapter/sequel.rb +13 -0
- data/lib/ex_machina/adapter.rb +18 -0
- data/lib/ex_machina/event/{runner.rb → execution.rb} +15 -24
- data/lib/ex_machina/event/transition.rb +0 -2
- data/lib/ex_machina/event.rb +21 -18
- data/lib/ex_machina/{eventable.rb → machine.rb} +2 -2
- data/lib/ex_machina/util.rb +39 -0
- data/lib/ex_machina/version.rb +1 -1
- data/lib/ex_machina.rb +3 -1
- data/wercker.yml +7 -0
- metadata +10 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc8cb9dc7e92353478947c8c9a88c166b5c02061
|
4
|
+
data.tar.gz: b612897d0b21304de584ea98a3801d6e7a76ea35
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 25b5ef8875fb3de5cf84bc7d1ae856c1345cae080deef13a3d04442be5106154aaf915b3682846876323b5fd229ecbe81002d5653637be037d98a198f10dc057
|
7
|
+
data.tar.gz: e60ebe4a9de753d450a408b00302771c333e044df90d76c6305ced6d9a8b46a9303ede4086456a55afc5a3ba91c0d907d923bcc241df986f487e0ba8b13cc31d
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,15 @@
|
|
1
|
+
[](https://app.wercker.com/project/bykey/32429e43b793f95c450ddd2a89efdb21)
|
2
|
+
|
1
3
|
# ExMachina
|
2
4
|
|
3
|
-
|
5
|
+
A simple implementation of [Finine-state machine](https://en.wikipedia.org/wiki/Finite-state_machine) using OOP following the principles:
|
6
|
+
|
7
|
+
- Machine: a set of status, events and transitions
|
8
|
+
- Status: the state of a machine
|
9
|
+
- Event: a action performed on each status
|
10
|
+
- Transition: the graph of status for a given event
|
4
11
|
|
5
|
-
|
12
|
+
The *machine* is declared on target class that knowns the possible *status* and *events*. Each event should be implemented on its own class, where the *transitions* are declared too. ExMachina manages the flow, execution and transitions of each event and status.
|
6
13
|
|
7
14
|
## Installation
|
8
15
|
|
@@ -22,7 +29,78 @@ Or install it yourself as:
|
|
22
29
|
|
23
30
|
## Usage
|
24
31
|
|
25
|
-
|
32
|
+
Include the `ExMachina::Machine` on target object and define the status and events calling `has_status` and `has_events` respectively:
|
33
|
+
|
34
|
+
Here a classic example of a state machine of an Engine, which have too events: start and stop:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
class Engine
|
38
|
+
include ExMachina::Machine
|
39
|
+
|
40
|
+
has_status [:stopped, :running]
|
41
|
+
has_events Engine::Start, Engine::Stop
|
42
|
+
|
43
|
+
attr_accessor :status, :fuel
|
44
|
+
def initialize(status = "stopped", fuel = 10)
|
45
|
+
@status, @fuel = satus, fuel
|
46
|
+
end
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
And implement each event on its own class. Here was made on subclasses:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
class Engine::Start
|
54
|
+
include ExMachina::Event
|
55
|
+
|
56
|
+
transition from: :stopped, to: :running, if: :has_fuel?, after: :consumes_fuel
|
57
|
+
|
58
|
+
def has_fuel?
|
59
|
+
context.fuel > 0
|
60
|
+
end
|
61
|
+
|
62
|
+
def perform(execution)
|
63
|
+
# do something here
|
64
|
+
|
65
|
+
execution.success!
|
66
|
+
end
|
67
|
+
|
68
|
+
def consumes_fuel
|
69
|
+
context.fuel -= 1
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class Engine::Stop
|
74
|
+
include ExMachina::Event
|
75
|
+
|
76
|
+
transition from: :running, to: :stopped
|
77
|
+
end
|
78
|
+
```
|
79
|
+
|
80
|
+
Here is an usage example:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
engine = Engine.new # => #<Engine:0x007fb77c065758>
|
84
|
+
engine.status # "stopped"
|
85
|
+
engine.fuel # 10
|
86
|
+
|
87
|
+
engine.can_start? # true
|
88
|
+
engine.start # true
|
89
|
+
engine.status # "running"
|
90
|
+
|
91
|
+
engine.running? # true
|
92
|
+
engine.start # false
|
93
|
+
engine.errors # ["No transitions defined from 'running' status"]
|
94
|
+
|
95
|
+
engine.stop # true
|
96
|
+
engine.status # "stopped"
|
97
|
+
engine.stopped? # true
|
98
|
+
engine.fuel # 9
|
99
|
+
|
100
|
+
engine.fuel = 0 # 0
|
101
|
+
engine.can_start? # false
|
102
|
+
engine.start # false
|
103
|
+
```
|
26
104
|
|
27
105
|
## Development
|
28
106
|
|
@@ -34,8 +112,6 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
34
112
|
|
35
113
|
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/ex_machina. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
36
114
|
|
37
|
-
|
38
115
|
## License
|
39
116
|
|
40
117
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
41
|
-
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "ex_machina/adapter/memory"
|
2
|
+
require "ex_machina/adapter/sequel"
|
3
|
+
require "ex_machina/adapter/active_record"
|
4
|
+
|
5
|
+
module ExMachina
|
6
|
+
module Adapter
|
7
|
+
def self.included(base)
|
8
|
+
if defined?(::Sequel)
|
9
|
+
base.include Adapter::Sequel
|
10
|
+
elsif defined?(::ActiveRecord)
|
11
|
+
base.include Adapter::ActiveRecord
|
12
|
+
else
|
13
|
+
base.include Adapter::Memory
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module ExMachina
|
2
2
|
module Event
|
3
|
-
class
|
3
|
+
class Execution
|
4
4
|
|
5
5
|
STATUSES = [
|
6
6
|
SUCCESS = :success,
|
@@ -86,8 +86,11 @@ module ExMachina
|
|
86
86
|
|
87
87
|
def eligible?
|
88
88
|
if transition.conditional?
|
89
|
-
|
90
|
-
invoke(
|
89
|
+
if transition.do_if
|
90
|
+
invoke(transition.do_if)
|
91
|
+
elsif transition.do_unless
|
92
|
+
!invoke(transition.do_unless)
|
93
|
+
end
|
91
94
|
else
|
92
95
|
true
|
93
96
|
end
|
@@ -98,41 +101,29 @@ module ExMachina
|
|
98
101
|
|
99
102
|
return skipped! unless eligible?
|
100
103
|
|
101
|
-
invoke(
|
104
|
+
invoke(transition.do_before)
|
102
105
|
begin
|
103
|
-
call = invoke(
|
104
|
-
invoke(
|
106
|
+
call = invoke(:perform)
|
107
|
+
invoke(transition.do_after)
|
105
108
|
finish!(call)
|
106
109
|
rescue StandardError => ex
|
107
110
|
error!(ex)
|
108
111
|
end
|
109
112
|
|
110
113
|
if success?
|
111
|
-
invoke(
|
112
|
-
invoke(
|
114
|
+
invoke(:transit)
|
115
|
+
invoke(transition.do_success)
|
113
116
|
elsif failure?
|
114
|
-
invoke(
|
117
|
+
invoke(transition.do_failure)
|
115
118
|
end
|
116
119
|
|
117
120
|
call
|
118
121
|
end
|
119
122
|
|
120
|
-
|
121
|
-
return if meth.nil?
|
123
|
+
protected
|
122
124
|
|
123
|
-
|
124
|
-
|
125
|
-
meth_params = meth.parameters
|
126
|
-
meth_args = *all_args.first(meth_params.size)
|
127
|
-
|
128
|
-
meth.call(*meth_args)
|
129
|
-
|
130
|
-
elsif context.respond_to?(meth)
|
131
|
-
meth_params = context.method(meth).parameters
|
132
|
-
meth_args = *args.first(meth_params.size)
|
133
|
-
|
134
|
-
context.send(meth, *meth_args)
|
135
|
-
end
|
125
|
+
def invoke(meth, arg = self)
|
126
|
+
Util.invoke_method(event, meth, arg)
|
136
127
|
end
|
137
128
|
end
|
138
129
|
end
|
@@ -5,8 +5,6 @@ module ExMachina
|
|
5
5
|
attr_reader :from, :to
|
6
6
|
attr_reader :do_if, :do_unless, :do_before, :do_after, :do_success, :do_failure
|
7
7
|
|
8
|
-
attr_accessor :previous, :current, :context, :result
|
9
|
-
|
10
8
|
def initialize(options)
|
11
9
|
@from = Array(options.fetch(:from)).map { |status| normalize(status) }
|
12
10
|
@to = normalize(options.fetch(:to))
|
data/lib/ex_machina/event.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
require "ex_machina/event/transition"
|
2
|
-
require "ex_machina/event/
|
2
|
+
require "ex_machina/event/execution"
|
3
3
|
require "ex_machina/event/validations"
|
4
|
+
require "ex_machina/adapter"
|
4
5
|
|
5
6
|
module ExMachina
|
6
7
|
module Event
|
7
8
|
def self.included(base)
|
8
|
-
base.extend
|
9
|
-
base.include
|
10
|
-
base.include
|
9
|
+
base.extend ClassMethods
|
10
|
+
base.include InstanceMethods
|
11
|
+
base.include Validations
|
12
|
+
base.include Adapter
|
11
13
|
end
|
12
14
|
|
13
15
|
module ClassMethods
|
@@ -27,7 +29,7 @@ module ExMachina
|
|
27
29
|
self.new(context).can_fire?
|
28
30
|
end
|
29
31
|
def event
|
30
|
-
self.name.demodulize.underscore
|
32
|
+
@event ||= Util::String.new(self.name).demodulize.underscore
|
31
33
|
end
|
32
34
|
end
|
33
35
|
|
@@ -38,31 +40,40 @@ module ExMachina
|
|
38
40
|
@context = context
|
39
41
|
end
|
40
42
|
|
43
|
+
# Available transitions
|
41
44
|
def transitions
|
42
45
|
self.class
|
43
46
|
.transitions
|
44
47
|
.select { |transition| transition.from?(status) }
|
45
48
|
end
|
46
49
|
|
50
|
+
# The context current status
|
47
51
|
def status
|
48
52
|
context.status
|
49
53
|
end
|
50
54
|
|
51
|
-
|
55
|
+
# Called on event success
|
56
|
+
def transit(execution)
|
52
57
|
context.status = execution.current.to_s
|
53
|
-
context
|
58
|
+
persist(context)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Override this method on event implementation
|
62
|
+
def perform
|
63
|
+
true
|
54
64
|
end
|
55
65
|
|
56
66
|
def fire
|
57
67
|
validate
|
58
|
-
return unless can_fire?
|
68
|
+
return false unless can_fire?
|
59
69
|
|
60
70
|
result = false
|
61
71
|
within_transaction do
|
62
72
|
transitions.each do |transition|
|
63
|
-
runner =
|
64
|
-
|
73
|
+
runner = Execution.new(self, transition)
|
74
|
+
runner.run
|
65
75
|
|
76
|
+
result = runner.success?
|
66
77
|
break result unless runner.skipped?
|
67
78
|
end
|
68
79
|
end
|
@@ -79,14 +90,6 @@ module ExMachina
|
|
79
90
|
valid?
|
80
91
|
end
|
81
92
|
|
82
|
-
def call
|
83
|
-
raise NotImplementedError, "method 'call' must be implemented on event class"
|
84
|
-
end
|
85
|
-
|
86
|
-
def within_transaction(&block)
|
87
|
-
# TODO implement strategy for transaction
|
88
|
-
DB.transaction(&block)
|
89
|
-
end
|
90
93
|
end
|
91
94
|
end
|
92
95
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module ExMachina
|
2
|
+
module Util
|
3
|
+
class String < ::String
|
4
|
+
# Convert 'MyModule::MyClass' to 'MyClass'
|
5
|
+
def demodulize
|
6
|
+
self.split("::").last
|
7
|
+
end
|
8
|
+
|
9
|
+
# Convert 'MyClass' to 'my_class'
|
10
|
+
def underscore
|
11
|
+
self
|
12
|
+
.gsub("::", "/")
|
13
|
+
.gsub(/(^[A-Z])/) { |match| "#{match.downcase}" }
|
14
|
+
.gsub(/([A-Z])/) { |match| "_#{match.downcase}" }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
extend self
|
19
|
+
|
20
|
+
# Invoke method or lambda on given context passing correspondent args
|
21
|
+
def invoke_method(context, meth, *args)
|
22
|
+
return if meth.nil?
|
23
|
+
|
24
|
+
if meth.respond_to?(:call)
|
25
|
+
all_args = Array([context, *args])
|
26
|
+
meth_params = meth.parameters
|
27
|
+
meth_args = *all_args.first(meth_params.size)
|
28
|
+
|
29
|
+
meth.call(*meth_args)
|
30
|
+
|
31
|
+
elsif context.respond_to?(meth)
|
32
|
+
meth_params = context.method(meth).parameters
|
33
|
+
meth_args = *args.first(meth_params.size)
|
34
|
+
|
35
|
+
context.send(meth, *meth_args)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/ex_machina/version.rb
CHANGED
data/lib/ex_machina.rb
CHANGED
data/wercker.yml
ADDED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ex_machina
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rodrigo Panachi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-03-
|
11
|
+
date: 2016-03-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -69,12 +69,18 @@ files:
|
|
69
69
|
- bin/setup
|
70
70
|
- ex_machina.gemspec
|
71
71
|
- lib/ex_machina.rb
|
72
|
+
- lib/ex_machina/adapter.rb
|
73
|
+
- lib/ex_machina/adapter/active_record.rb
|
74
|
+
- lib/ex_machina/adapter/memory.rb
|
75
|
+
- lib/ex_machina/adapter/sequel.rb
|
72
76
|
- lib/ex_machina/event.rb
|
73
|
-
- lib/ex_machina/event/
|
77
|
+
- lib/ex_machina/event/execution.rb
|
74
78
|
- lib/ex_machina/event/transition.rb
|
75
79
|
- lib/ex_machina/event/validations.rb
|
76
|
-
- lib/ex_machina/
|
80
|
+
- lib/ex_machina/machine.rb
|
81
|
+
- lib/ex_machina/util.rb
|
77
82
|
- lib/ex_machina/version.rb
|
83
|
+
- wercker.yml
|
78
84
|
homepage: https://github.com/rpanachi/ex_machina
|
79
85
|
licenses:
|
80
86
|
- MIT
|