simple_state_machine 0.4.3 → 0.5.0.beta

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.
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ #--debugger
3
+ --backtrace
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ # A sample Gemfile
2
+ source "http://rubygems.org"
3
+
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,34 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ simple_state_machine (1.5.0)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ ZenTest (4.4.2)
10
+ activerecord (2.3.10)
11
+ activesupport (= 2.3.10)
12
+ activesupport (2.3.10)
13
+ diff-lcs (1.1.2)
14
+ rake (0.8.7)
15
+ rspec (2.5.0)
16
+ rspec-core (~> 2.5.0)
17
+ rspec-expectations (~> 2.5.0)
18
+ rspec-mocks (~> 2.5.0)
19
+ rspec-core (2.5.1)
20
+ rspec-expectations (2.5.0)
21
+ diff-lcs (~> 1.1.2)
22
+ rspec-mocks (2.5.0)
23
+ sqlite3-ruby (1.3.1)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ ZenTest
30
+ activerecord (~> 2.3.5)
31
+ rake
32
+ rspec
33
+ simple_state_machine!
34
+ sqlite3-ruby
data/Rakefile CHANGED
@@ -1,38 +1,21 @@
1
- require 'rubygems'
2
- require 'rake'
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
3
 
4
- begin
5
- require 'jeweler'
6
- Jeweler::Tasks.new do |gem|
7
- gem.name = "simple_state_machine"
8
- gem.summary = %Q{A statemachine that focuses on events instead of states}
9
- gem.description = %Q{A simple DSL to decorate existing methods with logic that guards state transitions.}
10
- gem.email = ["FIX@example.com"]
11
- gem.homepage = "http://github.com/mdh/ssm"
12
- gem.authors = ["Marek de Heus", "Petrik de Heus"]
13
- gem.add_development_dependency "rspec", ">= 1.2.9"
14
- # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
- end
16
- Jeweler::GemcutterTasks.new
17
- rescue LoadError
18
- puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
- end
20
-
21
- require 'spec/rake/spectask'
22
- Spec::Rake::SpecTask.new(:spec) do |spec|
23
- spec.libs << 'lib' << 'spec'
24
- spec.spec_files = FileList['spec/**/*_spec.rb']
25
- end
26
-
27
- Spec::Rake::SpecTask.new(:rcov) do |spec|
28
- spec.libs << 'lib' << 'spec'
29
- spec.pattern = 'spec/**/*_spec.rb'
30
- spec.rcov = true
31
- end
32
-
33
- task :spec => :check_dependencies
34
-
35
- task :default => :spec
4
+ #require 'spec/rake/spectask'
5
+ #Spec::Rake::SpecTask.new(:spec) do |spec|
6
+ # spec.libs << 'lib' << 'spec'
7
+ # spec.spec_files = FileList['spec/**/*_spec.rb']
8
+ #end
9
+ #
10
+ #Spec::Rake::SpecTask.new(:rcov) do |spec|
11
+ # spec.libs << 'lib' << 'spec'
12
+ # spec.pattern = 'spec/**/*_spec.rb'
13
+ # spec.rcov = true
14
+ #end
15
+ #
16
+ #task :spec => :check_dependencies
17
+ #
18
+ #task :default => :spec
36
19
 
37
20
  require 'rake/rdoctask'
38
21
  Rake::RDocTask.new do |rdoc|
@@ -0,0 +1 @@
1
+ Autotest.add_discovery { "rspec2" }
@@ -1,10 +1,8 @@
1
1
  module SimpleStateMachine::ActiveRecord
2
2
 
3
- include SimpleStateMachine::StateMachineMixin
4
-
5
- def state_machine_decorator subject
6
- Decorator.new subject
7
- end
3
+ include SimpleStateMachine::Mountable
4
+ include SimpleStateMachine::Extendable
5
+ include SimpleStateMachine::Inheritable
8
6
 
9
7
  class Decorator < SimpleStateMachine::Decorator
10
8
 
@@ -60,5 +58,12 @@ module SimpleStateMachine::ActiveRecord
60
58
  def define_state_getter_method; end
