simple_state_machine 0.6.0 → 0.6.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4cf708bed33ca00037b1fbb2562b2a4c351666d82a4555b84078600b9e90ad03
4
+ data.tar.gz: 251e24c03f5c59313aec334045670ed024c2720630c188d17bfed1eced1827be
5
+ SHA512:
6
+ metadata.gz: 482b1ba0a9e91260dfe56153453b636640dcd7c37916fd692e13d8607f59ee141d7161c9db6e9a50cdbb14b21b2895227129edf1c4f35dbcb733caceafeb8beb
7
+ data.tar.gz: 5c3b05eb49e792a4289e85c5be1f10f9239020a0147e0ab5b617bba58d7fac4b727a47f45564c472e25e4079e8d1fb7d52838f9a8af9dd610a90e2580d6a9132
@@ -0,0 +1,46 @@
1
+ name: Build
2
+ on:
3
+ - push
4
+ - pull_request
5
+
6
+ jobs:
7
+ build:
8
+ name: Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }}
9
+ if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
10
+ strategy:
11
+ fail-fast: false
12
+ matrix:
13
+ ruby:
14
+ - "3.0"
15
+ - "2.7"
16
+ - "2.6"
17
+ - "2.5"
18
+ rails:
19
+ - "5.2"
20
+ - "6.0"
21
+ - "6.1"
22
+ - main
23
+ exclude:
24
+ - ruby: 2.5
25
+ rails: main
26
+ - ruby: 2.6
27
+ rails: main
28
+ - ruby: 3.0
29
+ rails: "5.1"
30
+ - ruby: 3.0
31
+ rails: "5.2"
32
+
33
+ runs-on: 'ubuntu-latest'
34
+
35
+ env:
36
+ BUNDLE_GEMFILE: gemfiles/Gemfile.activerecord-${{ matrix.rails }}.x
37
+
38
+ steps:
39
+ - uses: actions/checkout@v2
40
+ - uses: ruby/setup-ruby@v1
41
+ with:
42
+ ruby-version: ${{ matrix.ruby }}
43
+ - name: Setup project
44
+ run: bundle install
45
+ - name: Run test
46
+ run: bundle exec rspec spec
data/.gitignore CHANGED
@@ -1 +1,4 @@
1
1
  *.swp
2
+ .bundle
3
+ .ruby-version
4
+ Gemfile.lock
data/.travis.yml ADDED
@@ -0,0 +1,17 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.5
4
+ - 2.7
5
+ - 3.0
6
+ - jruby-9.2.16.0
7
+ script: bundle exec rspec spec
8
+
9
+ gemfile:
10
+ - gemfiles/Gemfile.activerecord-6.0.x
11
+ - gemfiles/Gemfile.activerecord-6.1.x
12
+ - gemfiles/Gemfile.basic
13
+
14
+ matrix:
15
+ exclude:
16
+ - rvm: jruby
17
+ gemfile: gemfiles/Gemfile.activerecord-4.2.x
data/Gemfile CHANGED
@@ -2,3 +2,10 @@
2
2
  source "http://rubygems.org"
3
3
 
4
4
  gemspec
5
+
6
+ group :test do
7
+ gem "rspec", "~> 3.0"
8
+ gem "activerecord", ">= 5.2.0"
9
+ gem "sqlite3", :platform => [:ruby, :mswin, :mingw]
10
+ gem "activerecord-jdbcsqlite3-adapter", :platform => :jruby
11
+ end
data/README.rdoc CHANGED
@@ -1,5 +1,7 @@
1
1
  = SimpleStateMachine
2
2
 
