stateful_enum 0.2.1 → 0.3.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: 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