aasm 3.0.26 → 3.1.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: affc5be8f824b6ad897f4d8edca0a1809691e4b0
4
- data.tar.gz: 6c777aba81646cf8d0ac801a9ecfe5d21c43929e
3
+ metadata.gz: 624aadcd0801dd7a8fb367c00d29ea7f5dccd6f3
4
+ data.tar.gz: 8f4d0f3978038a5a16fbe2696efbb1bb1f173603
5
5
  SHA512:
6
- metadata.gz: 56acaaef0033789a207dbabffa8e0aa088b26f0f7c9796a692992dfea502356a1fbb45131c61bce93c850f0b8b85859a6fa8870394a268e3115c4c88305291a7
7
- data.tar.gz: acb7811990e9d2bc678395f1520c553f359432327f18b745a5a4d1e11fe702f87d1166ae18da9f375aac2aca2ac1e0083f283a97d7693f388bacebd74fcbcf9b
6
+ metadata.gz: 9359ea9f090c25f5ae789f3ef47e3ad8829ebf7074e23cb9fee9738537f9207e96a9c5e6ddbbe592c02b2d80aac18df7673aa3427735b0766b6e9a34d4c62dd9
7
+ data.tar.gz: ddbfc65e9019d92b165f75ebf170c68682e829ed969d9c743df02dabaa03bc53ca6397d5d5b3bb5a8ebe66cae53621cefd8f74bffc4890536638112a6856bf88
@@ -1,7 +1,7 @@
1
1
  language: ruby
2
2
 
3
3
  rvm:
4
- - 1.8.7
4
+ # - 1.8.7
5
5
  - 1.9.2
6
6
  - 1.9.3
7
7
  - 2.0.0
@@ -4,9 +4,17 @@
4
4
 
5
5
  * deprecated old aasm_* class methods (old-style DSL), in preparation for AASM v4.0.0
6
6
 