61
59
 
62
60
  end
63
-
61
+
62
+ def state_machine_definition
63
+ unless @state_machine_definition
64
+ @state_machine_definition = SimpleStateMachine::StateMachineDefinition.new
65
+ @state_machine_definition.lazy_decorator = lambda { Decorator.new(self) }
66
+ end
67
+ @state_machine_definition
68
+ end
64
69
  end
@@ -1,50 +1,68 @@
1
1
  module SimpleStateMachine
2
+
3
+ require 'cgi'
2
4
 
3
- class Error < ::RuntimeError
5
+ class IllegalStateTransitionError < ::RuntimeError
4
6
  end
5
7
 
8
+ ##
9
+ # Allows class to mount a state_machine
10
+ module Mountable
11
+ def state_machine_definition
12
+ unless @state_machine_definition
13
+ @state_machine_definition = StateMachineDefinition.new
14
+ @state_machine_definition.lazy_decorator = lambda { Decorator.new(self) }
15
+ end
16
+ @state_machine_definition
17
+ end
18
+
19
+ def state_machine_definition= state_machine_definition
20
+ @state_machine_definition = state_machine_definition
21
+ state_machine_definition.transitions.each do |transition|
22
+ state_machine_definition.decorator.decorate(transition)
23
+ end
24
+ end
25
+ end
26
+ include Mountable
27
+
6
28
  ##
7
29
  # Adds state machine methods to the extended class
8
- module StateMachineMixin
30
+ module Extendable
9
31
 
10
32
  # mark the method as an event and specify how the state should transition
11
33
  def event event_name, state_transitions
12
34
  state_transitions.each do |froms, to|
13
35
  [froms].flatten.each do |from|
14
- transition = state_machine_definition.add_transition(event_name, from, to)
15
- state_machine_decorator(self).decorate(transition)
36
+ state_machine_definition.add_transition(event_name, from, to)
16
37
  end
17
38
  end
18
39
  end
19
40
 
20
- def state_machine_definition
21
- @state_machine_definition ||= StateMachineDefinition.new
22
- end
23
-
24
- def state_machine_definition= state_machine_definition
25
- @state_machine_definition = state_machine_definition
26
- state_machine_definition.transitions.each do |transition|
27
- state_machine_decorator(self).decorate(transition)
28
- end
29
- end
30
-
31
- def state_machine_decorator subject
32
- Decorator.new subject
33
- end
41
+ end
42
+ include Extendable
34
43
 
44
+ ##
45
+ # Allows subclasses to inherit state machines
46
+ module Inheritable
35
47
  def inherited(subclass)
36
48
  subclass.state_machine_definition = state_machine_definition.clone
49
+ decorator = state_machine_definition.decorator
50
+ decorator.subject = subclass
51
+ subclass.state_machine_definition.decorator = decorator
37
52
  super
38
53
  end
39
54
  end
40
-
41
- include StateMachineMixin
55
+ include Inheritable
42
56
 
43
57
  ##
44
58
  # Defines state machine transitions
45
59
  class StateMachineDefinition
46
60
 
47
- attr_writer :state_method
61
+ attr_writer :state_method, :decorator, :lazy_decorator
62
+
63
+ def decorator
64
+ @decorator ||= @lazy_decorator.call
65
+ end
48
66
 
49
67
  def transitions
50
68
  @transitions ||= []
@@ -53,12 +71,28 @@ module SimpleStateMachine
53
71
  def add_transition event_name, from, to
54
72
  transition = Transition.new(event_name, from, to)
55
73
  transitions << transition
56
- transition
74
+ decorator.decorate(transition)
57
75
  end
58
76
 
59
77
  def state_method
60
78
  @state_method ||= :state
61
79
  end
