aasm 3.0.14 → 3.0.15

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,9 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 3.0.15
4
+
5
+ * added support for localized state names (on a class level, like Record.aasm.states.map(&:localized_name))
6
+
3
7
  ## 3.0.14
4
8
 
5
9
  * supporting event inspection for to-states transitions (`Event#transitions_to_state?`)
@@ -2,15 +2,14 @@ module AASM
2
2
 
3
3
  def self.included(base) #:nodoc:
4
4
  base.extend AASM::ClassMethods
5
-
6
- unless AASM::StateMachine[base]
7
- AASM::StateMachine[base] = AASM::StateMachine.new('')
8
- end
5
+ AASM::StateMachine[base] ||= AASM::StateMachine.new('')
9
6
  AASM::Persistence.set_persistence(base)
10
7
  super
11
8
  end
12
9
 
13
10
  module ClassMethods
11
+
12
+ # make sure inheritance (aka subclassing) works with AASM
14
13
  def inherited(klass)
15
14
  AASM::StateMachine[klass] = AASM::StateMachine[self].clone
16
15
  super
@@ -18,13 +17,14 @@ module AASM
18
17
 
19
18
  def aasm(options={}, &block)
20
19
  @aasm ||= AASM::Base.new(self, options)
21
- @aasm.instance_eval(&block) if block
20
+ @aasm.instance_eval(&block) if block # new DSL
22
21
  @aasm
23
22
  end
24
23
 
24
+ # TODO: maybe better: aasm.initial_state
25
25
  def aasm_initial_state(set_state=nil)
26
26
  if set_state
27
- # deprecated
27
+ # deprecated way to set the value
28
28
  AASM::StateMachine[self].initial_state = set_state
29
29
  else
30
30
  AASM::StateMachine[self].initial_state
@@ -69,7 +69,8 @@ module AASM
69
69
  aasm.states_for_select
70
70
  end
71
71
 
72
- def aasm_human_event_name(event)
72
+ # aasm.event(:event_name).human?
73
+ def aasm_human_event_name(event) # event_name?
73
74
  AASM::SupportingClasses::Localizer.new.human_event_name(self, event)
74
75
  end
75
76
  end # ClassMethods
@@ -110,7 +111,7 @@ module AASM
110
111
  end
111
112
 
112
113
  def aasm_human_state
113
- AASM::SupportingClasses::Localizer.new.human_state(self)
114
+ AASM::SupportingClasses::Localizer.new.human_state_name(self.class, aasm_current_state)
114
115
  end
115
116
 
116
117
  private
@@ -1,5 +1,6 @@
1
1
  module AASM
2
2
  class Base
3
+
3
4
  def initialize(clazz, options={}, &block)
4
5
  @clazz = clazz
5
6
  sm = AASM::StateMachine[@clazz]
@@ -18,10 +19,14 @@ module AASM
18
19
  end
19
20
  end
20
21
 
22
+ def initial_state
23
+ AASM::StateMachine[@clazz].initial_state
24
+ end
25
+
21
26
  def state(name, options={})
22
27
  # @clazz.aasm_state(name, options)
23
28
  sm = AASM::StateMachine[@clazz]
24
- sm.create_state(name, options)
29
+ sm.create_state(name, @clazz, options)
25
30
  sm.initial_state = name if options[:initial] || !sm.initial_state
26
31
 
27
32
  @clazz.send(:define_method, "#{name.to_s}?") do
@@ -1,5 +1,7 @@
1
1
  module AASM
2
2
  class StateMachine
3
+
4
+ # the following two methods provide the storage of all state machines
3
5
  def self.[](clazz)
4
6
  (@machines ||= {})[clazz.to_s]
5
7
  end
@@ -11,6 +13,7 @@ module AASM
11
13
  attr_accessor :states, :events, :initial_state, :config
12
14
  attr_reader :name
13
15
 
16
+ # QUESTION: what's the name for? [alto, 2012-11-28]
14
17
  def initialize(name)
15
18
  @name = name
16
19
  @initial_state = nil
@@ -19,14 +22,16 @@ module AASM
19
22
  @config = OpenStruct.new
20
23
  end
21
24
 
25
+ # called internally by Ruby 1.9 after clone()
22
26
  def initialize_copy(orig)
23
27
  super
24
28
  @states = @states.dup
25
29
  @events = @events.dup
26
30
  end
27
31
 
28
- def create_state(name, options)
29
- @states << AASM::SupportingClasses::State.new(name, options) unless @states.include?(name)
32
+ def create_state(name, clazz, options)
33
+ @states << AASM::SupportingClasses::State.new(name, clazz, options) unless @states.include?(name)
30
34
  end
