stateful_enum 0.2.1 → 0.3.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/.gitignore +1 -0
- data/.travis.yml +20 -2
- data/README.md +13 -4
- data/Rakefile +9 -0
- data/gemfiles/Gemfile-rails.4.1.x +8 -0
- data/gemfiles/Gemfile-rails.4.2.x +8 -0
- data/gemfiles/Gemfile-rails.5.0.0.beta3 +8 -0
- data/lib/generators/stateful_enum/graph_generator.rb +66 -0
- data/lib/stateful_enum/active_record_extension.rb +2 -1
- data/lib/stateful_enum/machine.rb +51 -31
- data/lib/stateful_enum/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: a1388d189d8fe2a3c7f30a3b765526823295ef8a
|
4
|
+
data.tar.gz: c672208f53cdd2b07ec8c7e3d03f9c284e915d62
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6110d07106f0590e8651bc0c1b91f5e7e72d1ba30063ddb67a2a151405cc2b3c9aaa08153516b8115fbc7e82e59a20a140cb3a112307295246a08aea43f1238f
|
7
|
+
data.tar.gz: af06537cecafe02ce03d1744f202ece3d3039be662f5d7a726277350d64e51ca41b5d907b13c01da43b3d52e2fc71eba91b02420b6eaf7d16544f1d00b08fdfe
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,4 +1,22 @@
|
|
1
1
|
language: ruby
|
2
|
+
|
3
|
+
cache:
|
4
|
+
- bundler
|
5
|
+
|
2
6
|
rvm:
|
3
|
-
- 2.4
|
4
|
-
|
7
|
+
- 2.2.4
|
8
|
+
- 2.3.0
|
9
|
+
- ruby-head
|
10
|
+
|
11
|
+
gemfile:
|
12
|
+
- gemfiles/Gemfile-rails.4.1.x
|
13
|
+
- gemfiles/Gemfile-rails.4.2.x
|
14
|
+
- gemfiles/Gemfile-rails.5.0.0.beta3
|
15
|
+
|
16
|
+
before_install:
|
17
|
+
- sudo apt-get install graphviz
|
18
|
+
- gem install bundler -v 1.11.2
|
19
|
+
|
20
|
+
matrix:
|
21
|
+
allow_failures:
|
22
|
+
- rvm: ruby-head
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# StatefulEnum
|
1
|
+
# StatefulEnum [](https://travis-ci.org/amatsuda/stateful_enum)
|
2
2
|
|
3
3
|
stateful_enum is a state machine gem built on top of ActiveRecord's built-in ActiveRecord::Enum.
|
4
4
|
|
@@ -18,7 +18,7 @@ And bundle.
|
|
18
18
|
|
19
19
|
### You Ain't Gonna Need Abstraction
|
20
20
|
|
21
|
-
stateful_enum depends on ActiveRecord. If you prefer a well-abstracted state machine library that supports multiple datastores, or Plain Old Ruby Objects (who needs that feature?), I'm sorry but this gem is not for you.
|
21
|
+
stateful_enum depends on ActiveRecord. If you prefer a "well-abstracted" state machine library that supports multiple datastores, or Plain Old Ruby Objects (who needs that feature?), I'm sorry but this gem is not for you.
|
22
22
|
|
23
23
|
### I Hate Saving States in a VARCHAR Column
|
24
24
|
|
@@ -77,7 +77,7 @@ You can declare events through `event` method inside of an `enum` block. Then st
|
|
77
77
|
**An instance method to fire the event**
|
78
78
|
|
79
79
|
```ruby
|
80
|
-
@bug.assign # does nothing if a valid transition for the current state is not defined
|
80
|
+
@bug.assign # does nothing and returns false if a valid transition for the current state is not defined
|
81
81
|
```
|
82
82
|
|
83
83
|
**An instance method with `!` to fire the event**
|
@@ -117,11 +117,20 @@ event :assign do
|
|
117
117
|
end
|
118
118
|
```
|
119
119
|
|
120
|
-
### Event
|
120
|
+
### Event Hooks
|
121
121
|
|
122
122
|
You can define `before` and `after` event hooks inside of an `event` block.
|
123
123
|
|
124
124
|
|
125
|
+
## Generating State Machine Diagrams
|
126
|
+
|
127
|
+
stateful_enum includes a Rails generator that generates a state machine diagram.
|
128
|
+
Note that you need to bundle the ruby-graphviz gem (and its dependencies) for the development env in order to run the generator.
|
129
|
+
|
130
|
+
```bash
|
131
|
+
% rails g stateful_enum:graph bug
|
132
|
+
```
|
133
|
+
|
125
134
|
## TODO
|
126
135
|
|
127
136
|
* Better Error handling
|
data/Rakefile
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rake/testtask"
|
3
|
+
require 'yaml'
|
3
4
|
|
4
5
|
Rake::TestTask.new(:test) do |t|
|
5
6
|
t.libs << "test"
|
@@ -8,3 +9,11 @@ Rake::TestTask.new(:test) do |t|
|
|
8
9
|
end
|
9
10
|
|
10
11
|
task default: :test
|
12
|
+
|
13
|
+
namespace :test do
|
14
|
+
task :all do
|
15
|
+
YAML.load(File.read(File.expand_path('.travis.yml')))['gemfile'].each do |gemfile|
|
16
|
+
sh "BUNDLE_GEMFILE='#{gemfile}' bundle exec rake test"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'rails/generators/named_base'
|
2
|
+
|
3
|
+
module StatefulEnum
|
4
|
+
module Generators
|
5
|
+
class GraphGenerator < ::Rails::Generators::NamedBase
|
6
|
+
desc 'Draws a state machine diagram'
|
7
|
+
def draw
|
8
|
+
require 'graphviz'
|
9
|
+
StatefulEnum::Machine.prepend StatefulEnum::Graph
|
10
|
+
class_name.constantize
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module Graph
|
16
|
+
def initialize(model, _column, states, prefix, suffix, &block)
|
17
|
+
super
|
18
|
+
GraphDrawer.new model, states, @prefix, @suffix, &block if block
|
19
|
+
end
|
20
|
+
|
21
|
+
class GraphDrawer
|
22
|
+
def initialize(model, states, prefix, suffix, &block)
|
23
|
+
@states, @prefix, @suffix = states, prefix, suffix
|
24
|
+
@g = ::GraphViz.new 'G', rankdir: 'TB'
|
25
|
+
|
26
|
+
states.each do |state|
|
27
|
+
@g.add_node state.to_s, label: state.to_s, width: '1', height: '1', shape: 'ellipse'
|
28
|
+
end
|
29
|
+
@g.add_edge @g.add_node('start state', shape: 'point'), @g.get_node_at_index(0)
|
30
|
+
|
31
|
+
instance_eval(&block)
|
32
|
+
|
33
|
+
(@g.each_edge.map {|e| e.node_two }.uniq - @g.each_edge.map {|e| e.node_one }.uniq).each do |final|
|
34
|
+
@g.get_node(final) {|n| n['shape'] = 'doublecircle' }
|
35
|
+
end
|
36
|
+
|
37
|
+
@g.output png: "#{model.name}.png"
|
38
|
+
end
|
39
|
+
|
40
|
+
def event(name, &block)
|
41
|
+
EventDrawer.new @g, @states, @prefix, @suffix, name, &block
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class EventDrawer < ::StatefulEnum::Machine::Event
|
46
|
+
def initialize(g, states, prefix, suffix, name, &block)
|
47
|
+
@g, @states, @prefix, @suffix, @name = g, states, prefix, suffix, name
|
48
|
+
|
49
|
+
instance_eval(&block) if block
|
50
|
+
end
|
51
|
+
|
52
|
+
def transition(transitions, options = {})
|
53
|
+
if options.blank?
|
54
|
+
transitions.delete :if
|
55
|
+
transitions.delete :unless
|
56
|
+
end
|
57
|
+
|
58
|
+
transitions.each_pair do |from, to|
|
59
|
+
Array(from).each do |f|
|
60
|
+
@g.add_edge f.to_s, to.to_s, label: "#{@prefix}#{@name}#{@suffix}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -8,12 +8,13 @@ module StatefulEnum
|
|
8
8
|
# end
|
9
9
|
# end
|
10
10
|
def enum(definitions, &block)
|
11
|
+
prefix, suffix = definitions[:_prefix], definitions[:_suffix] if Rails::VERSION::STRING >= '5'
|
11
12
|
enum = super definitions
|
12
13
|
|
13
14
|
if block
|
14
15
|
definitions.each_key do |column|
|
15
16
|
states = enum[column]
|
16
|
-
StatefulEnum::Machine.new self, column, (states.is_a?(Hash) ? states.keys : states), &block
|
17
|
+
StatefulEnum::Machine.new self, column, (states.is_a?(Hash) ? states.keys : states), prefix, suffix, &block
|
17
18
|
end
|
18
19
|
end
|
19
20
|
end
|
@@ -1,58 +1,77 @@
|
|
1
1
|
module StatefulEnum
|
2
2
|
class Machine
|
3
|
-
def initialize(model, column, states, &block)
|
3
|
+
def initialize(model, column, states, prefix, suffix, &block)
|
4
4
|
@model, @column, @states, @event_names = model, column, states, []
|
5
|
+
@prefix = if prefix == true
|
6
|
+
"#{column}_"
|
7
|
+
elsif prefix
|
8
|
+
"#{prefix}_"
|
9
|
+
end
|
10
|
+
@suffix = if suffix == true
|
11
|
+
"_#{column}"
|
12
|
+
elsif suffix
|
13
|
+
"_#{suffix}"
|
14
|
+
end
|
5
15
|
|
6
16
|
# undef non-verb methods e.g. Model#active!
|
7
17
|
states.each do |state|
|
8
|
-
@model.send :undef_method, "#{state}!"
|
18
|
+
@model.send :undef_method, "#{@prefix}#{state}#{@suffix}!"
|
9
19
|
end
|
10
20
|
|
11
21
|
instance_eval(&block) if block
|
12
22
|
end
|
13
23
|
|
14
24
|
def event(name, &block)
|
15
|
-
raise "event: :#{name} has already been defined." if @event_names.include? name
|
16
|
-
Event.new @model, @column, @states, name, &block
|
25
|
+
raise ArgumentError, "event: :#{name} has already been defined." if @event_names.include? name
|
26
|
+
Event.new @model, @column, @states, @prefix, @suffix, name, &block
|
17
27
|
@event_names << name
|
18
28
|
end
|
19
29
|
|
20
30
|
class Event
|
21
|
-
def initialize(model, column, states, name, &block)
|
22
|
-
@
|
31
|
+
def initialize(model, column, states, prefix, suffix, name, &block)
|
32
|
+
@states, @name, @transitions, @before, @after = states, name, {}, nil, nil
|
23
33
|
|
24
34
|
instance_eval(&block) if block
|
25
35
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
def define_transition_methods
|
30
|
-
column, name, transitions, before, after = @column, @name, @transitions, @before, @after
|
36
|
+
transitions, before, after = @transitions, @before, @after
|
37
|
+
new_method_name = "#{prefix}#{name}#{suffix}"
|
31
38
|
|
32
|
-
|
33
|
-
|
34
|
-
#
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
39
|
+
# defining event methods
|
40
|
+
model.class_eval do
|
41
|
+
# def assign()
|
42
|
+
detect_enum_conflict! column, new_method_name
|
43
|
+
define_method new_method_name do
|
44
|
+
to, condition = transitions[self.send(column).to_sym]
|
45
|
+
#TODO better error
|
46
|
+
if to && (!condition || instance_exec(&condition))
|
47
|
+
#TODO transaction?
|
48
|
+
instance_eval(&before) if before
|
49
|
+
original_method = self.class.send(:_enum_methods_module).instance_method "#{prefix}#{to}#{suffix}!"
|
50
|
+
ret = original_method.bind(self).call
|
51
|
+
instance_eval(&after) if after
|
52
|
+
ret
|
53
|
+
else
|
54
|
+
false
|
55
|
+
end
|
43
56
|
end
|
44
|
-
end
|
45
57
|
|
46
|
-
|
47
|
-
|
48
|
-
|
58
|
+
# def assign!()
|
59
|
+
detect_enum_conflict! column, "#{new_method_name}!"
|
60
|
+
define_method "#{new_method_name}!" do
|
61
|
+
send(new_method_name) || raise('Invalid transition')
|
62
|
+
end
|
49
63
|
|
50
|
-
|
51
|
-
|
52
|
-
|
64
|
+
# def can_assign?()
|
65
|
+
detect_enum_conflict! column, "can_#{new_method_name}?"
|
66
|
+
define_method "can_#{new_method_name}?" do
|
67
|
+
transitions.has_key? self.send(column).to_sym
|
68
|
+
end
|
53
69
|
|
54
|
-
|
55
|
-
|
70
|
+
# def assign_transition()
|
71
|
+
detect_enum_conflict! column, "#{new_method_name}_transition"
|
72
|
+
define_method "#{new_method_name}_transition" do
|
73
|
+
transitions[self.send(column).to_sym].try! :first
|
74
|
+
end
|
56
75
|
end
|
57
76
|
end
|
58
77
|
|
@@ -68,6 +87,7 @@ module StatefulEnum
|
|
68
87
|
raise "Undefined state #{to}" unless @states.include? to
|
69
88
|
Array(from).each do |f|
|
70
89
|
raise "Undefined state #{f}" unless @states.include? f
|
90
|
+
raise "Duplicate entry: Transition from #{f} to #{@transitions[f].first} has already been defined." if @transitions[f]
|
71
91
|
@transitions[f] = [to, options[:if]]
|
72
92
|
end
|
73
93
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stateful_enum
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Akira Matsuda
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-03-
|
11
|
+
date: 2016-03-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -95,6 +95,10 @@ files:
|
|
95
95
|
- Rakefile
|
96
96
|
- bin/console
|
97
97
|
- bin/setup
|
98
|
+
- gemfiles/Gemfile-rails.4.1.x
|
99
|
+
- gemfiles/Gemfile-rails.4.2.x
|
100
|
+
- gemfiles/Gemfile-rails.5.0.0.beta3
|
101
|
+
- lib/generators/stateful_enum/graph_generator.rb
|
98
102
|
- lib/stateful_enum.rb
|
99
103
|
- lib/stateful_enum/active_record_extension.rb
|
100
104
|
- lib/stateful_enum/machine.rb
|