80
+
81
+ # Human readable format: old_state.event! => new_state
82
+ def to_s
83
+ transitions.map(&:to_s).join("\n")
84
+ end
85
+
86
+ # Graphiz dot format for rendering as a directional graph
87
+ def to_graphiz_dot
88
+ transitions.map { |t| t.to_graphiz_dot }.join(";")
89
+ end
90
+
91
+ # Generates a url that renders states and events as a directional graph.
92
+ # See http://code.google.com/apis/chart/docs/gallery/graphviz.html
93
+ def google_chart_url
94
+ "http://chart.googleapis.com/chart?cht=gv&chl=digraph{#{::CGI.escape to_graphiz_dot}}"
95
+ end
62
96
  end
63
97
 
64
98
  ##
@@ -128,7 +162,7 @@ module SimpleStateMachine
128
162
 
129
163
  # override with your own implementation, like setting errors in your model
130
164
  def illegal_event_callback event_name
131
- raise Error.new("You cannot '#{event_name}' when state is '#{@subject.send(state_method)}'")
165
+ raise IllegalStateTransitionError.new("You cannot '#{event_name}' when state is '#{@subject.send(state_method)}'")
132
166
  end
133
167
 
134
168
  end
@@ -153,6 +187,15 @@ module SimpleStateMachine
153
187
  is_same_event?(event_name) && error.class == from
154
188
  end
155
189
 
