simple_state_machine 0.6.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
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