aasm 3.4.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,51 @@
1
+ callbacks
2
+
3
+ AASM 3
4
+
5
+ begin
6
+ old_state exit # old? should be deprecated -> use old_state.before_exit instead
7
+ event before
8
+ old_state before_exit
9
+ new_state before_enter
10
+ new_state enter # old? should be deprecated -> use new_state.before_enter instead
11
+ ...update state...
12
+ transition guard
13
+ event guard
14
+ transition on_transition
15
+ event success # if persist successful
16
+ old_state after_exit
17
+ new_state after_enter
18
+ event after
19
+ rescue
20
+ event error
21
+ end
22
+
23
+ AASM 4
24
+
25
+ todo
26
+
27
+ done
28
+ - move event.before before everything else
29
+ - move old_state.before_exit before old_state.exit
30
+ - move event.guard before transition.guard
31
+ - fire guards before running state callbacks (test run)
32
+
33
+ begin
34
+ event before
35
+ event guard # test run
36
+ transition guard # test run
37
+ old_state before_exit
38
+ old_state exit
39
+ new_state before_enter
40
+ new_state enter
41
+ event guard
42
+ transition guard
43
+ transition on_transition
44
+ ...update state...
45
+ event success # if persist successful
46
+ old_state after_exit
47
+ new_state after_enter
48
+ event after
49
+ rescue
50
+ event error
51
+ end
@@ -3,7 +3,9 @@ require 'ostruct'
3
3
  %w(
4
4
  version
5
5
  errors
6
+ configuration
6
7
  base
8
+ dsl_helper
7
9
  instance_base
8
10
  transition
9
11
  event
@@ -26,59 +26,6 @@ module AASM
26
26
  @aasm
27
27
  end
28
28
 
29
- # TODO remove this method in v4.0.0
30
- def aasm_initial_state(set_state=nil)
31
- if set_state
32
- warn ".aasm_initial_state(:name) is deprecated and will be removed in version 4.0.0; please use .aasm.initial_state = :name instead!"
33
- AASM::StateMachine[self].initial_state = set_state
34
- else
35
- warn ".aasm_initial_state is deprecated and will be removed in version 4.0.0; please use .aasm.initial_state instead!"
36
- AASM::StateMachine[self].initial_state
37
- end
38
- end
39
-
40
- # TODO remove this method in v4.0.0
41
- def aasm_from_states_for_state(state, options={})
42
- warn ".aasm_from_states_for_state is deprecated and will be removed in version 4.0.0; please use .aasm.from_states_for_state instead!"
43
- aasm.from_states_for_state(state, options)
44
- end
45
-
46
- # TODO remove this method in v4.0.0
47
- def aasm_initial_state=(state)
48
- warn ".aasm_initial_state= is deprecated and will be removed in version 4.0.0"
49
- AASM::StateMachine[self].initial_state = state
50
- end
51
-
52
- # TODO remove this method in v4.0.0
53
- def aasm_state(name, options={})
54
- warn ".aasm_state is deprecated and will be removed in version 4.0.0; please use .aasm.state instead!"
55
- aasm.state(name, options)
56
- end
57
-
58
- # TODO remove this method in v4.0.0
59
- def aasm_event(name, options = {}, &block)
60
- warn ".aasm_event is deprecated and will be removed in version 4.0.0; please use .aasm.event instead!"
61
- aasm.event(name, options, &block)
62
- end
63
-
64
- # TODO remove this method in v4.0.0
65
- def aasm_states
66
- warn ".aasm_states is deprecated and will be removed in version 4.0.0; please use .aasm.states instead!"
67
- aasm.states
68
- end
69
-
70
- # TODO remove this method in v4.0.0
71
- def aasm_events
72
- warn ".aasm_events is deprecated and will be removed in version 4.0.0; please use .aasm.events instead!"
73
- aasm.events
74
- end
75
-
76
- # TODO remove this method in v4.0.0
77
- def aasm_states_for_select
78
- warn ".aasm_states_for_select is deprecated and will be removed in version 4.0.0; please use .aasm.states_for_select instead!"
79
- aasm.states_for_select
80
- end
81
-
82
29
  # aasm.event(:event_name).human?
83
30
  def aasm_human_event_name(event) # event_name?
84
31
  AASM::Localizer.new.human_event_name(self, event)
@@ -107,55 +54,43 @@ module AASM
107
54
  true
108
55
  end
109
56
 
110
- # TODO remove this method in v4.0.0
111
- def aasm_current_state
112
- warn "#aasm_current_state is deprecated and will be removed in version 4.0.0; please use #aasm.current_state instead!"
113
- aasm.current_state
114
- end
115
-
116
- # TODO remove this method in v4.0.0
117
- def aasm_enter_initial_state
118
- warn "#aasm_enter_initial_state is deprecated and will be removed in version 4.0.0; please use #aasm.enter_initial_state instead!"
119
- aasm.enter_initial_state
120
- end
121
-
122
- # TODO remove this method in v4.0.0
123
- def aasm_events_for_current_state
124
- warn "#aasm_events_for_current_state is deprecated and will be removed in version 4.0.0; please use #aasm.events(aasm.current_state) instead!"
125
- aasm.events(aasm.current_state)
126
- end
127
-
128
- # TODO remove this method in v4.0.0
129
- def aasm_permissible_events_for_current_state
130
- warn "#aasm_permissible_events_for_current_state is deprecated and will be removed in version 4.0.0; please use #aasm.permissible_events instead!"
131
- aasm.permissible_events
132
- end
133
-
134
- # TODO remove this method in v4.0.0
135
- def aasm_events_for_state(state_name)
136
- warn "#aasm_events_for_state(state_name) is deprecated and will be removed in version 4.0.0; please use #aasm.events(state_name) instead!"
137
- aasm.events(state_name)
138
- end
57
+ private
139
58
 
140
- # TODO remove this method in v4.0.0
141
- def aasm_human_state
142
- warn "#aasm_human_state is deprecated and will be removed in version 4.0.0; please use #aasm.human_state instead!"
143
- aasm.human_state
59
+ # Takes args and a from state and removes the first
60
+ # element from args if it is a valid to_state for
61
+ # the event given the from_state
62
+ def process_args(event, from_state, *args)
63
+ # If the first arg doesn't respond to to_sym then
64
+ # it isn't a symbol or string so it can't be a state
65
+ # name anyway
66
+ return args unless args.first.respond_to?(:to_sym)
67
+ if event.transitions_from_state(from_state).map(&:to).flatten.include?(args.first)
68
+ return args[1..-1]
69
+ end
70
+ return args
144
71
  end
145
72
 
146
- private
147
-
148
73
  def aasm_fire_event(event_name, options, *args, &block)
149
- event = self.class.aasm.events[event_name]
74
+ event = self.class.aasm.state_machine.events[event_name]
150
75
  begin
151
76
  old_state = aasm.state_object_for_name(aasm.current_state)
152
- old_state.fire_callbacks(:exit, self)
153
77
 
154
78
  # new event before callback
155
- event.fire_callbacks(:before, self)
156
-
157
- if new_state_name = event.fire(self, *args)
158
- aasm_fired(event, old_state, new_state_name, options, &block)
79
+ event.fire_callbacks(
80
+ :before,
81
+ self,
82
+ *process_args(event, aasm.current_state, *args)
83
+ )
84
+
85
+ if event.may_fire?(self, *args)
86
+ old_state.fire_callbacks(:before_exit, self)
87
+ old_state.fire_callbacks(:exit, self) # TODO: remove for AASM 4?
88
+
89
+ if new_state_name = event.fire(self, *args)
90
+ aasm_fired(event, old_state, new_state_name, options, *args, &block)
91
+ else
92
+ aasm_failed(event_name, old_state)
93
+ end
159
94
  else
160
95
  aasm_failed(event_name, old_state)
161
96
  end
@@ -164,16 +99,14 @@ private
164
99
  end
165
100
  end
166
101
 
167
- def aasm_fired(event, old_state, new_state_name, options)
102
+ def aasm_fired(event, old_state, new_state_name, options, *args)
168
103
  persist = options[:persist]
169
104
 
170
105
  new_state = aasm.state_object_for_name(new_state_name)
171
106
 
172
- # new before_ callbacks
173
- old_state.fire_callbacks(:before_exit, self)
174
107
  new_state.fire_callbacks(:before_enter, self)
175
108
 
176
- new_state.fire_callbacks(:enter, self)
109
+ new_state.fire_callbacks(:enter, self) # TODO: remove for AASM 4?
177
110
 
178
111
  persist_successful = true
179
112
  if persist
@@ -190,7 +123,11 @@ private
190
123
  if persist_successful
191
124
  old_state.fire_callbacks(:after_exit, self)
192
125
  new_state.fire_callbacks(:after_enter, self)
193
- event.fire_callbacks(:after, self)
126
+ event.fire_callbacks(
127
+ :after,
128
+ self,
129
+ *process_args(event, old_state.name, *args)
130
+ )
194
131
 
195
132
  self.aasm_event_fired(event.name, old_state.name, aasm.current_state) if self.respond_to?(:aasm_event_fired)
196
133
  else
@@ -1,10 +1,13 @@
1
1
  module AASM
2
2
  class Base
3
3
 
4
+ attr_reader :state_machine
5
+
4
6
  def initialize(klass, options={}, &block)
5
7
  @klass = klass
6
8
  @state_machine = AASM::StateMachine[@klass]
7
- @state_machine.config.column = options[:column].to_sym if options[:column]
9
+ @state_machine.config.column ||= (options[:column] || :aasm_state).to_sym # aasm4
10
+ # @state_machine.config.column = options[:column].to_sym if options[:column] # master
8
11
  @options = options
9
12
 
10
13
  # let's cry if the transition is invalid
@@ -13,13 +16,22 @@ module AASM
13
16
  # create named scopes for each state
14
17
  configure :create_scopes, true
15
18
 
16
- # don't store any new state if the model is invalid
19
+ # don't store any new state if the model is invalid (in ActiveRecord)
17
20
  configure :skip_validation_on_save, false
18
21
 
19
- # use requires_new for nested transactions
22
+ # use requires_new for nested transactions (in ActiveRecord)
20
23
  configure :requires_new_transaction, true
21
24
 
25
+ # set to true to forbid direct assignment of aasm_state column (in ActiveRecord)
26
+ configure :no_direct_assignment, false
27
+
22
28
  configure :enum, nil
29
+
30
+ if @state_machine.config.no_direct_assignment
31
+ @klass.send(:define_method, "#{@state_machine.config.column}=") do |state_name|
32
+ raise AASM::NoDirectAssignmentError.new('direct assignment of AASM column has been disabled (see AASM configuration for this class)')
33
+ end
34
+ end
23
35
  end
24
36
 
25
37
  def initial_state(new_initial_state=nil)
@@ -70,7 +82,7 @@ module AASM
70
82
  end
71
83
 
72
84
  def events
73
- @state_machine.events
85
+ @state_machine.events.values
74
86
  end
75
87
 
76
88
  def states_for_select
@@ -79,16 +91,15 @@ module AASM
79
91
 
80
92
  def from_states_for_state(state, options={})
81
93
  if options[:transition]
82
- events[options[:transition]].transitions_to_state(state).flatten.map(&:from).flatten
94
+ @state_machine.events[options[:transition]].transitions_to_state(state).flatten.map(&:from).flatten
83
95
  else
84
- events.map {|k,v| v.transitions_to_state(state)}.flatten.map(&:from).flatten
96
+ events.map {|e| e.transitions_to_state(state)}.flatten.map(&:from).flatten
85
97
  end
86
98
  end
87
99
 
88
100
  private
89
101
 
90
102
  def configure(key, default_value)
91
- @state_machine.config.send(:new_ostruct_member, key)
92
103
  if @options.key?(key)
93
104
  @state_machine.config.send("#{key}=", @options[key])
94
105
  elsif @state_machine.config.send(key).nil?
@@ -0,0 +1,23 @@
1
+ module AASM
2
+ class Configuration
3
+ # for all persistence layers: which database column to use?
4
+ attr_accessor :column
5
+
6
+ # let's cry if the transition is invalid
7
+ attr_accessor :whiny_transitions
8
+
9
+ # for all persistence layers: create named scopes for each state
10
+ attr_accessor :create_scopes
11
+
12
+ # for ActiveRecord: don't store any new state if the model is invalid
13
+ attr_accessor :skip_validation_on_save
14
+
15
+ # for ActiveRecord: use requires_new for nested transactions?
16
+ attr_accessor :requires_new_transaction
17
+
18
+ # forbid direct assignment in aasm_state column (in ActiveRecord)
19
+ attr_accessor :no_direct_assignment
20
+
21
+ attr_accessor :enum
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ module DslHelper
2
+
3
+ class Proxy
4
+ attr_accessor :options
5
+
6
+ def initialize(options, valid_keys, source)
7
+ @valid_keys = valid_keys
8
+ @source = source
9
+
10
+ @options = options
11
+ end
12
+
13
+ def method_missing(name, *args, &block)
14
+ if @valid_keys.include?(name)
15
+ options[name] = Array(options[name])
16
+ options[name] << block if block
17
+ options[name] += Array(args)
18
+ else
19
+ @source.send name, *args, &block
20
+ end
21
+ end
22
+ end
23
+
24
+ def add_options_from_dsl(options, valid_keys, &block)
25
+ proxy = Proxy.new(options, valid_keys, self)
26
+ proxy.instance_eval(&block)
27
+ proxy.options
28
+ end
29
+
30
+ end
@@ -1,4 +1,5 @@
1
1
  module AASM
2
2
  class InvalidTransition < RuntimeError; end
3
3
  class UndefinedState < RuntimeError; end
4
+ class NoDirectAssignmentError < RuntimeError; end
4
5
  end
@@ -1,13 +1,18 @@
1
1
  module AASM
2
2
  class Event
3
+ include DslHelper
3
4
 
4
5
  attr_reader :name, :options
5
6
 
6
7
  def initialize(name, options = {}, &block)
7
8
  @name = name
8
9
  @transitions = []
9
- @guards = Array(options[:guard] || options[:guards])
10
- update(options, &block)
10
+ @guards = Array(options[:guard] || options[:guards] || options[:if])
11
+ @unless = Array(options[:unless]) #TODO: This could use a better name
12
+
13
+ # from aasm4
14
+ @options = options # QUESTION: .dup ?
15
+ add_options_from_dsl(@options, [:after, :before, :error, :success], &block) if block
11
16
  end
12
17
 
13
18
  # a neutered version of fire - it doesn't actually fire the event, it just
@@ -37,13 +42,9 @@ module AASM
37
42
  @transitions.select { |t| t.to == state }
38
43
  end
39
44
 
40
- # TODO remove this method in v4.0.0
41
- def all_transitions
42
- warn "Event#all_transitions is deprecated and will be removed in version 4.0.0; please use Event#transitions instead!"
43
- transitions
44
- end
45
-
46
45
  def fire_callbacks(callback_name, record, *args)
46
+ # strip out the first element in args if it's a valid to_state
47
+ # #given where we're coming from, this condition implies args not empty
47
48
  invoke_callbacks(@options[callback_name], record, args)
48
49
  end
49
50
 
@@ -74,18 +75,14 @@ module AASM
74
75
 
75
76
  def attach_event_guards(definitions)
76
77
  unless @guards.empty?
77
- given_guards = Array(definitions.delete(:guard) || definitions.delete(:guards))
78
- definitions[:guards] = given_guards + @guards
78
+ given_guards = Array(definitions.delete(:guard) || definitions.delete(:guards) || definitions.delete(:if))
79
+ definitions[:guards] = @guards + given_guards # from aasm4
79
80
  end
80
- definitions
81
- end
82
-
83
- def update(options = {}, &block)
84
- @options = options
85
- if block then
86
- instance_eval(&block)
81
+ unless @unless.empty?
82
+ given_unless = Array(definitions.delete(:unless))
83
+ definitions[:unless] = given_unless + @unless
87
84
  end
88
- self
85
+ definitions
89
86
  end
90
87
 
91
88
  # Execute if test == false, otherwise return true/false depending on whether it would fire
@@ -98,9 +95,18 @@ module AASM
98
95
  transitions = @transitions
99
96
  end
100
97
 
98
+ # If to_state is not nil it either contains a potential
99
+ # to_state or an arg
100
+ unless to_state == nil
101
+ if !to_state.respond_to?(:to_sym) || !transitions.map(&:to).flatten.include?(to_state.to_sym)
102
+ args.unshift(to_state)
103
+ to_state = nil
104
+ end
105
+ end
106
+
101
107
  transitions.each do |transition|
102
108
  next if to_state and !Array(transition.to).include?(to_state)
103
- if transition.perform(obj, *args)
109
+ if transition.allowed?(obj, *args)
104
110
  if test
105
111
  result = true
106
112
  else
@@ -130,12 +136,5 @@ module AASM
130
136
  end
131
137
  end
132
138
 
133
- [:after, :before, :error, :success].each do |callback_name|
134
- define_method callback_name do |*args, &block|
135
- options[callback_name] = Array(options[callback_name])
136
- options[callback_name] << block if block
137
- options[callback_name] += Array(args)
138
- end
139
- end
140
139
  end
141
140
  end # AASM