mylescarrick-aasm 2.1.5

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,109 @@
1
+ class AASM::SupportingClasses::Event
2
+ attr_reader :name, :success, :options
3
+
4
+ def initialize(name, options = {}, &block)
5
+ @name = name
6
+ @transitions = []
7
+ update(options, &block)
8
+ end
9
+
10
+ def fire(obj, to_state=nil, *args)
11
+ transitions = @transitions.select { |t| t.from == obj.aasm_current_state }
12
+ raise AASM::InvalidTransition, "Event '#{name}' cannot transition from '#{obj.aasm_current_state}'" if transitions.size == 0
13
+
14
+ next_state = nil
15
+ transitions.each do |transition|
16
+ next if to_state and !Array(transition.to).include?(to_state)
17
+ if transition.perform(obj)
18
+ next_state = to_state || Array(transition.to).first
19
+ transition.execute(obj, *args)
20
+ break
21
+ end
22
+ end
23
+ next_state
24
+ end
25
+
26
+ def transitions_from_state?(state)
27
+ @transitions.any? { |t| t.from == state }
28
+ end
29
+
30
+ def transitions_from_state(state)
31
+ @transitions.select { |t| t.from == state }
32
+ end
33
+
34
+ def all_transitions
35
+ @transitions
36
+ end
37
+
38
+ def call_action(action, record)
39
+ action = @options[action]
40
+ action.is_a?(Array) ?
41
+ action.each {|a| _call_action(a, record)} :
42
+ _call_action(action, record)
43
+ end
44
+
45
+ def ==(event)
46
+ if event.is_a? Symbol
47
+ name == event
48
+ else
49
+ name == event.name
50
+ end
51
+ end
52
+
53
+ def update(options = {}, &block)
54
+ if options.key?(:success) then
55
+ @success = options[:success]
56
+ end
57
+ if options.key?(:error) then
58
+ @error = options[:error]
59
+ end
60
+ if block then
61
+ instance_eval(&block)
62
+ end
63
+ @options = options
64
+ self
65
+ end
66
+
67
+ def execute_success_callback(obj, success = nil)
68
+ callback = success || @success
69
+ case(callback)
70
+ when String, Symbol
71
+ obj.send(callback)
72
+ when Proc
73
+ callback.call(obj)
74
+ when Array
75
+ callback.each{|meth|self.execute_success_callback(obj, meth)}
76
+ end
77
+ end
78
+
79
+ def execute_error_callback(obj, error, error_callback=nil)
80
+ callback = error_callback || @error
81
+ raise error unless callback
82
+ case(callback)
83
+ when String, Symbol
84
+ raise NoMethodError unless obj.respond_to?(callback.to_sym)
85
+ obj.send(callback, error)
86
+ when Proc
87
+ callback.call(obj, error)
88
+ when Array
89
+ callback.each{|meth|self.execute_error_callback(obj, error, meth)}
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def _call_action(action, record)
96
+ case action
97
+ when Symbol, String
98
+ record.send(action)
99
+ when Proc
100
+ action.call(record)
101
+ end
102
+ end
103
+
104
+ def transitions(trans_opts)
105
+ Array(trans_opts[:from]).each do |s|
106
+ @transitions << AASM::SupportingClasses::StateTransition.new(trans_opts.merge({:from => s.to_sym}))
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,14 @@
1
+ module AASM::Persistence
2
+
3
+ # Checks to see this class or any of it's superclasses inherit from
4
+ # ActiveRecord::Base and if so includes ActiveRecordPersistence
5
+ def self.set_persistence(base)
6
+ # Use a fancier auto-loading thingy, perhaps. When there are more persistence engines.
7
+ hierarchy = base.ancestors.map {|klass| klass.to_s}
8
+
9
+ if hierarchy.include?("ActiveRecord::Base")
10
+ require File.join(File.dirname(__FILE__), 'persistence', 'active_record_persistence')
11
+ base.send(:include, AASM::Persistence::ActiveRecordPersistence)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,246 @@
1
+ module AASM
2
+ module Persistence
3
+ module ActiveRecordPersistence
4
+ # This method:
5
+ #
6
+ # * extends the model with ClassMethods
7
+ # * includes InstanceMethods
8
+ #
9
+ # Unless the corresponding methods are already defined, it includes
10
+ # * ReadState
11
+ # * WriteState
12
+ # * WriteStateWithoutPersistence
13
+ #
14
+ # Adds
15
+ #
16
+ # before_validation :aasm_ensure_initial_state, :on => :create
17
+ #
18
+ # As a result, it doesn't matter when you define your methods - the following 2 are equivalent
19
+ #
20
+ # class Foo < ActiveRecord::Base
21
+ # def aasm_write_state(state)
22
+ # "bar"
23
+ # end
24
+ # include AASM
25
+ # end
26
+ #
27
+ # class Foo < ActiveRecord::Base
28
+ # include AASM
29
+ # def aasm_write_state(state)
30
+ # "bar"
31
+ # end
32
+ # end
33
+ #
34
+ def self.included(base)
35
+ base.extend AASM::Persistence::ActiveRecordPersistence::ClassMethods
36
+ base.send(:include, AASM::Persistence::ActiveRecordPersistence::InstanceMethods)
37
+ base.send(:include, AASM::Persistence::ActiveRecordPersistence::ReadState) unless base.method_defined?(:aasm_read_state)
38
+ base.send(:include, AASM::Persistence::ActiveRecordPersistence::WriteState) unless base.method_defined?(:aasm_write_state)
39
+ base.send(:include, AASM::Persistence::ActiveRecordPersistence::WriteStateWithoutPersistence) unless base.method_defined?(:aasm_write_state_without_persistence)
40
+
41
+ if base.respond_to?(:named_scope)
42
+ base.extend(AASM::Persistence::ActiveRecordPersistence::NamedScopeMethods)
43
+
44
+ base.class_eval do
45
+ class << self
46
+ unless method_defined?(:aasm_state_without_named_scope)
47
+ alias_method :aasm_state_without_named_scope, :aasm_state
48
+ alias_method :aasm_state, :aasm_state_with_named_scope
49
+ end
50
+ end
51
+ end
52
+ end
53
+ base.respond_to?(:before_validation_on_create) ? base.before_validation_on_create(:aasm_ensure_initial_state) : base.before_validation(:aasm_ensure_initial_state, :on => :create)
54
+ end
55
+
56
+ module ClassMethods
57
+ # Maps to the aasm_column in the database. Deafults to "aasm_state". You can write:
58
+ #
59
+ # create_table :foos do |t|
60
+ # t.string :name
61
+ # t.string :aasm_state
62
+ # end
63
+ #
64
+ # class Foo < ActiveRecord::Base
65
+ # include AASM
66
+ # end
67
+ #
68
+ # OR:
69
+ #
70
+ # create_table :foos do |t|
71
+ # t.string :name
72
+ # t.string :status
73
+ # end
74
+ #
75
+ # class Foo < ActiveRecord::Base
76
+ # include AASM
77
+ # aasm_column :status
78
+ # end
79
+ #
80
+ # This method is both a getter and a setter
81
+ def aasm_column(column_name=nil)
82
+ if column_name
83
+ AASM::StateMachine[self].config.column = column_name.to_sym
84
+ # @aasm_column = column_name.to_sym
85
+ else
86
+ AASM::StateMachine[self].config.column ||= :aasm_state
87
+ # @aasm_column ||= :aasm_state
88
+ end
89
+ # @aasm_column
90
+ AASM::StateMachine[self].config.column
91
+ end
92
+
93
+ def find_in_state(number, state, *args)
94
+ with_state_scope state do
95
+ find(number, *args)
96
+ end
97
+ end
98
+
99
+ def count_in_state(state, *args)
100
+ with_state_scope state do
101
+ count(*args)
102
+ end
103
+ end
104
+
105
+ def calculate_in_state(state, *args)
106
+ with_state_scope state do
107
+ calculate(*args)
108
+ end
109
+ end
110
+
111
+ protected
112
+ def with_state_scope(state)
113
+ with_scope :find => {:conditions => ["#{table_name}.#{aasm_column} = ?", state.to_s]} do
114
+ yield if block_given?
115
+ end
116
+ end
117
+ end
118
+
119
+ module InstanceMethods
120
+
121
+ # Returns the current aasm_state of the object. Respects reload and
122
+ # any changes made to the aasm_state field directly
123
+ #
124
+ # Internally just calls <tt>aasm_read_state</tt>
125
+ #
126
+ # foo = Foo.find(1)
127
+ # foo.aasm_current_state # => :pending
128
+ # foo.aasm_state = "opened"
129
+ # foo.aasm_current_state # => :opened
130
+ # foo.close # => calls aasm_write_state_without_persistence
131
+ # foo.aasm_current_state # => :closed
132
+ # foo.reload
133
+ # foo.aasm_current_state # => :pending
134
+ #
135
+ def aasm_current_state
136
+ @current_state = aasm_read_state
137
+ end
138
+
139
+ private
140
+
141
+ # Ensures that if the aasm_state column is nil and the record is new
142
+ # that the initial state gets populated before validation on create
143
+ #
144
+ # foo = Foo.new
145
+ # foo.aasm_state # => nil
146
+ # foo.valid?
147
+ # foo.aasm_state # => "open" (where :open is the initial state)
148
+ #
149
+ #
150
+ # foo = Foo.find(:first)
151
+ # foo.aasm_state # => 1
152
+ # foo.aasm_state = nil
153
+ # foo.valid?
154
+ # foo.aasm_state # => nil
155
+ #
156
+ def aasm_ensure_initial_state
157
+ send("#{self.class.aasm_column}=", self.aasm_enter_initial_state.to_s) if send(self.class.aasm_column).blank?
158
+ end
159
+
160
+ end
161
+
162
+ module WriteStateWithoutPersistence
163
+ # Writes <tt>state</tt> to the state column, but does not persist it to the database
164
+ #
165
+ # foo = Foo.find(1)
166
+ # foo.aasm_current_state # => :opened
167
+ # foo.close
168
+ # foo.aasm_current_state # => :closed
169
+ # Foo.find(1).aasm_current_state # => :opened
170
+ # foo.save
171
+ # foo.aasm_current_state # => :closed
172
+ # Foo.find(1).aasm_current_state # => :closed
173
+ #
174
+ # NOTE: intended to be called from an event
175
+ def aasm_write_state_without_persistence(state)
176
+ write_attribute(self.class.aasm_column, state.to_s)
177
+ end
178
+ end
179
+
180
+ module WriteState
181
+ # Writes <tt>state</tt> to the state column and persists it to the database
182
+ #
183
+ # foo = Foo.find(1)
184
+ # foo.aasm_current_state # => :opened
185
+ # foo.close!
186
+ # foo.aasm_current_state # => :closed
187
+ # Foo.find(1).aasm_current_state # => :closed
188
+ #
189
+ # NOTE: intended to be called from an event
190
+ def aasm_write_state(state)
191
+ old_value = read_attribute(self.class.aasm_column)
192
+ write_attribute(self.class.aasm_column, state.to_s)
193
+
194
+ unless self.save
195
+ write_attribute(self.class.aasm_column, old_value)
196
+ return false
197
+ end
198
+
199
+ true
200
+ end
201
+ end
202
+
203
+ module ReadState
204
+
205
+ # Returns the value of the aasm_column - called from <tt>aasm_current_state</tt>
206
+ #
207
+ # If it's a new record, and the aasm state column is blank it returns the initial state:
208
+ #
209
+ # class Foo < ActiveRecord::Base
210
+ # include AASM
211
+ # aasm_column :status
212
+ # aasm_state :opened
213
+ # aasm_state :closed
214
+ # end
215
+ #
216
+ # foo = Foo.new
217
+ # foo.current_state # => :opened
218
+ # foo.close
219
+ # foo.current_state # => :closed
220
+ #
221
+ # foo = Foo.find(1)
222
+ # foo.current_state # => :opened
223
+ # foo.aasm_state = nil
224
+ # foo.current_state # => nil
225
+ #
226
+ # NOTE: intended to be called from an event
227
+ #
228
+ # This allows for nil aasm states - be sure to add validation to your model
229
+ def aasm_read_state
230
+ if new_record?
231
+ send(self.class.aasm_column).blank? ? aasm_determine_state_name(self.class.aasm_initial_state) : send(self.class.aasm_column).to_sym
232
+ else
233
+ send(self.class.aasm_column).nil? ? nil : send(self.class.aasm_column).to_sym
234
+ end
235
+ end
236
+ end
237
+
238
+ module NamedScopeMethods
239
+ def aasm_state_with_named_scope name, options = {}
240
+ aasm_state_without_named_scope name, options
241
+ self.named_scope name, :conditions => { "#{table_name}.#{self.aasm_column}" => name.to_s} unless self.respond_to?(name)
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,53 @@
1
+ class AASM::SupportingClasses::State
2
+ attr_reader :name, :options
3
+
4
+ def initialize(name, options={})
5
+ @name = name
6
+ update(options)
7
+ end
8
+
9
+ def ==(state)
10
+ if state.is_a? Symbol
11
+ name == state
12
+ else
13
+ name == state.name
14
+ end
15
+ end
16
+
17
+ def call_action(action, record)
18
+ action = @options[action]
19
+ catch :halt_aasm_chain do
20
+ action.is_a?(Array) ?
21
+ action.each {|a| _call_action(a, record)} :
22
+ _call_action(action, record)
23
+ end
24
+ end
25
+
26
+ def display_name
27
+ @display_name ||= name.to_s.gsub(/_/, ' ').capitalize
28
+ end
29
+
30
+ def for_select
31
+ [display_name, name.to_s]
32
+ end
33
+
34
+ def update(options = {})
35
+ if options.key?(:display) then
36
+ @display_name = options.delete(:display)
37
+ end
38
+ @options = options
39
+ self
40
+ end
41
+
42
+ private
43
+
44
+ def _call_action(action, record)
45
+ case action
46
+ when Symbol, String
47
+ record.send(action)
48
+ when Proc
49
+ action.call(record)
50
+ end
51
+ end
52
+
53
+ end
@@ -0,0 +1,32 @@
1
+ class AASM::StateMachine
2
+ def self.[](*args)
3
+ (@machines ||= {})[args]
4
+ end
5
+
6
+ def self.[]=(*args)
7
+ val = args.pop
8
+ (@machines ||= {})[args] = val
9
+ end
10
+
11
+ attr_accessor :states, :events, :initial_state, :config
12
+ attr_reader :name
13
+
14
+ def initialize(name)
15
+ @name = name
16
+ @initial_state = nil
17
+ @states = []
18
+ @events = {}
19
+ @config = OpenStruct.new
20
+ end
21
+
22
+ def clone
23
+ klone = super
24
+ klone.states = states.clone
25
+ klone.events = events.clone
26
+ klone
27
+ end
28
+
29
+ def create_state(name, options)
30
+ @states << AASM::SupportingClasses::State.new(name, options) unless @states.include?(name)
31
+ end
32
+ end