31
- end
35
+
36
+ end # StateMachine
32
37
  end # AASM
@@ -9,21 +9,20 @@ module AASM
9
9
  translate_queue(checklist) || I18n.translate(checklist.shift, :default => event.to_s.humanize)
10
10
  end
11
11
 
12
- def human_state(obj)
13
- klass = obj.class
12
+ def human_state_name(klass, state)
14
13
  checklist = ancestors_list(klass).inject([]) do |list, ancestor|
15
- list << item_for(obj, klass, ancestor)
16
- list << item_for(obj, klass, ancestor, :old_style => true)
14
+ list << item_for(klass, state, ancestor)
15
+ list << item_for(klass, state, ancestor, :old_style => true)
17
16
  list
18
17
  end
19
- translate_queue(checklist) || I18n.translate(checklist.shift, :default => obj.aasm_current_state.to_s.humanize)
18
+ translate_queue(checklist) || I18n.translate(checklist.shift, :default => state.to_s.humanize)
20
19
  end
21
20
 
22
21
  private
23
22
 
24
- def item_for(obj, klass, ancestor, options={})
23
+ def item_for(klass, state, ancestor, options={})
25
24
  separator = options[:old_style] ? '.' : '/'
26
- :"#{i18n_scope(klass)}.attributes.#{i18n_klass(ancestor)}.#{klass.aasm_column}#{separator}#{obj.aasm_current_state}"
25
+ :"#{i18n_scope(klass)}.attributes.#{i18n_klass(ancestor)}.#{klass.aasm_column}#{separator}#{state}"
27
26
  end
28
27
 
29
28
  def translate_queue(checklist)
@@ -3,8 +3,9 @@ module AASM
3
3
  class State
4
4
  attr_reader :name, :options
5
5
 
6
- def initialize(name, options={})
6
+ def initialize(name, clazz, options={})
7
7
  @name = name
8
+ @clazz = clazz
8
9
  update(options)
9
10
  end
10
11
 
@@ -24,6 +25,10 @@ module AASM
24
25
  end
25
26
  end
26
27
 
28
+ def to_s
29
+ name.to_s
30
+ end
31
+
27
32
  def fire_callbacks(action, record)
28
33
  action = @options[action]
29
34
  catch :halt_aasm_chain do
@@ -34,7 +39,17 @@ module AASM
34
39
  end
35
40
 
36
41
  def display_name
37
- @display_name ||= name.to_s.gsub(/_/, ' ').capitalize
42
+ @display_name ||= begin
43
+ if Module.const_defined?(:I18n)
44
+ localized_name
45
+ else
46
+ name.to_s.gsub(/_/, ' ').capitalize
47
+ end
48
+ end
49
+ end
50
+
51
+ def localized_name
52
+ AASM::SupportingClasses::Localizer.new.human_state_name(@clazz, self)
38
53
  end
39
54
 
40
55
  def for_select
@@ -1,3 +1,3 @@
1
1
  module AASM
2
- VERSION = "3.0.14"
2
+ VERSION = "3.0.15"
3
3
  end
@@ -6,4 +6,4 @@ en:
6
6
 
7
7
  attributes:
8
8
  localizer_test_model:
9
- aasm_state/open: "It's opened now!"
9
+ aasm_state/opened: "It's open now!"
@@ -7,4 +7,4 @@ en:
7
7
  attributes:
8
8
  localizer_test_model:
9
9
  aasm_state:
10
- open: "It's opened now!"
10
+ opened: "It's open now!"
@@ -0,0 +1,11 @@
1
+ class Argument
2
+ include AASM
3
+ aasm do
4
+ state :invalid, :initial => true
5
+ state :valid
6
+
7
+ event :valid do
8
+ transitions :to => :valid, :from => [:invalid]
9
+ end
10
+ end
11
+ end
@@ -6,10 +6,12 @@ class CallbackNewDsl
6
6
  :before_enter => :before_enter_open,
7
7
  :after_enter => :after_enter_open,
8
8
  :before_exit => :before_exit_open,
9
+ :exit => :exit_open,
9
10
  :after_exit => :after_exit_open
10
11
 
11
12
  state :closed,
12
13
  :before_enter => :before_enter_closed,
14
+ :enter => :enter_closed,
13
15
  :after_enter => :after_enter_closed,
14
16
  :before_exit => :before_exit_closed,
15
17
  :after_exit => :after_exit_closed
@@ -35,4 +37,7 @@ class CallbackNewDsl
35
37
 
36
38
  def before; end
37
39
  def after; end
