aasm 3.0.26 → 3.1.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: 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