3
+ {<img src="https://github.com/mdh/ssm/actions/workflows/build.yml/badge.svg" />}[https://github.com/mdh/ssm/actions?query=workflow%3A.github%2Fworkflows%2Fbuild.yml+branch%3Amaster++]
4
+
3
5
  A simple DSL to decorate existing methods with state transition guards.
4
6
 
5
7
  Instead of using a DSL to define events, SimpleStateMachine decorates methods
@@ -7,26 +9,14 @@ to help you encapsulate state and guard state transitions.
7
9
 
8
10
  It supports exception rescuing, google chart visualization and mountable state machines.
9
11
 
10
-
11
- == Installation
12
-
13
- Use gem install:
14
-
15
- gem install simple_state_machine
16
-
17
- Or add it to your Gemfile:
18
-
19
- gem 'simple_state_machine'
20
-
21
-
22
12
  == Usage
23
13
 
24
14
  Define an event and specify how the state should transition. If we want the state to change
25
- from :pending to :active we write:
15
+ from *pending* to *active* we write:
26
16
 
27
17
  event :activate_account, :pending => :active
28
18
 
29
- That's it. You can now call activate_account and the state will automatically change.
19
+ That's it. You can now call *activate_account* and the state will automatically change.
30
20
  If the state change is not allowed, a SimpleStateMachine::IllegalStateTransitionError is
31
21
  raised.
32
22
 
@@ -74,20 +64,23 @@ when the method is called.
74
64
 
75
65
  == ActiveRecord
76
66
 
67
+ For ActiveRecord methods are decorated with state transition guards _and_ persistence.
68
+ Methods marked as events behave like ActiveRecord *save* and *save!*.
69
+
77
70
  === Example
78
71
 
79
72
  To add a state machine to an ActiveRecord class, you will have to:
80
- - extend SimpleStateMachine::ActiveRecord,
81
- - set the initial state in after_initialize,
82
- - turn methods into events
73
+ * extend SimpleStateMachine::ActiveRecord,
74
+ * set the initial state in after_initialize,
75
+ * turn methods into events
83
76
 
84
77
  class User < ActiveRecord::Base
85
78
 
86
79
  extend SimpleStateMachine::ActiveRecord
87
80
 
88
- def after_initialize
81
+ after_initialize do
89
82
  self.state ||= 'pending'
90
- end
83
+ end
91
84
 
92
85
  def invite
93
86
  self.activation_code = Digest::SHA1.hexdigest("salt #{Time.now.to_f}")
@@ -102,12 +95,12 @@ To add a state machine to an ActiveRecord class, you will have to:
102
95
  user.activation_code # => 'SOMEDIGEST'
103
96
 
104
97
  For the invite method this generates the following event methods
105
- - invite (behaves like ActiveRecord save )
106
- - invite! (behaves like ActiveRecord save!)
98
+ * *invite* (behaves like ActiveRecord save )
99
+ * *invite!* (behaves like ActiveRecord save!)
107
100
 
108
101
  If you want to be more verbose you can also use:
109
- - invite_and_save (alias for invite)
110
- - invite_and_save! (alias for invite!)
102
+ * *invite_and_save* (alias for invite)
103
+ * *invite_and_save!* (alias for invite!)
111
104
 
112
105
 
113
106
  === Using ActiveRecord / ActiveModel validations
@@ -153,7 +146,7 @@ If you like to separate your state machine from your model class, you can do so
153
146
  extend SimpleStateMachine::Mountable
154
147
  mount_state_machine MyStateMachine
155
148
 
156
- def after_initialize
149
+ after_initialize do
157
150
  self.state ||= 'new'
158
151
  end
159
152
 
@@ -163,7 +156,7 @@ If you like to separate your state machine from your model class, you can do so
163
156
  == Transitions
164
157
 
165
158
  === Catching all from states
166
- If an event should transition from all other defined states, you can use :all as from state:
159
+ If an event should transition from all other defined states, you can use the *:all* state:
167
160
 
168
161
  event :suspend, :all => :suspended
169
162
 
@@ -173,18 +166,18 @@ If an event should transition from all other defined states, you can use :all as
173
166
  You can let the state machine handle exceptions by specifying the failure state for an Error:
174
167
 
175
168
  def download_data
176
- raise Service::ConnectionError
169
+ raise Service::ConnectionError, "Uhoh"
177
170
  end
178
171
  event :download_data, Service::ConnectionError => :download_failed
179
172
 
180
173
  download_data # catches Service::ConnectionError
181
174
  state # => "download_failed"
182
- state_machine.raised_error # the raised error
175
+ state_machine.raised_error # "Uhoh"
183
176
 
184
177
 
185
178
  === Default error state
186
179
 
187
- To automatically change all states to a default error state use default_error_state:
180
+ To automatically catch all exceptions to a default error state use default_error_state:
188
181
 
189
182
  state_machine_definition.default_error_state = :failed
190
183
 
@@ -192,7 +185,7 @@ To automatically change all states to a default error state use default_error_st
192
185
 
193
186
  If you want to run events in transactions run them in a transaction block:
194
187
 
195
- user.transaction { user.invite }
188
+ user.transaction { user.invite! }
196
189
 
197
190
  == Tools
198
191
 
@@ -207,6 +200,15 @@ For details run:
207
200
  A Googlechart example:
208
201
  http://tinyurl.com/79xztr6
209
202
 
203
+ == Installation
204
+
205
+ Use gem install:
206
+
207
+ gem install simple_state_machine
208
+
209
+ Or add it to your Gemfile:
210
+
211
+ gem 'simple_state_machine'
210
212
 
211
213
  == Note on Patches/Pull Requests
212
214
 
@@ -2,19 +2,19 @@
2
2
  #
3
3
  # class Conversation
4
4
  # include AASM
5
- #
5
+ #
6
6
  # aasm_column :current_state # defaults to aasm_state
7
- #
7
+ #
8
8
  # aasm_initial_state :unread
9
- #
9
+ #
10
10
  # aasm_state :unread
11
11
  # aasm_state :read
12
12
  # aasm_state :closed
13
- #
13
+ #
14
14
  # aasm_event :view do
15
15
  # transitions :to => :read, :from => [:unread]
16
16
  # end
17
- #
17
+ #
18
18
  # aasm_event :close do
19
19
  # transitions :to => :closed, :from => [:read, :unread]
20
20
  # end
data/examples/lamp.rb CHANGED
@@ -1,21 +1,21 @@
1
1
  class Lamp
2
-
2
+
3
3
  extend SimpleStateMachine
4
4
 
5
5
  def initialize
6
6
  self.state = 'off'
7
7
  end
8
-
8
+
9
9
  def push_button1
10
10
  puts "click1: #{state}"
11
11
  end
12
- event :push_button1, :off => :on,
12
+ event :push_button1, :off => :on,
13
13
  :on => :off
14
-
14
+
15
15
  def push_button2
16
16
  puts "click2: #{state}"
17
17
  end
18
18
  event :push_button2, :off => :on,
19
19
  :on => :off
20
20
 
21
- end
21
+ end
@@ -2,23 +2,23 @@
2
2
  #
3
3
  # class Relationship
4
4
  # include AASM
5
- #
5
+ #
6
6
  # aasm_column :status
7
- #
7
+ #
8
8
  # aasm_initial_state Proc.new { |relationship| relationship.strictly_for_fun? ? :intimate : :dating }
9
- #
9
+ #
10
10
  # aasm_state :dating, :enter => :make_happy, :exit => :make_depressed
11
11
  # aasm_state :intimate, :enter => :make_very_happy, :exit => :never_speak_again
12
12
  # aasm_state :married, :enter => :give_up_intimacy, :exit => :buy_exotic_car_and_wear_a_combover
13
- #
13
+ #
14
14
  # aasm_event :get_intimate do
15
15
  # transitions :to => :intimate, :from => [:dating], :guard => :drunk?
16
16
  # end
17
- #
17
+ #
18
18
  # aasm_event :get_married do
19
19
  # transitions :to => :married, :from => [:dating, :intimate], :guard => :willing_to_give_up_manhood?
20
20
  # end
21
- #
21
+ #
22
22
  # def strictly_for_fun?; end
23
23
  # def drunk?; end
24
24
  # def willing_to_give_up_manhood?; end
@@ -32,7 +32,7 @@
32
32
 
33
33
  class Relationship
34
34
  extend SimpleStateMachine
35
-
35
+
36
36
  def initialize
37
37
  self.state = relationship.strictly_for_fun? ? get_intimate : start_dating
38
38
  end
@@ -84,4 +84,4 @@ class Relationship
84
84
  def never_speak_again; end
85
85
  def give_up_intimacy; end
86
86
  def buy_exotic_car_and_wear_a_combover; end
87
- end
87
+ end
@@ -1,7 +1,7 @@
1
1
  class TrafficLight
2
-
2
+
3
3
  extend SimpleStateMachine
4
-
4
+
5
5
  def initialize
6
6
  self.state = 'green'
7
7
  end
@@ -14,4 +14,4 @@ class TrafficLight
14
14
  event :change_state, :green => :orange,
15
15
  :orange => :red,
16
16
  :red => :green
17
- end
17
+ end
data/examples/user.rb CHANGED
@@ -5,6 +5,7 @@ class User < ActiveRecord::Base
5
5
 
6
6
  extend SimpleStateMachine::ActiveRecord
7
7
 
8
+ after_initialize :after_initialize
8
9
  def after_initialize
9
10
  self.state ||= 'new'
10
11
  # if you get an ActiveRecord::MissingAttributeError
@@ -0,0 +1,9 @@
1
+ # A sample Gemfile
2
+ source "http://rubygems.org"
3
+
4
+ group :test do
5
+ gem "rspec"
6
+ gem "activerecord", "~>5.2.0"
7
+ gem "sqlite3", :platform => [:ruby, :mswin, :mingw]
8
+ gem "activerecord-jdbcsqlite3-adapter", :platform => :jruby
9
+ end
@@ -0,0 +1,9 @@
1
+ # A sample Gemfile
2
+ source "http://rubygems.org"
3
+
4
+ group :test do
5
+ gem "rspec"
6
+ gem "activerecord", "~>6.0.0"
7
+ gem "sqlite3", :platform => [:ruby, :mswin, :mingw]
8
+ gem "activerecord-jdbcsqlite3-adapter", :platform => :jruby
9
+ end
@@ -0,0 +1,8 @@
1
+ # A sample Gemfile
2
+ source "http://rubygems.org"
3
+
4
+ group :test do
5
+ gem "rspec"
6
+ gem "activerecord", "~>6.1.0"
7
+ gem "sqlite3", :platform => [:ruby, :mswin, :mingw]
8
+ end
@@ -0,0 +1,9 @@
1
+ # A sample Gemfile
2
+ source "http://rubygems.org"
3
+
4
+ group :test do
5
+ gem "rspec"
6
+ gem "activerecord", git: "https://github.com/rails/rails.git", branch: "main"
7
+ gem "sqlite3", :platform => [:ruby, :mswin, :mingw]
8
+ gem "activerecord-jdbcsqlite3-adapter", :platform => :jruby
9
+ end
@@ -0,0 +1,6 @@
1
+ # A sample Gemfile
2
+ source "http://rubygems.org"
3
+
4
+ group :test do
5
+ gem "rspec"
6
+ end
@@ -20,19 +20,14 @@ module SimpleStateMachine
20
20
  event_name_and_save = "#{event_name}_and_save"
21
21
  unless @subject.method_defined?(event_name_and_save)
22
22
  @subject.send(:define_method, event_name_and_save) do |*args|
23
- result = false
24
23
  old_state = self.send(self.class.state_machine_definition.state_method)
25
24
  send "#{event_name}_with_managed_state", *args
26
- if !self.errors.entries.empty?
27
- self.send("#{self.class.state_machine_definition.state_method}=", old_state)
25
+ if self.errors.entries.empty? && save
26
+ true
28
27
  else
29
- if save
30
- result = true
31
- else
32
- self.send("#{self.class.state_machine_definition.state_method}=", old_state)
33
- end
28
+ self.send("#{self.class.state_machine_definition.state_method}=", old_state)
29
+ false
34
30
  end
35
- return result
36
31
  end
37
32
  @subject.send :alias_method, "#{event_name}", event_name_and_save
38
33
  end
@@ -42,20 +37,19 @@ module SimpleStateMachine
42
37
  event_name_and_save_bang = "#{event_name}_and_save!"
43
38
  unless @subject.method_defined?(event_name_and_save_bang)
44
39
  @subject.send(:define_method, event_name_and_save_bang) do |*args|
45
- result = nil
46
40
  old_state = self.send(self.class.state_machine_definition.state_method)
47
41
  send "#{event_name}_with_managed_state", *args
48
- if !self.errors.entries.empty?
42
+ if self.errors.entries.empty?
43
+ begin
44
+ save!
45
+ rescue ::ActiveRecord::RecordInvalid
46
+ self.send("#{self.class.state_machine_definition.state_method}=", old_state)
47
+ raise #re raise
48
+ end
49
+ else
49
50
  self.send("#{self.class.state_machine_definition.state_method}=", old_state)
50
51
  raise ::ActiveRecord::RecordInvalid.new(self)
51
52
  end
52
- begin
53
- result = save!
54
- rescue ::ActiveRecord::RecordInvalid
55
- self.send("#{self.class.state_machine_definition.state_method}=", old_state)
56
- raise #re raise
57
- end
58
- return result
59
53
  end
60
54
  @subject.send :alias_method, "#{event_name}!", event_name_and_save_bang
61
55
  end
@@ -4,13 +4,17 @@ module SimpleStateMachine
4
4
  module Graphviz
5
5
  # Graphviz dot format for rendering as a directional graph
6
6
  def to_graphviz_dot
7
- transitions.map { |t| t.to_graphviz_dot }.sort.join(";")
7
+ "digraph G {\n" +
8
+ transitions.map { |t| t.to_graphviz_dot }.sort.join(";\n") +
9
+ "\n}"
8
10
  end
9
11
 
10
12
  # Generates a url that renders states and events as a directional graph.
11
13
  # See http://code.google.com/apis/chart/docs/gallery/graphviz.html
12
14
  def google_chart_url
13
- "http://chart.googleapis.com/chart?cht=gv&chl=digraph{#{::CGI.escape to_graphviz_dot}}"
15
+ graph = transitions.map { |t| t.to_graphviz_dot }.sort.join(";")
16
+ puts graph
17
+ "http://chart.googleapis.com/chart?cht=gv&chl=digraph{#{::CGI.escape graph}}"
14
18
  end
15
19
  end
16
20
  end
@@ -1,3 +1,3 @@
1
1
  module SimpleStateMachine
2
- VERSION = "0.6.0"
2
+ VERSION = "0.6.1"
3
3
  end
@@ -5,9 +5,10 @@ require 'simple_state_machine/tools/inspector'
5
5
  require 'simple_state_machine/state_machine_definition'
6
6
  require 'simple_state_machine/transition'
7
7
  require 'simple_state_machine/decorator/default'
8
- # if defined?(ActiveRecord)
8
+ if defined?(ActiveRecord)
9
9
  require 'simple_state_machine/active_record'
10
10
  require 'simple_state_machine/decorator/active_record'
11
- # end
12
- require "simple_state_machine/railtie" if defined?(Rails::Railtie)
13
-
11
+ end
12
+ if defined?(Rails::Railtie)
13
+ require "simple_state_machine/railtie"
14
+ end
@@ -1,5 +1,15 @@
1
1
  namespace :ssm do
2
2
  namespace :graph do
3
+
4
+ desc 'Outputs a dot file. You must specify class=ClassNAME'
5
+ task :dot => :environment do
6
+ if clazz = ENV['class']
7
+ puts clazz.constantize.state_machine_definition.to_graphviz_dot
8
+ else
9
+ puts "Missing argument: class. Please specify class=ClassName"
10
+ end
11
+ end
12
+
3
13
  desc 'Generate a url for a google chart. You must specify class=ClassName'
4
14
  task :url => :environment do
5
15
  if clazz = ENV['class']
@@ -3,30 +3,19 @@ $:.push File.expand_path("../lib", __FILE__)
3
3
  require "simple_state_machine/version"
4
4
 
5
5
  Gem::Specification.new do |s|
6
- s.name = %q{simple_state_machine}
7
- s.version = SimpleStateMachine::VERSION
8
- s.platform = Gem::Platform::RUBY
9
-
10
- s.authors = ["Marek de Heus", "Petrik de Heus"]
11
- s.description = %q{A simple DSL to decorate existing methods with state transition guards.}
12
- s.email = ["FIX@example.com"]
13
- s.homepage = %q{http://github.com/mdh/ssm}
14
- s.extra_rdoc_files = [
15
- "LICENSE",
16
- "README.rdoc"
17
- ]
18
- s.files = `git ls-files`.split("\n")
19
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
- s.rdoc_options = ["--charset=UTF-8"]
21
- s.require_paths = ["lib"]
22
- s.rubygems_version = %q{1.3.7}
23
- s.summary = %q{A simple DSL to decorate existing methods with logic that guards state transitions.}
24
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
- s.add_development_dependency "rake"
26
- s.add_development_dependency "ZenTest"
27
- s.add_development_dependency "rspec"
28
- s.add_development_dependency "activerecord", "~>2.3.5"
29
- s.add_development_dependency "sqlite3-ruby"
30
- s.add_development_dependency "ruby-debug"
6
+ s.name = %q{simple_state_machine}
7
+ s.version = SimpleStateMachine::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Marek de Heus", "Petrik de Heus"]
10
+ s.description = %q{A simple DSL to decorate existing methods with state transition guards.}
11
+ s.email = ["FIX@example.com"]
12
+ s.homepage = %q{http://github.com/mdh/ssm}
13
+ s.extra_rdoc_files = ["LICENSE","README.rdoc"]
14
+ s.files = `git ls-files`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+ s.rdoc_options = ["--charset=UTF-8"]
17
+ s.require_paths = ["lib"]
18
+ s.summary = %q{A simple DSL to decorate existing methods with logic that guards state transitions.}
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
31
20
  end
32
21