40
+
41
+ def enter_closed; end
42
+ def exit_open; end
38
43
  end
@@ -4,14 +4,16 @@ class CallbackOldDsl
4
4
  aasm_initial_state :open
5
5
  aasm_state :open,
6
6
  :before_enter => :before_enter_open,
7
- :before_exit => :before_exit_open,
8
- :after_enter => :after_enter_open,
9
- :after_exit => :after_exit_open
7
+ :after_enter => :after_enter_open,
8
+ :before_exit => :before_exit_open,
9
+ :exit => :exit_open,
10
+ :after_exit => :after_exit_open
10
11
  aasm_state :closed,
11
12
  :before_enter => :before_enter_closed,
12
- :before_exit => :before_exit_closed,
13
- :after_enter => :after_enter_closed,
14
- :after_exit => :after_exit_closed
13
+ :enter => :enter_closed,
14
+ :after_enter => :after_enter_closed,
15
+ :before_exit => :before_exit_closed,
16
+ :after_exit => :after_exit_closed
15
17
 
16
18
  aasm_event :close, :before => :before, :after => :after do
17
19
  transitions :to => :closed, :from => [:open]
@@ -33,4 +35,7 @@ class CallbackOldDsl
33
35
 
34
36
  def before; end
35
37
  def after; end
38
+
39
+ def enter_closed; end
40
+ def exit_open; end
36
41
  end
@@ -0,0 +1,29 @@
1
+ class ParametrisedEvent
2
+ include AASM
3
+ aasm do
4
+ state :sleeping, :initial => true
5
+ state :showering
6
+ state :working
7
+ state :dating
8
+ state :prettying_up
9
+
10
+ event :wakeup do
11
+ transitions :from => :sleeping, :to => [:showering, :working]
12
+ end
13
+
14
+ event :dress do
15
+ transitions :from => :sleeping, :to => :working, :on_transition => :wear_clothes
16
+ transitions :from => :showering, :to => [:working, :dating], :on_transition => Proc.new { |obj, *args| obj.wear_clothes(*args) }
17
+ transitions :from => :showering, :to => :prettying_up, :on_transition => [:condition_hair, :fix_hair]
18
+ end
19
+ end
20
+
21
+ def wear_clothes(shirt_color, trouser_type)
22
+ end
23
+
24
+ def condition_hair
25
+ end
26
+
27
+ def fix_hair
28
+ end
29
+ end
@@ -51,31 +51,6 @@ end
51
51
  class Baz < Bar
52
52
  end
53
53
 
54
- class Banker
55
- include AASM
56
- aasm do
57
- state :retired
58
- state :selling_bad_mortgages
59
- end
60
- aasm_initial_state Proc.new { |banker| banker.rich? ? :retired : :selling_bad_mortgages }
61
- RICH = 1_000_000
62
- attr_accessor :balance
63
- def initialize(balance = 0); self.balance = balance; end
64
- def rich?; self.balance >= RICH; end
65
- end
66
-
67
- class Argument
68
- include AASM
69
- aasm do
70
- state :invalid, :initial => true
71
- state :valid
72
-
73
- event :valid do
74
- transitions :to => :valid, :from => [:invalid]
75
- end
76
- end
77
- end
78
-
79
54
  class ThisNameBetterNotBeInUse
80
55
  include AASM
81
56
 
@@ -87,33 +62,3 @@ class ThisNameBetterNotBeInUse
87
62
  state :proc
88
63
  end
89
64
  end
90
-
91
- class ChetanPatil
92
- include AASM
93
- aasm do
94
- state :sleeping, :initial => true
95
- state :showering
96
- state :working
97
- state :dating
98
- state :prettying_up
99
-
100
- event :wakeup do
101
- transitions :from => :sleeping, :to => [:showering, :working]
102
- end
103
-
104
- event :dress do
105
- transitions :from => :sleeping, :to => :working, :on_transition => :wear_clothes
106
- transitions :from => :showering, :to => [:working, :dating], :on_transition => Proc.new { |obj, *args| obj.wear_clothes(*args) }
107
- transitions :from => :showering, :to => :prettying_up, :on_transition => [:condition_hair, :fix_hair]
108
- end
109
- end
110
-
111
- def wear_clothes(shirt_color, trouser_type)
112
- end
113
-
114
- def condition_hair
115
- end
116
-
117
- def fix_hair
118
- end
119
- end
@@ -0,0 +1,116 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'callbacks for the old DSL' do
4
+ let(:callback) {CallbackOldDsl.new}
5
+
6
+ it "should get close callbacks" do
7
+ callback.should_receive(:exit_open).once.ordered
8
+ callback.should_receive(:before).once.ordered
9
+ callback.should_receive(:before_exit_open).once.ordered # these should be before the state changes
10
+ callback.should_receive(:before_enter_closed).once.ordered
11
+ callback.should_receive(:enter_closed).once.ordered
12
+ callback.should_receive(:aasm_write_state).once.ordered.and_return(true) # this is when the state changes
13
+ callback.should_receive(:after_exit_open).once.ordered # these should be after the state changes
14
+ callback.should_receive(:after_enter_closed).once.ordered
15
+ callback.should_receive(:after).once.ordered
16
+
17
+ callback.close!
18
+ end
19
+ end
20
+
21
+ describe 'callbacks for the new DSL' do
22
+ let(:callback) {CallbackNewDsl.new}
23
+
24
+ it "be called in order" do
25
+ callback.should_receive(:exit_open).once.ordered
26
+ callback.should_receive(:before).once.ordered
27
+ callback.should_receive(:before_exit_open).once.ordered # these should be before the state changes
28
+ callback.should_receive(:before_enter_closed).once.ordered
29
+ callback.should_receive(:enter_closed).once.ordered
30
+ callback.should_receive(:aasm_write_state).once.ordered.and_return(true) # this is when the state changes
31
+ callback.should_receive(:after_exit_open).once.ordered # these should be after the state changes
32
+ callback.should_receive(:after_enter_closed).once.ordered
33
+ callback.should_receive(:after).once.ordered
34
+
35
+ callback.close!
36
+ end
37
+ end
38
+
39
+ describe 'event callbacks' do
40
+ describe "with an error callback defined" do
41
+ before do
42
+ class Foo
43
+ aasm_event :safe_close, :success => :success_callback, :error => :error_callback do
44
+ transitions :to => :closed, :from => [:open]
45
+ end
46
+ end
47
+
48
+ @foo = Foo.new
49
+ end
50
+
51
+ it "should run error_callback if an exception is raised and error_callback defined" do
52
+ def @foo.error_callback(e); end
53
+
54
+ @foo.stub!(:enter).and_raise(e=StandardError.new)
55
+ @foo.should_receive(:error_callback).with(e)
56
+
57
+ @foo.safe_close!
58
+ end
59
+
60
+ it "should raise NoMethodError if exceptionis raised and error_callback is declared but not defined" do
61
+ @foo.stub!(:enter).and_raise(StandardError)
62
+ lambda{@foo.safe_close!}.should raise_error(NoMethodError)
63
+ end
64
+
65
+ it "should propagate an error if no error callback is declared" do
66
+ @foo.stub!(:enter).and_raise("Cannot enter safe")
67
+ lambda{@foo.close!}.should raise_error(StandardError, "Cannot enter safe")
68
+ end
69
+ end
70
+
71
+ describe "with aasm_event_fired defined" do
72
+ before do
73
+ @foo = Foo.new
74
+ def @foo.aasm_event_fired(event, from, to); end
75
+ end
76
+
77
+ it 'should call it for successful bang fire' do
78
+ @foo.should_receive(:aasm_event_fired).with(:close, :open, :closed)
79
+ @foo.close!
80
+ end
81
+
82
+ it 'should call it for successful non-bang fire' do
83
+ @foo.should_receive(:aasm_event_fired)
84
+ @foo.close
85
+ end
86
+
87
+ it 'should not call it for failing bang fire' do
88
+ @foo.stub!(:aasm_set_current_state_with_persistence).and_return(false)
89
+ @foo.should_not_receive(:aasm_event_fired)
90
+ @foo.close!
91
+ end
92
+ end
93
+
94
+ describe "with aasm_event_failed defined" do
95
+ before do
96
+ @foo = Foo.new
97
+ def @foo.aasm_event_failed(event, from); end
98
+ end
99
+
100
+ it 'should call it when transition failed for bang fire' do
101
+ @foo.should_receive(:aasm_event_failed).with(:null, :open)
102
+ lambda {@foo.null!}.should raise_error(AASM::InvalidTransition)
103
+ end
104
+
105
+ it 'should call it when transition failed for non-bang fire' do
106
+ @foo.should_receive(:aasm_event_failed).with(:null, :open)
107
+ lambda {@foo.null}.should raise_error(AASM::InvalidTransition)
108
+ end
109
+
110
+ it 'should not call it if persist fails for bang fire' do
111
+ @foo.stub!(:aasm_set_current_state_with_persistence).and_return(false)
112
+ @foo.should_receive(:aasm_event_failed)
113
+ @foo.close!
114
+ end
115
+ end
116
+ end