end_state 0.3.2 → 0.4.0

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: c199657701bb9dec215e09e17d5e3e27d1d95d3e
4
- data.tar.gz: 240681ba88cc834317071327063867828e994af0
3
+ metadata.gz: 56937726694a33d9ac097f1f224d5602bb9600c3
4
+ data.tar.gz: 6f3e4fcf53831c33e11e4fdbac2485a1d6fecd3a
5
5
  SHA512:
6
- metadata.gz: 0728b393e7550ad3aa5833854b36c0c84089556c9e798c3ef9c9ac627989d48f4064225c9eedf8770bbdcc818f5991f37646ba3eaa4e784378cb0acebe37dd27
7
- data.tar.gz: 5b4b5f3fd214558f7a1529015f23cea0ca12de44dc5c4c23663013d6cce49582c86c907dd5afda5816251a03430fc6861d78be36daf117e72fa743cca6042dd5
6
+ metadata.gz: 0c2afcefdfaaf9de627121c7e0d13494f4ca632718d79f82c55ee847d935cda7a56ddbf0fefbb4591c88d823d0fee1ba813010ac7631e1fa8b47a48d765ce56c
7
+ data.tar.gz: 3503000ac420f5746ab0a3389966151d5a1d7a10aefb5e4a438180fd7ab6017cbe5362bc551ce56d6def5c5cd1dccf2cc554c74cfcba3b09c684d46cb315b1bd
data/README.md CHANGED
@@ -62,6 +62,17 @@ machine.go! # => :true
62
62
  machine.state # => :b
