r38y-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,20 @@
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
+ elsif hierarchy.include?("Mongoid::Document")
13
+ require File.join(File.dirname(__FILE__), 'persistence', 'mongoid_persistence')
14
+ base.send(:include, AASM::Persistence::MongoidPersistence)
15
+ elsif hierarchy.include?("MongoMapper::Document")
16
+ require File.join(File.dirname(__FILE__), 'persistence', 'mongo_mapper_persistence')
17
+ base.send(:include, AASM::Persistence::MongoMapperPersistence)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,248 @@
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_on_create :aasm_ensure_initial_state
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
+
54
+ base.before_validation_on_create :aasm_ensure_initial_state
55
+ end
56
+
57
+ module ClassMethods
58
+ # Maps to the aasm_column in the database. Deafults to "aasm_state". You can write:
59
+ #
60
+ # create_table :foos do |t|
61
+ # t.string :name
62
+ # t.string :aasm_state
63
+ # end
64
+ #
65
+ # class Foo < ActiveRecord::Base
66
+ # include AASM
67
+ # end
68
+ #
69
+ # OR:
70
+ #
71
+ # create_table :foos do |t|
72
+ # t.string :name
73
+ # t.string :status
74
+ # end
75
+ #
76
+ # class Foo < ActiveRecord::Base
77
+ # include AASM
78
+ # aasm_column :status
79
+ # end
80
+ #
81
+ # This method is both a getter and a setter
82
+ def aasm_column(column_name=nil)
83
+ if column_name
84
+ AASM::StateMachine[self].config.column = column_name.to_sym
85
+ # @aasm_column = column_name.to_sym
86
+ else
87
+ AASM::StateMachine[self].config.column ||= :aasm_state
88
+ # @aasm_column ||= :aasm_state
89
+ end
90
+ # @aasm_column
91
+ AASM::StateMachine[self].config.column
92
+ end
93
+
94
+ def find_in_state(number, state, *args)
95
+ with_state_scope state do
96
+ find(number, *args)
97
+ end
98
+ end
99
+
100
+ def count_in_state(state, *args)
101
+ with_state_scope state do
102
+ count(*args)
103
+ end
104
+ end
105
+
106
+ def calculate_in_state(state, *args)
107
+ with_state_scope state do
108
+ calculate(*args)
109
+ end
110
+ end
111
+
112
+ protected
113
+ def with_state_scope(state)
114
+ with_scope :find => {:conditions => ["#{table_name}.#{aasm_column} = ?", state.to_s]} do
115
+ yield if block_given?
116
+ end
117
+ end
118
+ end
119
+
120
+ module InstanceMethods
121
+
122
+ # Returns the current aasm_state of the object. Respects reload and
123
+ # any changes made to the aasm_state field directly
124
+ #
125
+ # Internally just calls <tt>aasm_read_state</tt>
126
+ #
127
+ # foo = Foo.find(1)
128
+ # foo.aasm_current_state # => :pending
129
+ # foo.aasm_state = "opened"
130
+ # foo.aasm_current_state # => :opened
131
+ # foo.close # => calls aasm_write_state_without_persistence
132
+ # foo.aasm_current_state # => :closed
133
+ # foo.reload
134
+ # foo.aasm_current_state # => :pending
135
+ #
136
+ def aasm_current_state
137
+ @current_state = aasm_read_state
138
+ end
139
+
140
+ private
141
+
142
+ # Ensures that if the aasm_state column is nil and the record is new
143
+ # that the initial state gets populated before validation on create
144
+ #
145
+ # foo = Foo.new
146
+ # foo.aasm_state # => nil
147
+ # foo.valid?
148
+ # foo.aasm_state # => "open" (where :open is the initial state)
149
+ #
150
+ #
151
+ # foo = Foo.find(:first)
152
+ # foo.aasm_state # => 1
153
+ # foo.aasm_state = nil
154
+ # foo.valid?
155
+ # foo.aasm_state # => nil
156
+ #
157
+ def aasm_ensure_initial_state
158
+ send("#{self.class.aasm_column}=", self.aasm_enter_initial_state.to_s) if send(self.class.aasm_column).blank?
159
+ end
160
+
161
+ end
162
+
163
+ module WriteStateWithoutPersistence
164
+ # Writes <tt>state</tt> to the state column, but does not persist it to the database
165
+ #
166
+ # foo = Foo.find(1)
167
+ # foo.aasm_current_state # => :opened
168
+ # foo.close
169
+ # foo.aasm_current_state # => :closed
170
+ # Foo.find(1).aasm_current_state # => :opened
171
+ # foo.save
172
+ # foo.aasm_current_state # => :closed
173
+ # Foo.find(1).aasm_current_state # => :closed
174
+ #
175
+ # NOTE: intended to be called from an event
176
+ def aasm_write_state_without_persistence(state)
177
+ write_attribute(self.class.aasm_column, state.to_s)
178
+ end
179
+ end
180
+
181
+ module WriteState
182
+ # Writes <tt>state</tt> to the state column and persists it to the database
183
+ # using update_attribute (which bypasses validation)
184
+ #
185
+ # foo = Foo.find(1)
186
+ # foo.aasm_current_state # => :opened
187
+ # foo.close!
188
+ # foo.aasm_current_state # => :closed
189
+ # Foo.find(1).aasm_current_state # => :closed
190
+ #
191
+ # NOTE: intended to be called from an event
192
+ def aasm_write_state(state)
193
+ old_value = read_attribute(self.class.aasm_column)
194
+ write_attribute(self.class.aasm_column, state.to_s)
195
+
196
+ unless self.save(false)
197
+ write_attribute(self.class.aasm_column, old_value)
198
+ return false
199
+ end
200
+
201
+ true
202
+ end
203
+ end
204
+
205
+ module ReadState
206
+
207
+ # Returns the value of the aasm_column - called from <tt>aasm_current_state</tt>
208
+ #
209
+ # If it's a new record, and the aasm state column is blank it returns the initial state:
210
+ #
211
+ # class Foo < ActiveRecord::Base
212
+ # include AASM
213
+ # aasm_column :status
214
+ # aasm_state :opened
215
+ # aasm_state :closed
216
+ # end
217
+ #
218
+ # foo = Foo.new
219
+ # foo.current_state # => :opened
220
+ # foo.close
221
+ # foo.current_state # => :closed
222
+ #
223
+ # foo = Foo.find(1)
224
+ # foo.current_state # => :opened
225
+ # foo.aasm_state = nil
226
+ # foo.current_state # => nil
227
+ #
228
+ # NOTE: intended to be called from an event
229
+ #
230
+ # This allows for nil aasm states - be sure to add validation to your model
231
+ def aasm_read_state
232
+ if new_record?
233
+ send(self.class.aasm_column).blank? ? aasm_determine_state_name(self.class.aasm_initial_state) : send(self.class.aasm_column).to_sym
234
+ else
235
+ send(self.class.aasm_column).nil? ? nil : send(self.class.aasm_column).to_sym
236
+ end
237
+ end
238
+ end
239
+
240
+ module NamedScopeMethods
241
+ def aasm_state_with_named_scope name, options = {}
242
+ aasm_state_without_named_scope name, options
243
+ self.named_scope name, :conditions => { "#{table_name}.#{self.aasm_column}" => name.to_s} unless self.respond_to?(name)
244
+ end
245
+ end
246
+ end
247
+ end
248
+ end
@@ -0,0 +1,227 @@
1
+ module AASM
2
+ module Persistence
3
+ module MongoMapperPersistence
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
17
+ #
18
+ # As a result, it doesn't matter when you define your methods - the following 2 are equivalent
19
+ #
20
+ # class Foo
21
+ # include MongoMapper::Document
22
+ # def aasm_write_state(state)
23
+ # "bar"
24
+ # end
25
+ # include AASM
26
+ # end
27
+ #
28
+ # class Foo
29
+ # include MongoMapper::Document
30
+ # include AASM
31
+ # def aasm_write_state(state)
32
+ # "bar"
33
+ # end
34
+ # end
35
+ #
36
+ def self.included(base)
37
+ base.extend AASM::Persistence::MongoMapperPersistence::ClassMethods
38
+ base.send(:include, AASM::Persistence::MongoMapperPersistence::InstanceMethods)
39
+ base.send(:include, AASM::Persistence::MongoMapperPersistence::ReadState) unless base.method_defined?(:aasm_read_state)
40
+ base.send(:include, AASM::Persistence::MongoMapperPersistence::WriteState) unless base.method_defined?(:aasm_write_state)
41
+ base.send(:include, AASM::Persistence::MongoMapperPersistence::WriteStateWithoutPersistence) unless base.method_defined?(:aasm_write_state_without_persistence)
42
+
43
+ # Mongoid's Validatable gem dependency goes not have a before_validation_on_xxx hook yet.
44
+ # base.before_validation_on_create :aasm_ensure_initial_state
45
+ base.before_validation :aasm_ensure_initial_state
46
+ end
47
+
48
+ module ClassMethods
49
+ # Maps to the aasm_column in the database. Deafults to "aasm_state". You can write:
50
+ #
51
+ # class Foo
52
+ # include MongoMapper::Document
53
+ # include AASM
54
+ # field :aasm_state
55
+ # end
56
+ #
57
+ # OR:
58
+ #
59
+ # class Foo
60
+ # include MongoMapper::Document
61
+ # include AASM
62
+ # field :status
63
+ # aasm_column :status
64
+ # end
65
+ #
66
+ # This method is both a getter and a setter
67
+ def aasm_column(column_name=nil)
68
+ if column_name
69
+ AASM::StateMachine[self].config.column = column_name.to_sym
70
+ # @aasm_column = column_name.to_sym
71
+ else
72
+ AASM::StateMachine[self].config.column ||= :aasm_state
73
+ # @aasm_column ||= :aasm_state
74
+ end
75
+ # @aasm_column
76
+ AASM::StateMachine[self].config.column
77
+ end
78
+
79
+ # def find_in_state(number, state, *args)
80
+ # with_state_scope state do
81
+ # find(number, *args)
82
+ # end
83
+ # end
84
+ #
85
+ # def count_in_state(state, *args)
86
+ # with_state_scope state do
87
+ # count(*args)
88
+ # end
89
+ # end
90
+ #
91
+ # def calculate_in_state(state, *args)
92
+ # with_state_scope state do
93
+ # calculate(*args)
94
+ # end
95
+ # end
96
+
97
+ protected
98
+ def with_state_scope(state)
99
+ with_scope :find => {:conditions => ["#{table_name}.#{aasm_column} = ?", state.to_s]} do
100
+ yield if block_given?
101
+ end
102
+ end
103
+ end
104
+
105
+ module InstanceMethods
106
+
107
+ # Returns the current aasm_state of the object. Respects reload and
108
+ # any changes made to the aasm_state field directly
109
+ #
110
+ # Internally just calls <tt>aasm_read_state</tt>
111
+ #
112
+ # foo = Foo.find(1)
113
+ # foo.aasm_current_state # => :pending
114
+ # foo.aasm_state = "opened"
115
+ # foo.aasm_current_state # => :opened
116
+ # foo.close # => calls aasm_write_state_without_persistence
117
+ # foo.aasm_current_state # => :closed
118
+ # foo.reload
119
+ # foo.aasm_current_state # => :pending
120
+ #
121
+ def aasm_current_state
122
+ @current_state = aasm_read_state
123
+ end
124
+
125
+ private
126
+
127
+ # Ensures that if the aasm_state column is nil and the record is new
128
+ # that the initial state gets populated before validation on create
129
+ #
130
+ # foo = Foo.new
131
+ # foo.aasm_state # => nil
132
+ # foo.valid?
133
+ # foo.aasm_state # => "open" (where :open is the initial state)
134
+ #
135
+ #
136
+ # foo = Foo.find(:first)
137
+ # foo.aasm_state # => 1
138
+ # foo.aasm_state = nil
139
+ # foo.valid?
140
+ # foo.aasm_state # => nil
141
+ #
142
+ def aasm_ensure_initial_state
143
+ send("#{self.class.aasm_column}=", self.aasm_enter_initial_state.to_s) if send(self.class.aasm_column).blank?
144
+ end
145
+
146
+ end
147
+
148
+ module WriteStateWithoutPersistence
149
+ # Writes <tt>state</tt> to the state column, but does not persist it to the database
150
+ #
151
+ # foo = Foo.find(1)
152
+ # foo.aasm_current_state # => :opened
153
+ # foo.close
154
+ # foo.aasm_current_state # => :closed
155
+ # Foo.find(1).aasm_current_state # => :opened
156
+ # foo.save
157
+ # foo.aasm_current_state # => :closed
158
+ # Foo.find(1).aasm_current_state # => :closed
159
+ #
160
+ # NOTE: intended to be called from an event
161
+ def aasm_write_state_without_persistence(state)
162
+ write_attribute(self.class.aasm_column, state.to_s)
163
+ end
164
+ end
165
+
166
+ module WriteState
167
+ # Writes <tt>state</tt> to the state column and persists it to the database
168
+ # using update_attribute (which bypasses validation)
169
+ #
170
+ # foo = Foo.find(1)
171
+ # foo.aasm_current_state # => :opened
172
+ # foo.close!
173
+ # foo.aasm_current_state # => :closed
174
+ # Foo.find(1).aasm_current_state # => :closed
175
+ #
176
+ # NOTE: intended to be called from an event
177
+ def aasm_write_state(state)
178
+ old_value = read_attribute(self.class.aasm_column)
179
+ write_attribute(self.class.aasm_column, state.to_s)
180
+
181
+ unless self.save(:validate => false)
182
+ write_attribute(self.class.aasm_column, old_value)
183
+ return false
184
+ end
185
+
186
+ true
187
+ end
188
+ end
189
+
190
+ module ReadState
191
+
192
+ # Returns the value of the aasm_column - called from <tt>aasm_current_state</tt>
193
+ #
194
+ # If it's a new record, and the aasm state column is blank it returns the initial state:
195
+ #
196
+ # class Foo
197
+ # include MongoMapper::Document
198
+ # include AASM
199
+ # aasm_column :status
200
+ # aasm_state :opened
201
+ # aasm_state :closed
202
+ # end
203
+ #
204
+ # foo = Foo.new
205
+ # foo.current_state # => :opened
206
+ # foo.close
207
+ # foo.current_state # => :closed
208
+ #
209
+ # foo = Foo.find(1)
210
+ # foo.current_state # => :opened
211
+ # foo.aasm_state = nil
212
+ # foo.current_state # => nil
213
+ #
214
+ # NOTE: intended to be called from an event
215
+ #
216
+ # This allows for nil aasm states - be sure to add validation to your model
217
+ def aasm_read_state
218
+ if new_record?
219
+ send(self.class.aasm_column).blank? ? aasm_determine_state_name(self.class.aasm_initial_state) : send(self.class.aasm_column).to_sym
220
+ else
221
+ send(self.class.aasm_column).nil? ? nil : send(self.class.aasm_column).to_sym
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end