just_state_machine 0.3.0 → 0.4.0
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/README.md +109 -2
- data/examples/active_record_state_machine.rb +8 -0
- data/examples/simple_state_machine.rb +9 -1
- data/lib/jsm.rb +13 -7
- data/lib/jsm/base.rb +9 -4
- data/lib/jsm/callbacks.rb +38 -0
- data/lib/jsm/callbacks/callback.rb +25 -0
- data/lib/jsm/callbacks/chain.rb +54 -0
- data/lib/jsm/callbacks/chain_collection.rb +15 -0
- data/lib/jsm/client_extension.rb +2 -1
- data/lib/jsm/event_executor/active_record.rb +1 -1
- data/lib/jsm/event_executor/base.rb +20 -6
- data/lib/jsm/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b8088d7c136243526d1a99b0353c9b5068efb6e3
|
4
|
+
data.tar.gz: f88ff999ee19e0c5fa41516999fe1a8e5fcd2f6d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b1200277a06f2a0da8313ff057f5237dfa71f1a3a6d5c4064ca7f9303cb655b44e545b8dc10e704b88b45b93742fdef3f498d206ab4c0fd73beaef57646d45b9
|
7
|
+
data.tar.gz: 1cd99d024ea0f880460e103ed6c8b8f0f0f3c3ad91d3fa4db37cca3672c217679c6327bdafa39677c0526380d6b9bf22a442c189e53de74a4890b884333c5e80
|
data/README.md
CHANGED
@@ -148,6 +148,19 @@ class UserStateMachine < Jsm::Base
|
|
148
148
|
transition from: :intermediate, to: :beginner
|
149
149
|
transition from: :master, to: :intermediate
|
150
150
|
end
|
151
|
+
|
152
|
+
|
153
|
+
before :upgrade_title do |user|
|
154
|
+
user.name = 'before'
|
155
|
+
end
|
156
|
+
|
157
|
+
after :upgrade_title do |result, user|
|
158
|
+
if result
|
159
|
+
user.name += ' after success'
|
160
|
+
else
|
161
|
+
user.name += 'after failed'
|
162
|
+
end
|
163
|
+
end
|
151
164
|
end
|
152
165
|
|
153
166
|
# Client Class
|
@@ -155,7 +168,7 @@ class User
|
|
155
168
|
include Jsm::Client
|
156
169
|
jsm_use UserStateMachine # your state machine class here
|
157
170
|
|
158
|
-
attr_accessor :title # same with attribute_name in UserStateMachine
|
171
|
+
attr_accessor :title, :name # same with attribute_name in UserStateMachine
|
159
172
|
def initialize
|
160
173
|
@title = :beginner
|
161
174
|
@level = 1
|
@@ -177,6 +190,62 @@ transition from: :intermediate, to: :beginner #one `from` states
|
|
177
190
|
transition from: [:intermediate, :master], to: :beginner #multiple `from` states
|
178
191
|
```
|
179
192
|
What above code means for multiple `from` states is the current state either `:intermediate` or `:master`, transform it into `:beginner` state
|
193
|
+
|
194
|
+
### Callbacks
|
195
|
+
`Jsm` now support callbacks. It provide 2 API: `before` and `after`. basically `before` callbacks is run before do event. Meanwhile, after callbacks is run after event. For **note**, after event first argument is `result` of the event
|
196
|
+
```ruby
|
197
|
+
class UserStateMachine < Jsm::Base
|
198
|
+
attribute_name :level
|
199
|
+
|
200
|
+
state :beginner
|
201
|
+
state :intermediate
|
202
|
+
state :master
|
203
|
+
|
204
|
+
event :upgrade_title do
|
205
|
+
transition from: [:beginner], to: :intermediate
|
206
|
+
transition from: [:intermediate], to: :master
|
207
|
+
end
|
208
|
+
|
209
|
+
event :downgrade_title do
|
210
|
+
transition from: :intermediate, to: :beginner
|
211
|
+
transition from: :master, to: :intermediate
|
212
|
+
end
|
213
|
+
|
214
|
+
before :upgrade_title do |user|
|
215
|
+
user.name = 'before'
|
216
|
+
end
|
217
|
+
|
218
|
+
after :upgrade_title do |result, user| # the first parameters is result of the event
|
219
|
+
if result
|
220
|
+
# execute if transition in event is success
|
221
|
+
user.name += ' after success'
|
222
|
+
else
|
223
|
+
# execute if transition in event is failed
|
224
|
+
user.name += 'after failed'
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# Client Class
|
230
|
+
class User
|
231
|
+
include Jsm::Client
|
232
|
+
jsm_use UserStateMachine # your state machine class here
|
233
|
+
|
234
|
+
attr_accessor :title, :name # same with attribute_name in UserStateMachine
|
235
|
+
def initialize
|
236
|
+
@title = :beginner
|
237
|
+
@level = 1
|
238
|
+
end
|
239
|
+
#your code here
|
240
|
+
end
|
241
|
+
|
242
|
+
user = User.new
|
243
|
+
user.title # :beginner
|
244
|
+
user.name # nil
|
245
|
+
user.upgrade_title # true(it still return the original return value of the event)
|
246
|
+
user.name # before after success
|
247
|
+
```
|
248
|
+
|
180
249
|
## Active Model Integration
|
181
250
|
```ruby
|
182
251
|
class UserStateMachine < Jsm::Base
|
@@ -231,7 +300,7 @@ end
|
|
231
300
|
|
232
301
|
### Validation
|
233
302
|
It also support validation from `ActiveModel` . Validation checked based on `errors` value in the `instance`. you can add an error to the errors object. This will prevent the state from being changed
|
234
|
-
```
|
303
|
+
```ruby
|
235
304
|
user = User.new
|
236
305
|
user.level # 1
|
237
306
|
user.level = 18
|
@@ -288,6 +357,44 @@ end
|
|
288
357
|
```
|
289
358
|
`Jsm` support `ActiveRecord`. In the `client` class include the `Jsm::Client::ActiveRecord`. It also support `ActiveRecord` Validation. The behavior is same with `ActiveModel` client.
|
290
359
|
|
360
|
+
## StateMachine Visualization
|
361
|
+
When you want to visualize the state machine that has been defined. You can use the `Jsm::Drawer:Graphviz`. Basically it will generate an url for images of states diagram with theirs events. Basically the image is generated by `google chart`. They provide a really good API to generate state diagram.
|
362
|
+
|
363
|
+
```ruby
|
364
|
+
class UserStateMachine < Jsm::Base
|
365
|
+
attribute_name :relationship
|
366
|
+
|
367
|
+
state :single, initial: true
|
368
|
+
state :in_relationship
|
369
|
+
state :married
|
370
|
+
state :divorced
|
371
|
+
state :widowed
|
372
|
+
|
373
|
+
event :start_dating do
|
374
|
+
transition from: :single, to: :in_relationship
|
375
|
+
end
|
376
|
+
|
377
|
+
event :marry do
|
378
|
+
transition from: [:single, :in_relationship], to: :married
|
379
|
+
end
|
380
|
+
|
381
|
+
event :cheating do
|
382
|
+
transition from: :in_relationship, to: :single
|
383
|
+
transition from: :married, to: :divorced
|
384
|
+
end
|
385
|
+
|
386
|
+
event :divorce do
|
387
|
+
transition from: :married, to: :divorced
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
Jsm::Drawer::Graphviz.generate_url(UserStateMachine) #the arguments is class name
|
392
|
+
```
|
393
|
+
the result will be:
|
394
|
+
|
395
|
+

|
396
|
+
|
397
|
+
[source]( https://chart.googleapis.com/chart?cht=gv&chl=digraph{single-%3Ein_relationship[label=start_dating];single-%3Emarried[label=marry];in_relationship-%3Emarried[label=marry];in_relationship-%3Esingle[label=cheating];married-%3Edivorced[label=cheating];married-%3Edivorced[label=divorce]}Â)
|
291
398
|
## Development
|
292
399
|
|
293
400
|
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.
|
@@ -25,6 +25,14 @@ class ActiveRecordSM < Jsm::Base
|
|
25
25
|
transition from: "married", to: "divorced"
|
26
26
|
end
|
27
27
|
|
28
|
+
before :marry do |user|
|
29
|
+
user.name = 'testBefore'
|
30
|
+
end
|
31
|
+
|
32
|
+
after :marry do |result, user|
|
33
|
+
user.name = ' testAfter'
|
34
|
+
end
|
35
|
+
|
28
36
|
validate "married" do |user|
|
29
37
|
unless user.approved_by_parents
|
30
38
|
user.errors.add(:approved_by_parents, 'can not marry, you havent been approved')
|
@@ -23,6 +23,14 @@ class UserStateMachine < Jsm::Base
|
|
23
23
|
event :divorce do
|
24
24
|
transition from: :married, to: :divorced
|
25
25
|
end
|
26
|
+
|
27
|
+
before :marry do |obj|
|
28
|
+
obj.name = 'testBefore'
|
29
|
+
end
|
30
|
+
|
31
|
+
after :marry do |result, obj|
|
32
|
+
obj.name += ' testAfterthis'
|
33
|
+
end
|
26
34
|
end
|
27
35
|
|
28
36
|
|
@@ -30,7 +38,7 @@ class UserBasic
|
|
30
38
|
include Jsm::Client
|
31
39
|
jsm_use UserStateMachine
|
32
40
|
|
33
|
-
attr_accessor :relationship
|
41
|
+
attr_accessor :relationship, :name
|
34
42
|
def initialize(relationship = nil)
|
35
43
|
@relationship = relationship
|
36
44
|
end
|
data/lib/jsm.rb
CHANGED
@@ -3,21 +3,27 @@ module Jsm
|
|
3
3
|
require "jsm/states"
|
4
4
|
require "jsm/event"
|
5
5
|
require "jsm/exceptions"
|
6
|
-
|
6
|
+
module EventExecutor
|
7
|
+
require "jsm/event_executor/base"
|
8
|
+
require "jsm/event_executor/active_model"
|
9
|
+
require "jsm/event_executor/active_record"
|
10
|
+
end
|
11
|
+
|
12
|
+
require "jsm/callbacks"
|
13
|
+
module Callbacks
|
14
|
+
require 'jsm/callbacks/callback'
|
15
|
+
require "jsm/callbacks/chain"
|
16
|
+
require "jsm/callbacks/chain_collection"
|
17
|
+
end
|
7
18
|
require "jsm/base"
|
8
19
|
require "jsm/client"
|
9
20
|
require "jsm/client/active_model"
|
10
21
|
require "jsm/client/active_record"
|
22
|
+
require "jsm/machines"
|
11
23
|
require "jsm/client_extension"
|
12
24
|
require "jsm/validator"
|
13
25
|
require "jsm/validators"
|
14
26
|
|
15
|
-
module EventExecutor
|
16
|
-
require "jsm/event_executor/base"
|
17
|
-
require "jsm/event_executor/active_model"
|
18
|
-
require "jsm/event_executor/active_record"
|
19
|
-
end
|
20
|
-
|
21
27
|
module Drawer
|
22
28
|
require "jsm/drawer/node"
|
23
29
|
require "jsm/drawer/digraph"
|
data/lib/jsm/base.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# this module used as extension for state machine class
|
2
2
|
# The DSL is built to define the state, event, and transition that happen
|
3
3
|
class Jsm::Base
|
4
|
-
|
4
|
+
include Jsm::Callbacks
|
5
5
|
# define attribute name of state attribute in the client class
|
6
6
|
def self.attribute_name(attribute_name = nil)
|
7
7
|
if attribute_name.nil?
|
@@ -67,13 +67,18 @@ class Jsm::Base
|
|
67
67
|
raise Jsm::InvalidStateError, "there is no state y"
|
68
68
|
end
|
69
69
|
|
70
|
-
|
71
|
-
@validators.add_validator(state_name, Jsm::Validator.new(:state, state_name, &block))
|
70
|
+
validators.add_validator(state_name, Jsm::Validator.new(:state, state_name, &block))
|
72
71
|
end
|
73
72
|
|
74
73
|
# list all validators that exist
|
75
74
|
def self.validators
|
76
|
-
@validators
|
75
|
+
@validators ||= Jsm::Validators.new
|
76
|
+
end
|
77
|
+
# set a before callback for an event
|
78
|
+
def self.pre_before(event_name, &block)
|
79
|
+
if !events[event_name]
|
80
|
+
raise Jsm::InvalidEventError, "event #{event_name} has not been registered"
|
81
|
+
end
|
77
82
|
end
|
78
83
|
|
79
84
|
def initialize(klass)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module ::Jsm::Callbacks
|
2
|
+
def self.included(base)
|
3
|
+
base.extend ClassMethods
|
4
|
+
end
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def jsm_callbacks
|
8
|
+
@jsm_callbacks ||= Jsm::Callbacks::ChainCollection.new(self)
|
9
|
+
end
|
10
|
+
#override this to do something before method `before`
|
11
|
+
def pre_before(context, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
#override this to do something before method `after`
|
15
|
+
def pre_after(context, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
# this method is to register a `before callback` to a jsm_callbacks
|
19
|
+
def before(context, &block)
|
20
|
+
|
21
|
+
pre_before(context, &block)
|
22
|
+
callback = Jsm::Callbacks::Callback.new(:before, &block)
|
23
|
+
jsm_callbacks[context] ||= Jsm::Callbacks::Chain.new(context)
|
24
|
+
jsm_callbacks[context].insert_callback(callback)
|
25
|
+
end
|
26
|
+
|
27
|
+
# this method is to register a `after callback` to a jsm_callbacks
|
28
|
+
def after(context, &block)
|
29
|
+
pre_after(context, &block)
|
30
|
+
callback = Jsm::Callbacks::Callback.new(:after, &block)
|
31
|
+
self.jsm_callbacks[context].insert_callback(callback)
|
32
|
+
end
|
33
|
+
|
34
|
+
def run_callback(context, *args, &block)
|
35
|
+
self.jsm_callbacks[context].compile(*args, &block)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# the purpose of this class is to store the block that will be used as callback
|
2
|
+
# e.g:
|
3
|
+
# Jsm::Callbacks::Callback.new(:before) do
|
4
|
+
# put 'me awesome'
|
5
|
+
# end
|
6
|
+
|
7
|
+
class Jsm::Callbacks::Callback
|
8
|
+
FILTER_TYPES = [:before, :after]
|
9
|
+
attr_reader :filter_type
|
10
|
+
|
11
|
+
# the allowed filter_type: :before, :after
|
12
|
+
def initialize(filter_type, &block)
|
13
|
+
if FILTER_TYPES.include?(filter_type)
|
14
|
+
@filter_type = filter_type
|
15
|
+
else
|
16
|
+
raise ArgumentError, "invalid type #{filter_type}, allowed: #{FILTER_TYPES.join(', ')}"
|
17
|
+
end
|
18
|
+
@block = block
|
19
|
+
end
|
20
|
+
|
21
|
+
# run callback
|
22
|
+
def execute(*obj)
|
23
|
+
@block.call(*obj)
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# chain is a repository to store list of callbacks of an event, for example
|
2
|
+
# store before and after callback of an event called `run`
|
3
|
+
# e.g:
|
4
|
+
# before_callback = Jsm::Callbacks::Callback.new(:before) do |user|
|
5
|
+
# log.info('before callback')
|
6
|
+
# end
|
7
|
+
# before_callback2 = Jsm::Callbacks::Callback.new(:before) do |user|
|
8
|
+
# user.age = 20
|
9
|
+
# end
|
10
|
+
# after_callback = Jsm::Callbacks::Callback.new(:after) do |result, user|
|
11
|
+
# log.info('after callback')
|
12
|
+
# end
|
13
|
+
# chain = Jsm::Callbacks::Chain.new(:run)
|
14
|
+
# chain.insert_callback(before_callback)
|
15
|
+
# chain.insert_callback(before_callback2)
|
16
|
+
# chain.insert_callback(after_callback)
|
17
|
+
# user = User.new
|
18
|
+
# result = chain.compile user do |user|
|
19
|
+
# user.address = 'Indonesia'
|
20
|
+
# end
|
21
|
+
# result -> true
|
22
|
+
# log -> before callback after callback
|
23
|
+
# user.address -> 'Indonesia'
|
24
|
+
|
25
|
+
class Jsm::Callbacks::Chain
|
26
|
+
attr_reader :context, :callbacks
|
27
|
+
FILTER_TYPES_POSITION = [:before, :after]
|
28
|
+
def initialize(context)
|
29
|
+
@context = context
|
30
|
+
@callbacks = []
|
31
|
+
end
|
32
|
+
|
33
|
+
def insert_callback(callback)
|
34
|
+
@callbacks.push(callback)
|
35
|
+
end
|
36
|
+
|
37
|
+
def compile(*args, &block)
|
38
|
+
arrange_callbacks(*args, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
#run callbacks from before, given block then after callback
|
43
|
+
# the return value is the original given block result
|
44
|
+
# before callback get arguments from *args
|
45
|
+
# after callback get result of block execution and *args
|
46
|
+
def arrange_callbacks(*args, &block)
|
47
|
+
before = @callbacks.select { |callback| callback.filter_type == :before }
|
48
|
+
after = @callbacks.select { |callback| callback.filter_type == :after }
|
49
|
+
before.each {|callback| callback.execute(*args) }
|
50
|
+
return_value = block.call(*args)
|
51
|
+
after.each { |callback| callback.execute(return_value, *args) }
|
52
|
+
return_value
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# collection of chain
|
2
|
+
class Jsm::Callbacks::ChainCollection
|
3
|
+
def initialize(klass)
|
4
|
+
@klass = klass
|
5
|
+
@chains = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def add_chain(context,chain)
|
9
|
+
@chains[context] = chain
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](context)
|
13
|
+
@chains[context] ||= Jsm::Callbacks::Chain.new(context)
|
14
|
+
end
|
15
|
+
end
|
data/lib/jsm/client_extension.rb
CHANGED
@@ -8,10 +8,11 @@ class Jsm::ClientExtension
|
|
8
8
|
end
|
9
9
|
|
10
10
|
attr_reader :klass, :state_machine, :event_executor
|
11
|
+
# @param options with options state_machine. It contain state_machine class( class inherited from Jsm::Base)
|
11
12
|
def initialize(klass, params = {})
|
12
13
|
@klass = klass
|
13
14
|
@state_machine = params[:state_machine]
|
14
|
-
@event_executor = klass.jsm_event_executor.new(
|
15
|
+
@event_executor = klass.jsm_event_executor.new(state_machine: @state_machine)
|
15
16
|
unless @state_machine.attribute_name
|
16
17
|
raise Jsm::NoAttributeError, "please assign the attribute_name first in class #{@state_machine.name}"
|
17
18
|
end
|
@@ -1,16 +1,21 @@
|
|
1
1
|
# this class is the base for adapter
|
2
2
|
# it can be extended for ActiveRecord adapter
|
3
3
|
class Jsm::EventExecutor::Base
|
4
|
-
attr_reader :validators
|
4
|
+
attr_reader :validators, :state_machine
|
5
5
|
def initialize(params = {})
|
6
|
-
@
|
6
|
+
@state_machine = params[:state_machine]
|
7
|
+
@validators = @state_machine.validators
|
8
|
+
|
7
9
|
end
|
8
10
|
|
9
11
|
# it execute event for the object.
|
10
12
|
# If transition failed or invalid by validation toward the object,
|
11
13
|
# then it will return false
|
14
|
+
# it also run callbacks of the event
|
12
15
|
def execute(event, obj)
|
13
|
-
|
16
|
+
state_machine.run_callback event.name, obj do |obj|
|
17
|
+
execute_action(event, obj)
|
18
|
+
end
|
14
19
|
end
|
15
20
|
|
16
21
|
# check if the obj possible to execute the event(passed the validation and can do transition)
|
@@ -20,10 +25,19 @@ class Jsm::EventExecutor::Base
|
|
20
25
|
end
|
21
26
|
|
22
27
|
# same with execute, but if its failed raise error
|
28
|
+
# it also run callbacks of the event
|
29
|
+
# however after callbacks only run when success because if its failed will raise error
|
23
30
|
def execute!(event, obj)
|
24
|
-
|
25
|
-
|
31
|
+
state_machine.run_callback event.name, obj do |obj|
|
32
|
+
unless execute_action(event, obj)
|
33
|
+
raise Jsm::IllegalTransitionError, "there is no matching transitions or invalid, Cant do event #{event.name}"
|
34
|
+
end
|
35
|
+
true
|
26
36
|
end
|
27
|
-
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def execute_action(event, obj)
|
41
|
+
can_be_executed?(event, obj) ? event.execute(obj) : false
|
28
42
|
end
|
29
43
|
end
|
data/lib/jsm/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: just_state_machine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- wendy0402
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-10-
|
11
|
+
date: 2015-10-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -144,6 +144,10 @@ files:
|
|
144
144
|
- jsm.gemspec
|
145
145
|
- lib/jsm.rb
|
146
146
|
- lib/jsm/base.rb
|
147
|
+
- lib/jsm/callbacks.rb
|
148
|
+
- lib/jsm/callbacks/callback.rb
|
149
|
+
- lib/jsm/callbacks/chain.rb
|
150
|
+
- lib/jsm/callbacks/chain_collection.rb
|
147
151
|
- lib/jsm/client.rb
|
148
152
|
- lib/jsm/client/active_model.rb
|
149
153
|
- lib/jsm/client/active_record.rb
|