190
+ def to_s
191
+ "#{from}.#{event_name}! => #{to}"
192
+ end
193
+
194
+ def to_graphiz_dot
195
+ %("#{from}"->"#{to}"[label=#{event_name}])
196
+ end
197
+
198
+
156
199
  private
157
200
 
158
201
  def is_same_event?(event_name)
@@ -168,6 +211,7 @@ module SimpleStateMachine
168
211
  # Decorates @subject with methods to access the state machine
169
212
  class Decorator
170
213
 
214
+ attr_writer :subject
171
215
  def initialize(subject)
172
216
  @subject = subject
173
217
  define_state_machine_method
@@ -191,7 +235,7 @@ module SimpleStateMachine
191
235
  end
192
236
 
193
237
  def define_state_helper_method state
194
- unless @subject.method_defined?("#{state.to_s}?")
238
+ unless any_method_defined?("#{state.to_s}?")
195
239
  @subject.send(:define_method, "#{state.to_s}?") do
196
240
  self.send(self.class.state_machine_definition.state_method) == state.to_s
197
241
  end
@@ -199,7 +243,7 @@ module SimpleStateMachine
199
243
  end
200
244
 
201
245
  def define_event_method event_name
202
- unless @subject.method_defined?("#{event_name}")
246
+ unless any_method_defined?("#{event_name}")
203
247
  @subject.send(:define_method, "#{event_name}") {}
204
248
  end
205
249
  end
@@ -218,7 +262,7 @@ module SimpleStateMachine
218
262
  end
219
263
 
220
264
  def define_state_setter_method
221
- unless @subject.method_defined?("#{state_method}=")
265
+ unless any_method_defined?("#{state_method}=")
222
266
  @subject.send(:define_method, "#{state_method}=") do |new_state|
223
267
  instance_variable_set(:"@#{self.class.state_machine_definition.state_method}", new_state)
224
268
  end
@@ -226,10 +270,16 @@ module SimpleStateMachine
226
270
  end
227
271
 
228
272
  def define_state_getter_method
229
- unless @subject.method_defined?(state_method)
273
+ unless any_method_defined?(state_method)
230
274
  @subject.send(:attr_reader, state_method)
231
275
  end
232
276
  end
277
+
278
+ def any_method_defined?(method)
279
+ @subject.method_defined?(method) ||
280
+ @subject.protected_method_defined?(method) ||
281
+ @subject.private_method_defined?(method)
282
+ end
233
283
 
234
284
  protected
235
285
 
@@ -0,0 +1,3 @@
1
+ module SimpleStateMachine
2
+ VERSION = "0.5.0.beta"
3
+ end
@@ -1,2 +1,2 @@
1
1
  require 'simple_state_machine/simple_state_machine'
2
- require 'simple_state_machine/active_record'
2
+ require 'simple_state_machine/active_record'
@@ -1,72 +1,31 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
1
  # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "simple_state_machine/version"
5
4
 
6
5
  Gem::Specification.new do |s|
7
6
  s.name = %q{simple_state_machine}
8
- s.version = "0.4.3"
7
+ s.version = SimpleStateMachine::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
9
 
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
10
  s.authors = ["Marek de Heus", "Petrik de Heus"]
12
- s.date = %q{2010-09-14}
13
11
  s.description = %q{A simple DSL to decorate existing methods with logic that guards state transitions.}
14
12
  s.email = ["FIX@example.com"]
13
+ s.homepage = %q{http://github.com/mdh/ssm}
15
14
  s.extra_rdoc_files = [
16
15
  "LICENSE",
17
16
  "README.rdoc"
18
17
  ]
19
- s.files = [
20
- ".gitignore",
21
- "LICENSE",
22
- "README.rdoc",
23
- "Rakefile",
24
- "VERSION",
25
- "examples/conversation.rb",
26
- "examples/lamp.rb",
27
- "examples/relationship.rb",
28
- "examples/traffic_light.rb",
29
- "examples/user.rb",
30
- "lib/simple_state_machine.rb",
31
- "lib/simple_state_machine/active_record.rb",
32
- "lib/simple_state_machine/simple_state_machine.rb",
33
- "simple_state_machine.gemspec",
34
- "spec/active_record_spec.rb",
35
- "spec/decorator_spec.rb",
36
- "spec/examples_spec.rb",
37
- "spec/simple_state_machine_spec.rb",
38
- "spec/spec.opts",
39
- "spec/spec_helper.rb"
40
- ]
41
- s.homepage = %q{http://github.com/mdh/ssm}
18
+ s.files = `git ls-files`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
42
20
  s.rdoc_options = ["--charset=UTF-8"]
43
21
  s.require_paths = ["lib"]
44
22
  s.rubygems_version = %q{1.3.7}
45
23
  s.summary = %q{A statemachine that focuses on events instead of states}
46
- s.test_files = [
47
- "spec/active_record_spec.rb",
48
- "spec/decorator_spec.rb",
49
- "spec/examples_spec.rb",
50
- "spec/simple_state_machine_spec.rb",
51
- "spec/spec_helper.rb",
52
- "examples/conversation.rb",
53
- "examples/lamp.rb",
54
- "examples/relationship.rb",
55
- "examples/traffic_light.rb",
56
- "examples/user.rb"
57
- ]
58
-
59
- if s.respond_to? :specification_version then
60
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
61
- s.specification_version = 3
62
-
63
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
64
- s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
65
- else
66
- s.add_dependency(%q<rspec>, [">= 1.2.9"])
67
- end
68
- else
69
- s.add_dependency(%q<rspec>, [">= 1.2.9"])
70
- end
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"
71
30
  end
72
31
 
@@ -1,28 +1,29 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
- require 'rubygems'
4
- gem 'activerecord', '~> 2.3.5'
3
+ require "rubygems"
4
+ require "bundler"
5
+ Bundler.require
6
+ #Bundler.setup(:test)#, :activerecord)
5
7
  require 'active_record'
6
8
  require 'examples/user'
7
9
 
8
- ActiveRecord::Base.logger = Logger.new STDOUT
9
- ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
10
+ ActiveRecord::Base.logger = Logger.new "test.log"
11
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3",
12
+ :database => ":memory:")
10
13
 
11
14
  def setup_db
12
15
  ActiveRecord::Schema.define(:version => 1) do
13
16
  create_table :users do |t|
14
- t.column :id, :integer
15
- t.column :name, :string
16
- t.column :state, :string
17
- t.column :activation_code, :string
18
- t.column :created_at, :datetime
19
- t.column :updated_at, :datetime
17
+ t.column :id, :integer
18
+ t.column :name, :string
19
+ t.column :state, :string
20
+ t.column :activation_code, :string
21
+ t.column :created_at, :datetime
22
+ t.column :updated_at, :datetime
20
23
  end
21
- end
22
- ActiveRecord::Schema.define(:version => 1) do
23
24
  create_table :tickets do |t|
24
- t.column :id, :integer
25
- t.column :ssm_state, :string
25
+ t.column :id, :integer
26
+ t.column :ssm_state, :string
26
27
  end
27
28
  end
28
29
  end
@@ -87,8 +88,10 @@ describe ActiveRecord do
87
88
 
88
89
  it "raises an error if an invalid state_transition is called" do
89
90
  user = User.create!(:name => 'name')
90
- l = lambda { user.confirm_invitation_and_save 'abc' }
91
- l.should raise_error(SimpleStateMachine::Error, "You cannot 'confirm_invitation' when state is 'new'")
91
+ expect {
92
+ user.confirm_invitation_and_save 'abc'
93
+ }.to raise_error(SimpleStateMachine::IllegalStateTransitionError,
94
+ "You cannot 'confirm_invitation' when state is 'new'")
92
95
  end
93
96
 
94
97
  it "returns false and keeps state if record is invalid" do
@@ -130,16 +133,20 @@ describe ActiveRecord do
130
133
 
131
134
  it "raises an error if an invalid state_transition is called" do
132
135
  user = User.create!(:name => 'name')
133
- l = lambda { user.confirm_invitation_and_save! 'abc' }
134
- l.should raise_error(SimpleStateMachine::Error, "You cannot 'confirm_invitation' when state is 'new'")
136
+ expect {
137
+ user.confirm_invitation_and_save! 'abc'
138
+ }.to raise_error(SimpleStateMachine::IllegalStateTransitionError,
139
+ "You cannot 'confirm_invitation' when state is 'new'")
135
140
  end
136
141
 
137
142
  it "raises a RecordInvalid and keeps state if record is invalid" do
138
143
  user = User.new
139
144
  user.should be_new
140
145
  user.should_not be_valid
141
- l = lambda { user.invite_and_save! }
142
- l.should raise_error(ActiveRecord::RecordInvalid, "Validation failed: Name can't be blank")
146
+ expect {
147
+ user.invite_and_save!
148
+ }.to raise_error(ActiveRecord::RecordInvalid,
149
+ "Validation failed: Name can't be blank")
143
150
  user.should be_new
144
151
  end
145
152
 
@@ -147,13 +154,34 @@ describe ActiveRecord do
147
154
  user = User.create!(:name => 'name')
148
155
  user.invite_and_save!
149
156
  user.should be_invited
150
- l = lambda { user.confirm_invitation_and_save!('x') }
151
- l.should raise_error(ActiveRecord::RecordInvalid, "Validation failed: Activation code is invalid")
157
+ expect {
158
+ user.confirm_invitation_and_save!('x')
159
+ }.to raise_error(ActiveRecord::RecordInvalid,
160
+ "Validation failed: Activation code is invalid")
152
161
  user.should be_invited
153
162
  end
154
163
 
155
164
  end
156
165
 
166
+ describe "event" do
167
+
168
+ it "does not persist transitions" do
169
+ user = User.create!(:name => 'name')
170
+ user.invite.should == true
171
+ User.find(user.id).should_not be_invited
172
+ User.find(user.id).activation_code.should be_nil
173
+ end
174
+
175
+ it "returns false and keeps state if record is invalid" do
176
+ user = User.new
177
+ user.should be_new
178
+ user.should_not be_valid
179
+ user.invite.should == false
180
+ user.should be_new
181
+ end
182
+
183
+ end
184
+
157
185
  describe "event!" do
158
186
 
159
187
  it "persists transitions" do
@@ -167,8 +195,7 @@ describe ActiveRecord do
167
195
  user = User.new
168
196
  user.should be_new
169
197
  user.should_not be_valid
170
- l = lambda { user.invite! }
171
- l.should raise_error(ActiveRecord::RecordInvalid, "Validation failed: Name can't be blank")
198
+ expect { user.invite! }.to raise_error(ActiveRecord::RecordInvalid, "Validation failed: Name can't be blank")
172
199
  user.should be_new
173
200
  end
174
201
 
@@ -191,4 +218,6 @@ describe ActiveRecord do
191
218
  end
192
219
 
193
220
  end
221
+
194
222
  end
223
+