7
+ ## 3.1.0
8
+
9
+ * validating the current state (see [issue #95](https://github.com/aasm/aasm/issues/95), thanks to [@ivantsepp](https://github.com/ivantsepp))
10
+ * allow configuring behavior of nested transactions (see [issue #107](https://github.com/aasm/aasm/issues/107))
11
+ * support multiple guards per transition
12
+ * support event guards (see [issue #85](https://github.com/aasm/aasm/issues/85))
13
+ * support reading from- and to-state during on_transition callback (see [issue #100](https://github.com/aasm/aasm/issues/100))
14
+
7
15
  ## 3.0.26
8
16
 
9
- * support state.human_name (aliased to state.localized_name) (see [issue #105](https://github.com/aasm/aasm/issues/105)
17
+ * support state.human_name (aliased to state.localized_name) (see [issue #105](https://github.com/aasm/aasm/issues/105))
10
18
 
11
19
  ## 3.0.25
12
20
 
data/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # AASM - Ruby state machines
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/aasm.png)](http://badge.fury.io/rb/aasm) [![Build Status](https://secure.travis-ci.org/aasm/aasm.png?branch=master)](http://travis-ci.org/aasm/aasm) [![Code Climate](https://codeclimate.com/github/aasm/aasm.png)](https://codeclimate.com/github/aasm/aasm) [![Coverage Status](https://coveralls.io/repos/aasm/aasm/badge.png?branch=master)](https://coveralls.io/r/aasm/aasm)
3
+ <a href="http://badge.fury.io/rb/aasm"><img src="https://badge.fury.io/rb/aasm@2x.png" alt="Gem Version" height="18"></a>
4
+ [![Build Status](https://secure.travis-ci.org/aasm/aasm.png?branch=master)](http://travis-ci.org/aasm/aasm)
5
+ [![Code Climate](https://codeclimate.com/github/aasm/aasm.png)](https://codeclimate.com/github/aasm/aasm)
6
+ [![Coverage Status](https://coveralls.io/repos/aasm/aasm/badge.png?branch=master)](https://coveralls.io/r/aasm/aasm)
4
7
 
5
8
  This package contains AASM, a library for adding finite state machines to Ruby classes.
6
9
 
@@ -91,7 +94,7 @@ class Job
91
94
  state :sleeping, :initial => true, :before_enter => :do_something
92
95
  state :running
93
96
 
94
- event :run, :after => Proc.new { |user| notify_somebody(user) } do
97
+ event :run, :after => Proc.new { do_afterwards } do
95
98
  transitions :from => :sleeping, :to => :running, :on_transition => Proc.new {|obj, *args| obj.set_process(*args) }
96
99
  end
97
100
 
@@ -114,7 +117,7 @@ class Job
114
117
  ...
115
118
  end
116
119
 
117
- def notify_somebody(user)
120
+ def do_afterwards
118
121
  ...
119
122
  end
120
123
 
@@ -122,7 +125,7 @@ end
122
125
  ```
123
126
 
124
127
  In this case `do_something` is called before actually entering the state `sleeping`,
125
- while `notify_somebody` is called after the transition `run` (from `sleeping` to `running`)
128
+ while `do_afterwards` is called after the transition `run` (from `sleeping` to `running`)
126
129
  is finished.
127
130
 
128
131
  Here you can see a list of all possible callbacks, together with their order of calling:
@@ -149,6 +152,15 @@ In this case the `set_process` would be called with `:defagmentation` argument.
149
152
  In case of an error during the event processing the error is rescued and passed to `:error`
150
153
  callback, which can handle it or re-raise it for further propagation.
151
154
 
155
+ During the `:on_transition` callback (and reliably only then) you can access the
156
+ originating state (the from-state) and the target state (the to state), like this:
157
+
158
+ ```ruby
159
+ def set_process(name)
160
+ logger.info "from #{aasm.from_state} to #{aasm.to_state}"
161
+ end
162
+ ```
163
+
152
164
  ### Guards
153
165
 
154
166
  Let's assume you want to allow particular transitions only if a defined condition is
@@ -190,6 +202,24 @@ job.may_sleep? # => false
190
202
  job.sleep # => raises AASM::InvalidTransition
191
203
  ```
192
204
 
205
+ You can even provide a number of guards, which all have to succeed to proceed
206
+
207
+ ```ruby
208
+ def walked_the_dog?; ...; end
209
+
210
+ event :sleep do
211
+ transitions :from => :running, :to => :sleeping, :guards => [:cleaning_needed?, :walked_the_dog?]
212
+ end
213
+ ```
214
+
215
+ If you want to provide guards for all transitions withing an event, you can use event guards
216
+
217
+ ```ruby
218
+ event :sleep, :guards => [:walked_the_dog?] do
219
+ transitions :from => :running, :to => :sleeping, :guards => [:cleaning_needed?]
220
+ transitions :from => :cleaning, :to => :sleeping
221
+ end
222
+ ```
193
223
 
194
224
  ### ActiveRecord
195
225
 
@@ -339,6 +369,28 @@ class Job < ActiveRecord::Base
339
369
  end
340
370
  ```
341
371
 
372
+ If you want to encapsulate state changes within an own transaction, the behavior
373
+ of this nested transaction might be confusing. Take a look at
374
+ [ActiveRecord Nested Transactions](http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html)
375
+ if you want to know more about this. Nevertheless, AASM by default requires a new transaction
376
+ `transaction(:requires_new => true)`. You can override this behavior by changing
377
+ the configuration
378
+
379
+ ```ruby
380
+ class Job < ActiveRecord::Base
381
+ include AASM
382
+
383
+ aasm :requires_new_transaction => false do
384
+ ...
385
+ end
386
+
387
+ ...
388
+ end
389
+ ```
390
+
391
+ which then leads to `transaction(:requires_new => false)`, the Rails default.
392
+
393
+
342
394
  ### Column name & migration
343
395
 
344
396
  As a default AASM uses the column `aasm_state` to store the states. You can override
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
19
19
  # s.add_development_dependency 'mongoid' if Gem::Version.create(RUBY_VERSION.dup) >= Gem::Version.create('1.9.3')
20
20
  s.add_development_dependency 'rake'
21
21
  s.add_development_dependency 'sdoc'
22
- s.add_development_dependency 'rspec', '~> 2.14'
22
+ s.add_development_dependency 'rspec', '>= 2.14'
23
23
  s.add_development_dependency 'rr'
24
24
  # s.add_development_dependency 'sqlite3'
25
25
  s.add_development_dependency 'minitest'
@@ -5,24 +5,19 @@ module AASM
5
5
  @clazz = clazz
6
6
  @state_machine = AASM::StateMachine[@clazz]
7
7
  @state_machine.config.column = options[:column].to_sym if options[:column]
8
+ @options = options
8
9
 
9
- if options.key?(:whiny_transitions)
10
- @state_machine.config.whiny_transitions = options[:whiny_transitions]
11
- elsif @state_machine.config.whiny_transitions.nil?
12
- @state_machine.config.whiny_transitions = true # this is the default, so let's cry
13
- end
10
+ # let's cry if the transition is invalid
11
+ configure :whiny_transitions, true
14
12
 
15
- if options.key?(:create_scopes)
16
- @state_machine.config.create_scopes = options[:create_scopes]
17
- elsif @state_machine.config.create_scopes.nil?
18
- @state_machine.config.create_scopes = true # this is the default, so let's create scopes
19
- end
13
+ # create named scopes for each state
14
+ configure :create_scopes, true
20
15
 
21
- if options.key?(:skip_validation_on_save)
22
- @state_machine.config.skip_validation_on_save = options[:skip_validation_on_save]
23
- elsif @state_machine.config.skip_validation_on_save.nil?
24
- @state_machine.config.skip_validation_on_save = false # this is the default, so don't store any new state if the model is invalid
25
- end
16
+ # don't store any new state if the model is invalid
17
+ configure :skip_validation_on_save, false
18
+
19
+ # use requires_new for nested transactions
20
+ configure :requires_new_transaction, true
26
21
  end
27
22
 
28
23
  def initial_state(new_initial_state=nil)
@@ -87,5 +82,16 @@ module AASM
87
82
  end
88
83
  end
89
84
 
85
+ private
86
+
87
+ def configure(key, default_value)
88
+ @state_machine.config.send(:new_ostruct_member, key)
89
+ if @options.key?(key)
90
+ @state_machine.config.send("#{key}=", @options[key])
91
+ elsif @state_machine.config.send(key).nil?
92
+ @state_machine.config.send("#{key}=", default_value)
93
+ end
94
+ end
95
+
90
96
  end
91
97
  end
@@ -6,6 +6,7 @@ module AASM
6
6
  def initialize(name, options = {}, &block)
7
7
  @name = name
8
8
  @transitions = []
9
+ @guards = Array(options[:guard] || options[:guards])
9
10
  update(options, &block)
10
11
  end
11
12
 
@@ -57,18 +58,28 @@ module AASM
57
58
  ## DSL interface
58
59
  def transitions(definitions=nil)
59
60
  if definitions # define new transitions
60
- # Create a separate transition for each from state to the given state
61
+ # Create a separate transition for each from-state to the given state
61
62
  Array(definitions[:from]).each do |s|
62
- @transitions << AASM::Transition.new(definitions.merge({:from => s.to_sym}))
63
+ @transitions << AASM::Transition.new(attach_event_guards(definitions.merge(:from => s.to_sym)))
64
+ end
65
+ # Create a transition if :to is specified without :from (transitions from ANY state)
66
+ if @transitions.empty? && definitions[:to]
67
+ @transitions << AASM::Transition.new(attach_event_guards(definitions))
63
68
  end
64
- # Create a transition if to is specified without from (transitions from ANY state)
65
- @transitions << AASM::Transition.new(definitions) if @transitions.empty? && definitions[:to]
66
69
  end
67
70
  @transitions
68
71
  end
69
72
 
70
73
  private
71
74
 
75
+ def attach_event_guards(definitions)
76
+ unless @guards.empty?
77
+ given_guards = Array(definitions.delete(:guard) || definitions.delete(:guards))
78
+ definitions[:guards] = given_guards + @guards
79
+ end
80
+ definitions
81
+ end
82
+
72
83
  def update(options = {}, &block)
73
84
  @options = options
74
85
  if block then
@@ -1,6 +1,8 @@
1
1
  module AASM
2
2
  class InstanceBase
3
3
 
4
+ attr_accessor :from_state, :to_state
5
+
4
6
  def initialize(instance)
5
7
  @instance = instance
6
8
  end
@@ -36,8 +36,12 @@ module AASM
36
36
  else
37
37
  base.before_validation_on_create(:aasm_ensure_initial_state)
38
38
  end
39
+
39
40
  # ensure initial aasm state even when validations are skipped
40
41
  base.before_create(:aasm_ensure_initial_state)
42
+
43
+ # ensure state is in the list of states
44
+ base.validate :aasm_validate_states
41
45
  end
42
46
 
43
47
  module ClassMethods
@@ -134,7 +138,7 @@ module AASM
134
138
  end
135
139
 
136
140
  def aasm_fire_event(name, options, *args, &block)
137
- success = self.class.transaction(:requires_new => true) do
141
+ success = self.class.transaction(:requires_new => requires_new?) do
138
142
  super
139
143
  end
140
144
 
@@ -145,6 +149,18 @@ module AASM
145
149
 
146
150
  success
147
151
  end
152
+
153
+ def requires_new?
154
+ AASM::StateMachine[self.class].config.requires_new_transaction
155
+ end
156
+
157
+ def aasm_validate_states
158
+ unless AASM::StateMachine[self.class].config.skip_validation_on_save
159
+ if aasm.current_state && !aasm.states.include?(aasm.current_state)
160
+ self.errors.add(AASM::StateMachine[self.class].config.column , "is invalid")
161
+ end
162
+ end
163
+ end
148
164
  end # InstanceMethods
149
165
 
150
166
  end
@@ -4,20 +4,24 @@ module AASM
4
4
  alias_method :options, :opts
5
5
 
6
6
  def initialize(opts)
7
- @from, @to, @guard, @on_transition = opts[:from], opts[:to], opts[:guard], opts[:on_transition]
7
+ @from = opts[:from]
8
+ @to = opts[:to]
9
+ @guards = Array(opts[:guard] || opts[:guards])
10
+ @on_transition = opts[:on_transition]
8
11
  @opts = opts
9
12
  end
10
13
 
11
14
  # TODO: should be named allowed? or similar
12
15
  def perform(obj, *args)
13
- case @guard
16
+ @guards.each do |guard|
17
+ case guard
14
18
  when Symbol, String
15
- obj.send(@guard, *args)
19
+ return false unless obj.send(guard, *args)
16
20
  when Proc
17
- @guard.call(obj, *args)
18
- else
19
- true
21
+ return false unless guard.call(obj, *args)
22
+ end
20
23
  end
24
+ true
21
25
  end
22
26
 
23
27
  def execute(obj, *args)
@@ -37,6 +41,9 @@ module AASM
37
41
  private
38
42
 
39
43
  def _execute(obj, on_transition, *args)
44
+ obj.aasm.from_state = @from if obj.aasm.respond_to?(:from_state=)
45
+ obj.aasm.to_state = @to if obj.aasm.respond_to?(:to_state=)
46
+
40
47
  case on_transition
41
48
  when Proc
42
49
  on_transition.arity == 0 ? on_transition.call : on_transition.call(obj, *args)
@@ -1,3 +1,3 @@
1
1
  module AASM
2
- VERSION = "3.0.26"
2
+ VERSION = "3.1.0"
3
3
  end
@@ -0,0 +1,48 @@
1
+ class Guardian
2
+ include AASM
3
+
4
+ aasm do
5
+ state :alpha, :initial => true
6
+ state :beta
7
+
8
+ event :use_one_guard_that_succeeds do
9
+ transitions :from => :alpha, :to => :beta, :guard => :succeed
10
+ end
11
+ event :use_one_guard_that_fails do
12
+ transitions :from => :alpha, :to => :beta, :guard => :fail
13
+ end
14
+
15
+ event :use_guards_that_succeed do
16
+ transitions :from => :alpha, :to => :beta, :guards => [:succeed, :another_succeed]
17
+ end
18
+ event :use_guards_where_the_first_fails do
19
+ transitions :from => :alpha, :to => :beta, :guards => [:succeed, :fail]
20
+ end
21
+ event :use_guards_where_the_second_fails do
22
+ transitions :from => :alpha, :to => :beta, :guards => [:fail, :succeed]
23
+ end
24
+
25
+ event :use_event_guards_that_succeed, :guards => [:succeed, :another_succeed] do
26
+ transitions :from => :alpha, :to => :beta
27
+ end
28
+ event :use_event_and_transition_guards_that_succeed, :guards => [:succeed, :another_succeed] do
29
+ transitions :from => :alpha, :to => :beta, :guards => [:more_succeed]
30
+ end
31
+ event :use_event_guards_where_the_first_fails, :guards => [:succeed, :fail] do
32
+ transitions :from => :alpha, :to => :beta
33
+ end
34
+ event :use_event_guards_where_the_second_fails, :guards => [:fail, :succeed] do
35
+ transitions :from => :alpha, :to => :beta, :guard => :another_succeed
36
+ end
37
+ event :use_event_and_transition_guards_where_third_fails, :guards => [:succeed, :another_succeed] do
38
+ transitions :from => :alpha, :to => :beta, :guards => [:fail]
39
+ end
40
+ end
41
+
42
+ def fail; false; end
43
+
44
+ def succeed; true; end
45
+ def another_succeed; true; end
46
+ def more_succeed; true; end
47
+
48
+ end
@@ -3,19 +3,19 @@ require 'models/active_record/api.rb'
3
3
 
4
4
  describe "reading the current state" do
5
5
  it "uses the AASM default" do
6
- DefaultState.new.aasm.current_state.should eql :alpha
6
+ expect(DefaultState.new.aasm.current_state).to eql :alpha
7
7
  end
8
8
 
9
9
  it "uses the provided method" do
10
- ProvidedState.new.aasm.current_state.should eql :beta
10
+ expect(ProvidedState.new.aasm.current_state).to eql :beta
11
11
  end
12
12
 
13
13
  it "uses the persistence storage" do
14
- PersistedState.new.aasm.current_state.should eql :alpha
14
+ expect(PersistedState.new.aasm.current_state).to eql :alpha
15
15
  end
16
16
 
17
17
  it "uses the provided method even if persisted" do
18
- ProvidedAndPersistedState.new.aasm.current_state.should eql :gamma
18
+ expect(ProvidedAndPersistedState.new.aasm.current_state).to eql :gamma
19
19
  end
20
20
  end
21
21
 
@@ -23,25 +23,25 @@ describe "writing and persisting the current state" do
23
23
  it "uses the AASM default" do
24
24
  o = DefaultState.new
25
25
  o.release!
26
- o.persisted_store.should be_nil
26
+ expect(o.persisted_store).to be_nil
27
27
  end
28
28
 
29
29
  it "uses the provided method" do
30
30
  o = ProvidedState.new
31
31
  o.release!
32
- o.persisted_store.should eql :beta
32
+ expect(o.persisted_store).to eql :beta
33
33
  end
34
34
 
35
35
  it "uses the persistence storage" do
36
36
  o = PersistedState.new
37
37
  o.release!
38
- o.persisted_store.should be_nil
38
+ expect(o.persisted_store).to be_nil
39
39
  end
40
40
 
41
41
  it "uses the provided method even if persisted" do
42
42
  o = ProvidedAndPersistedState.new
43
43
  o.release!
44
- o.persisted_store.should eql :beta
44
+ expect(o.persisted_store).to eql :beta
45
45
  end
46
46
  end
47
47
 
@@ -49,24 +49,24 @@ describe "writing the current state without persisting it" do
49
49
  it "uses the AASM default" do
50
50
  o = DefaultState.new
51
51
  o.release
52
- o.transient_store.should be_nil
52
+ expect(o.transient_store).to be_nil
53
53
  end
54
54
 
55
55
  it "uses the provided method" do
56
56
  o = ProvidedState.new
57
57
  o.release
58
- o.transient_store.should eql :beta
58
+ expect(o.transient_store).to eql :beta
59
59
  end
60
60
 
61
61
  it "uses the persistence storage" do
62
62
  o = PersistedState.new
63
63
  o.release
64
- o.transient_store.should be_nil
64
+ expect(o.transient_store).to be_nil
65
65
  end
66
66
 
67
67
  it "uses the provided method even if persisted" do
68
68
  o = ProvidedAndPersistedState.new
69
69
  o.release
70
- o.transient_store.should eql :beta
70
+ expect(o.transient_store).to eql :beta
71
71
  end
72
72
  end