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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e93fa3aad64f0c66c8d1e5a14993b0176c269274
4
- data.tar.gz: 0712a38f75019a9ee82e8a121e8d6d509aa208f4
3
+ metadata.gz: a1388d189d8fe2a3c7f30a3b765526823295ef8a
4
+ data.tar.gz: c672208f53cdd2b07ec8c7e3d03f9c284e915d62
5
5
  SHA512:
6
- metadata.gz: f54ad2ee66dc272f0f0c6594c847b5df6e70deb7f6e851e570f8442a1e8ae9c7301a512389c3fec15f188fe3fb26f73e971029d004f83d9b1489e8ab8899debc
7
- data.tar.gz: c734c80ccf99dd5c077f643c9769369b1937fc7a8141fb48db674cae5d5e149b53fb618c4cd1db088aecf1bf92743a7a45f51e7d02ed805647bc41f93354a094
6
+ metadata.gz: 6110d07106f0590e8651bc0c1b91f5e7e72d1ba30063ddb67a2a151405cc2b3c9aaa08153516b8115fbc7e82e59a20a140cb3a112307295246a08aea43f1238f
7
+ data.tar.gz: af06537cecafe02ce03d1744f202ece3d3039be662f5d7a726277350d64e51ca41b5d907b13c01da43b3d52e2fc71eba91b02420b6eaf7d16544f1d00b08fdfe
data/.gitignore CHANGED
@@ -1,6 +1,7 @@
1
1
  /.bundle/
2
2
  /.yardoc
3
3
  /Gemfile.lock
4
+ gemfiles/*.lock
4
5
  /_yardoc/
5
6
  /coverage/
6
7
  /doc/
data/.travis.yml CHANGED
@@ -1,4 +1,22 @@
1
1
  language: ruby
2
+
3
+ cache:
4
+ - bundler
5
+
2
6
  rvm:
3
- - 2.4.0
4
- before_install: gem install bundler -v 1.11.2
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 [![Build Status](https://travis-ci.org/amatsuda/stateful_enum.svg?branch=master)](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 hooks
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,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'stateful_enum', path: '..'
4
+
5
+ gem 'rails', '~> 4.1.0'
6
+ gem 'sqlite3'
7
+ gem 'minitest', '~> 5.0'
8
+ gem 'ruby-graphviz'
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'stateful_enum', path: '..'
4
+
5
+ gem 'rails', '~> 4.2.0'
6
+ gem 'sqlite3'
7
+ gem 'minitest', '~> 5.0'
8
+ gem 'ruby-graphviz'
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'stateful_enum', path: '..'
4
+
5
+ gem 'rails', '~> 5.0.0.beta3'
6
+ gem 'sqlite3'
7
+ gem 'minitest', '~> 5.0'
8
+ gem 'ruby-graphviz'
@@ -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
- @model, @column, @states, @name, @transitions = model, column, states, name, {}
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
- define_transition_methods
27
- end
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
- @model.send(:define_method, name) do
33
- to, condition = transitions[self.send(column).to_sym]
34
- #TODO better error
35
- if to && (!condition || instance_exec(&condition))
36
- #TODO transaction?
37
- instance_eval(&before) if before
38
- ret = self.class.instance_variable_get(:@_enum_methods_module).instance_method("#{to}!").bind(self).call
39
- instance_eval(&after) if after
40
- ret
41
- else
42
- false
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
- @model.send(:define_method, "#{name}!") do
47
- send(name) || raise('Invalid transition')
48
- end
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
- @model.send(:define_method, "can_#{name}?") do
51
- transitions.has_key? self.send(column).to_sym
52
- end
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
- @model.send(:define_method, "#{name}_transition") do
55
- transitions[self.send(column).to_sym].try! :first
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
@@ -1,3 +1,3 @@
1
1
  module StatefulEnum
2
- VERSION = '0.2.1'
2
+ VERSION = '0.3.0'
3
3
  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.2.1
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-17 00:00:00.000000000 Z
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