aasm 3.4.0 → 4.0.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.
@@ -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