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: cd8176055fcd833b0c5e292f89d251ef32067687
4
- data.tar.gz: 8c5526e2c67353ce0bb71d52713584219a901d1c
3
+ metadata.gz: af5b761971dfb358c71f10ac26f27fe6e8bd5d2d
4
+ data.tar.gz: c09dd654724097dc1db5c1ca9d4106460a3c8303
5
5
  SHA512:
6
- metadata.gz: e364a995db6ffbde2984893fdc5f2e0b7d2dccf67b85514904aba17a5e66b86db90d958eb1e8e475e85b63589f70ecdcd7cb7cba6f800fb930073c0e7b043257
7
- data.tar.gz: 0c2f0375d4df894541648ca6ddd7e2ebc7a14ca90edf6ed867c91a63b8effe15d914aece8e9496d6f9c964b10541bd9fb3853e145d717655272516eabfcb29ff
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
- Still working on this one...
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 `version.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).
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/[USERNAME]/strict_machine.
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.gsub("@",'')
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
- dt = Time.now
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
- if transition.guarded? && !is_bang
52
- raise GuardedTransitionError unless self.public_send(
53
- transition.guard
54
- )
55
- end
51
+ duration = Benchmark.measure do
52
+ is_bang, transition = current_state_obj.get_transition(trigger)
56
53
 
57
- new_state = definition.get_state_by_name(transition.to)
58
- new_state.on_entry.each do |proc|
59
- self.instance_exec(current_state_name, trigger.to_sym, &proc)
60
- end
54
+ if transition.guarded? && !is_bang
55
+ raise GuardedTransitionError unless public_send(transition.guard)
56
+ end
61
57
 
62
- duration = Time.now - dt
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.transitions.each do |proc|
65
- self.instance_exec(
66
- current_state_name, new_state.name, trigger.to_sym, duration, &proc
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
@@ -1,13 +1,12 @@
1
1
  require_relative "ext/object"
2
2
 
3
3
  module StrictMachine
4
- VERSION = "0.2.0".freeze
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
 
@@ -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://not-yet.com"
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.0
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://not-yet.com
104
+ homepage: https://github.com/sardaukar/strict_machine
105
105
  licenses:
106
106
  - MIT
107
107
  metadata: {}