strict_machine 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: af5b761971dfb358c71f10ac26f27fe6e8bd5d2d
|
4
|
+
data.tar.gz: c09dd654724097dc1db5c1ca9d4106460a3c8303
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fb18988d67119ceab8121d57c61c12ab65dd7ba1b55a1ad009d24343488b33a1557185c3f4938cc66671a2425ec9cf3157ef11b7c014cb1093683e7bbd949aad
|
7
|
+
data.tar.gz: 28c3aa5e21a28d1247c4fee56e9b3d0864be384dd831251d9f685cb556bca515c69db7fa81826afa8560d6aa67d56d8f64d9f48bcee5958504a223262b6bc59a
|
data/README.md
CHANGED
@@ -20,17 +20,134 @@ Or install it yourself as:
|
|
20
20
|
|
21
21
|
## Usage
|
22
22
|
|
23
|
-
|
23
|
+
You can use this gem in two ways - either by embedding a state machine onto
|
24
|
+
your class, or by mounting a separate class.
|
25
|
+
|
26
|
+
### Embedded
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
class A < StrictMachine::Base
|
30
|
+
strict_machine do
|
31
|
+
state :initial do
|
32
|
+
on hop: :middle
|
33
|
+
end
|
34
|
+
state :middle do
|
35
|
+
on hop: :final
|
36
|
+
end
|
37
|
+
state :final
|
38
|
+
on_transition do |from, to, trigger_event, duration|
|
39
|
+
log from, to, trigger_event, duration
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def log(from, to, trigger_event, duration)
|
44
|
+
# ...
|
45
|
+
end
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
### Mounting
|
50
|
+
|
51
|
+
(using same class A as previous example, minus the `log` method)
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
class B
|
55
|
+
include StrictMachine::MountStateMachine
|
56
|
+
|
57
|
+
mount_state_machine A
|
58
|
+
|
59
|
+
def log(from, to, trigger_event, duration)
|
60
|
+
# ...
|
61
|
+
end
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
**NOTE**: when mounting, the methods referenced should always be on the class doing the mounting, not on the state machine one!
|
66
|
+
|
67
|
+
### Object extensions
|
68
|
+
|
69
|
+
Whether embedding or mounting, an object instance will have the following
|
70
|
+
methods added to it:
|
71
|
+
|
72
|
+
- `#state` returns the current state's name
|
73
|
+
- `#trigger(*transitions)` triggers the given transition(s) by name
|
74
|
+
- `#state_attr` the name of the state attribute being used
|
75
|
+
- `#states` list of `State` objects in the machine's definition
|
76
|
+
|
77
|
+
and an object's class will have:
|
78
|
+
|
79
|
+
- `#definition` the DSL evaluation context object
|
80
|
+
- `#strict_machine_attr` the name of the attribute to store state in
|
81
|
+
- `#strict_machine_class` either the class containing the state machine, when
|
82
|
+
mounting, or `self` when embedding
|
83
|
+
- `#strict_machine` the DSL hook
|
84
|
+
|
85
|
+
### Internals
|
86
|
+
|
87
|
+
The gem works by storing, at the object's class level, the state machine's
|
88
|
+
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
|
89
|
+
`InstanceMethods` and a new state will be reached (and `true` returned), or
|
90
|
+
an exception will be raised. Existing exceptions:
|
91
|
+
|
92
|
+
- `StateNotFoundError` tried to transition to a non-existing state
|
93
|
+
- `TransitionNotFoundError` the transition requested does not exist
|
94
|
+
- `GuardedTransitionError` the guard condition for the transition was not met
|
95
|
+
|
96
|
+
You can bypass the guard condition on a transition by adding a bang `!` to the
|
97
|
+
transition's name, i.e.,
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
obj.trigger('transition!') # bypass guards
|
101
|
+
```
|
102
|
+
|
103
|
+
## The DSL
|
104
|
+
|
105
|
+
Here's a complete example:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
strict_machine do
|
109
|
+
state :new do
|
110
|
+
on submit: :awaiting_review
|
111
|
+
end
|
112
|
+
|
113
|
+
state :awaiting_review do
|
114
|
+
on review: :under_review
|
115
|
+
end
|
116
|
+
|
117
|
+
state :under_review do
|
118
|
+
on_entry { |previous, trigger| some_method(previous, trigger) }
|
119
|
+
on accept: :accepted, if: :cool_article?
|
120
|
+
on reject: :rejected, if: :bad_article?
|
121
|
+
end
|
122
|
+
|
123
|
+
state :accepted
|
124
|
+
state :rejected
|
125
|
+
|
126
|
+
on_transition do |from, to, trigger_event, duration|
|
127
|
+
log from, to, trigger_event, duration
|
128
|
+
end
|
129
|
+
end
|
130
|
+
```
|
131
|
+
|
132
|
+
Guards (the `:if`s on `on` calls) must return `true` in order to pass.
|
133
|
+
|
134
|
+
When mounting, you can specify the state attribute's name with:
|
135
|
+
|
136
|
+
`mount_state_machine SomeClass, state: "meh"`
|
137
|
+
|
138
|
+
And when embedding:
|
139
|
+
|
140
|
+
`strict_machine("meh") do ....`
|
24
141
|
|
25
142
|
## Development
|
26
143
|
|
27
144
|
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.
|
28
145
|
|
29
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `
|
146
|
+
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).
|
30
147
|
|
31
148
|
## Contributing
|
32
149
|
|
33
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
150
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/sardaukar/strict_machine.
|
34
151
|
|
35
152
|
|
36
153
|
## License
|
@@ -31,5 +31,11 @@ module StrictMachine
|
|
31
31
|
def add_on_entry(proc)
|
32
32
|
@on_entry << proc
|
33
33
|
end
|
34
|
+
|
35
|
+
def run_on_entries(instance, current_state_name, trigger)
|
36
|
+
@on_entry.each do |proc|
|
37
|
+
instance.instance_exec(current_state_name, trigger, &proc)
|
38
|
+
end
|
39
|
+
end
|
34
40
|
end
|
35
41
|
end
|
@@ -21,6 +21,14 @@ module StrictMachine
|
|
21
21
|
@states.first.name
|
22
22
|
end
|
23
23
|
|
24
|
+
def run_transitions(instance, current_state, new_state, trigger, duration)
|
25
|
+
@transitions.each do |proc|
|
26
|
+
instance.instance_exec(
|
27
|
+
current_state, new_state, trigger, duration, &proc
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
24
32
|
### DSL
|
25
33
|
|
26
34
|
def state(name, &block)
|
@@ -1,8 +1,10 @@
|
|
1
|
+
require "benchmark"
|
2
|
+
|
1
3
|
module StrictMachine
|
2
4
|
module MountStateMachine
|
3
5
|
module InstanceMethods
|
4
6
|
def trigger(*transitions)
|
5
|
-
transitions.map {|t| change_state(t, state) }
|
7
|
+
transitions.map { |t| change_state(t, state) }
|
6
8
|
|
7
9
|
true
|
8
10
|
end
|
@@ -14,7 +16,7 @@ module StrictMachine
|
|
14
16
|
end
|
15
17
|
|
16
18
|
def state_attr
|
17
|
-
self.class.strict_machine_attr.to_s.
|
19
|
+
self.class.strict_machine_attr.to_s.delete("@")
|
18
20
|
end
|
19
21
|
|
20
22
|
def states
|
@@ -44,31 +46,29 @@ module StrictMachine
|
|
44
46
|
end
|
45
47
|
|
46
48
|
def change_state(trigger, current_state_name)
|
47
|
-
|
48
|
-
current_state = definition.get_state_by_name(current_state_name)
|
49
|
-
is_bang, transition = current_state.get_transition(trigger)
|
49
|
+
new_state = nil
|
50
50
|
|
51
|
-
|
52
|
-
|
53
|
-
transition.guard
|
54
|
-
)
|
55
|
-
end
|
51
|
+
duration = Benchmark.measure do
|
52
|
+
is_bang, transition = current_state_obj.get_transition(trigger)
|
56
53
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
end
|
54
|
+
if transition.guarded? && !is_bang
|
55
|
+
raise GuardedTransitionError unless public_send(transition.guard)
|
56
|
+
end
|
61
57
|
|
62
|
-
|
58
|
+
new_state = definition.get_state_by_name(transition.to)
|
59
|
+
new_state.run_on_entries(self, current_state_name, trigger.to_sym)
|
60
|
+
end.real
|
63
61
|
|
64
|
-
definition.
|
65
|
-
self.
|
66
|
-
|
67
|
-
)
|
68
|
-
end
|
62
|
+
definition.run_transitions(
|
63
|
+
self, current_state_name, new_state.name, trigger.to_sym, duration
|
64
|
+
)
|
69
65
|
|
70
66
|
write_state(new_state.name)
|
71
67
|
end
|
68
|
+
|
69
|
+
def current_state_obj
|
70
|
+
definition.get_state_by_name(current_state_attr_value)
|
71
|
+
end
|
72
72
|
end
|
73
73
|
end
|
74
74
|
end
|
data/lib/strict_machine.rb
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
require_relative "ext/object"
|
2
2
|
|
3
3
|
module StrictMachine
|
4
|
-
VERSION = "0.2.
|
4
|
+
VERSION = "0.2.1".freeze
|
5
5
|
|
6
6
|
class << self; attr_accessor :list; end
|
7
7
|
end
|
8
8
|
|
9
9
|
class StateNotFoundError < StandardError; end
|
10
|
-
class InvalidTransitionError < StandardError; end
|
11
10
|
class TransitionNotFoundError < StandardError; end
|
12
11
|
class GuardedTransitionError < StandardError; end
|
13
12
|
|
data/strict_machine.gemspec
CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
11
11
|
spec.email = ["sardaukar.siet@gmail.com"]
|
12
12
|
|
13
13
|
spec.summary = "State machine functionality for Ruby classes"
|
14
|
-
spec.homepage = "https://
|
14
|
+
spec.homepage = "https://github.com/sardaukar/strict_machine"
|
15
15
|
spec.license = "MIT"
|
16
16
|
|
17
17
|
spec.files = `git ls-files -z`
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: strict_machine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bruno Antunes
|
@@ -101,7 +101,7 @@ files:
|
|
101
101
|
- spec/mountable_machine_spec.rb
|
102
102
|
- spec/spec_helper.rb
|
103
103
|
- strict_machine.gemspec
|
104
|
-
homepage: https://
|
104
|
+
homepage: https://github.com/sardaukar/strict_machine
|
105
105
|
licenses:
|
106
106
|
- MIT
|
107
107
|
metadata: {}
|