63
63
  ```
64
64
 
65
+ ## Initial State
66
+
67
+ If you wrap an object that currently has `nil` as the state, the state will be set to `:__nil__`.
68
+ You can change this using the `set_initial_state` method.
69
+
70
+ ```ruby
71
+ class Machine < EndState::StateMachine
72
+ set_initial_state :first
73
+ end
74
+ ```
75
+
65
76
  ## Guards
66
77
 
67
78
  Guards can be created by subclassing `EndState::Guard`. Your class will be provided access to:
@@ -223,7 +234,7 @@ machine.failure_messages # => ['Cannot go!']
223
234
 
224
235
  You may want to use an attribute other than `state` to track the state of the machine.
225
236
 
226
- ```
237
+ ```ruby
227
238
  class Machine < EndState::StateMachine
228
239
  state_attribute :status
229
240
  end
@@ -232,7 +243,7 @@ end
232
243
  Depending on how you persist the `state` (if at all) you may want what is stored in `state` to be a string instead
233
244
  of a symbol. You can tell the machine this preference.
234
245
 
235
- ```
246
+ ```ruby
236
247
  class Machine < EndState::StateMachine
237
248
  store_states_as_strings!
238
249
  end
@@ -240,13 +251,21 @@ end
240
251
 
241
252
  ## Exceptions for failing Transitions
242
253
 
243
- By default `transition` will only raise an exception, `EndState::UnknownState`, if called with a state that doesn't exist.
254
+ By default `transition` will only raise an error, `EndState::UnknownState`, if called with a state that doesn't exist.
244
255
  All other failures, such as missing transition, guard failure, or finalizer failure will silently just return `false` and not
245
256
  transition to the new state.
246
257
 
247
258
  You also have the option to use `transition!` which will instead raise an error for failures. If your guards and/or finalizers
248
259
  add to the `failure_messages` array then they will be included in the error message.
249
260
 
261
+ Additionally, if you would like to treat all transitions as hard and raise an error you can set that in the machine definition.
262
+
263
+ ```ruby
264
+ class Machine < EndState::StateMachine
265
+ treat_all_transitions_as_hard!
266
+ end
267
+ ```
268
+
250
269
  ## Graphing
251
270
 
252
271
  If you install `GraphViz` and the gem `ruby-graphviz` you can create images representing your state machines.
@@ -2,6 +2,7 @@ module EndState
2
2
  class Error < StandardError; end
3
3
  class UnknownState < Error; end
4
4
  class UnknownTransition < Error; end
5
+ class InvalidEvent < Error; end
5
6
  class GuardFailed < Error; end
6
7
  class FinalizerFailed < Error; end
7
8
  end
@@ -2,6 +2,30 @@ module EndState
2
2
  class StateMachine < SimpleDelegator
3
3
  attr_accessor :failure_messages, :success_messages
4
4
 
5
+ def initialize(object)
6
+ super
7
+ Action.new(self, self.class.initial_state).call if self.state.nil?
8
+ end
9
+
10
+ @initial_state = :__nil__
11
+ @mode = :soft
12
+
13
+ def self.initial_state
14
+ @initial_state
15
+ end
16
+
17
+ def self.set_initial_state(state)
18
+ @initial_state = state.to_sym
19
+ end
20
+
21
+ def self.treat_all_transitions_as_hard!
22
+ @mode = :hard
23
+ end
24
+
25
+ def self.mode
26
+ @mode
27
+ end
28
+
5
29
  def self.store_states_as_strings!
6
30
  @store_states_as_strings = true
7
31
  end
@@ -61,7 +85,7 @@ module EndState
61
85
  transition.will_allow? state, params
62
86
  end
63
87
 
64
- def transition(state, params = {}, mode = :soft)
88
+ def transition(state, params = {}, mode = self.class.mode)
65
89
  @failure_messages = []
66
90
  @success_messages = []
67
91
  previous_state = self.state ? self.state.to_sym : self.state
@@ -80,10 +104,10 @@ module EndState
80
104
 
81
105
  def method_missing(method, *args, &block)
82
106
  check_state = method.to_s[0..-2].to_sym
107
+ return super unless is_state_or_event?(check_state)
83
108
  return current_state?(check_state) if method.to_s.end_with?('?')
84
109
  check_state = state_for_event(check_state) || check_state
85
110
  return false if check_state == :__invalid_event__
86
- return super unless self.class.states.include?(check_state)
87
111
  if method.to_s.end_with?('!')
88
112
  transition check_state, args[0]
89
113
  else
@@ -93,6 +117,10 @@ module EndState
93
117
 
94
118
  private
95
119
 
120
+ def is_state_or_event?(check_state)
121
+ self.class.states.include?(check_state) or self.class.events[check_state]
122
+ end
123
+
96
124
  def current_state?(check_state)
97
125
  state.to_sym == check_state
98
126
  end
@@ -105,6 +133,7 @@ module EndState
105
133
  end
106
134
 
107
135
  def invalid_event(event)
136
+ fail InvalidEvent, "Transition by event: #{event} is invalid." if self.class.mode == :hard
108
137
  message = self.class.transitions[self.class.events[event].first].blocked_event_message
109
138
  @failure_messages = [message] if message
110
139
  :__invalid_event__
@@ -1,3 +1,3 @@
1
1
  module EndState
2
- VERSION = '0.3.2'
2
+ VERSION = '0.4.0'
3
3
  end
@@ -9,6 +9,8 @@ module EndState
9
9
  StateMachine.instance_variable_set '@transitions'.to_sym, nil
10
10
  StateMachine.instance_variable_set '@events'.to_sym, nil
11
11
  StateMachine.instance_variable_set '@store_states_as_strings'.to_sym, nil
12
+ StateMachine.instance_variable_set '@initial_state'.to_sym, :__nil__
13
+ StateMachine.instance_variable_set '@mode'.to_sym, :soft
12
14
  end
13
15
 
14
16
  describe '.transition' do
@@ -97,7 +99,21 @@ module EndState
97
99
  end
98
100
  end
99
101
 
102
+ describe '.initial_state' do
103
+ context 'when set to :first' do
104
+ before { StateMachine.set_initial_state :first }
105
+
106
+ it 'has that initial state' do
107
+ expect(machine.state).to eq :first
108
+ end
109
+ end
110
+ end
111
+
100
112
  describe '#state' do
113
+ context 'when there is no state set' do
114
+ specify { expect(machine.state).to eq :__nil__ }
115
+ end
116
+
101
117
  context 'when the object has state :a' do
102
118
  let(:object) { OpenStruct.new(state: :a) }
103
119
 
@@ -140,6 +156,18 @@ module EndState
140
156
  specify { expect(machine.stop?).to be_true }
141
157
  end
142
158
  end
159
+
160
+ context 'when the requested predicate is not a state or event' do
161
+ module Predicate
162
+ def foo? ; :foo ; end
163
+ end
164
+
165
+ before { object.extend(Predicate) }
166
+
167
+ it 'call super up to SimpleDelegator, which handles the method' do
168
+ expect(machine.foo?).to eq(:foo)
169
+ end
170
+ end
143
171
  end
144
172
 
145
173
  describe '#{state}!' do
@@ -178,6 +206,14 @@ module EndState
178
206
  machine.go!
179
207
  expect(machine.failure_messages).to eq ['Invalid event!']
180
208
  end
209
+
210
+ context 'and all transitions are forced to run in :hard mode' do
211
+ before { machine.class.treat_all_transitions_as_hard! }
212
+
213
+ it 'raises an InvalidEvent error' do
214
+ expect { machine.go! }.to raise_error(InvalidEvent)
215
+ end
216
+ end
181
217
  end
182
218
  end
183
219
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: end_state
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - alexpeachey
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-03 00:00:00.000000000 Z
11
+ date: 2014-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler