aasm 3.0.14 → 3.0.15

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.
@@ -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