preprocessor-aasmaa 2.0.7

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.
data/lib/aasm.rb ADDED
@@ -0,0 +1,160 @@
1
+ require File.join(File.dirname(__FILE__), 'event')
2
+ require File.join(File.dirname(__FILE__), 'state')
3
+ require File.join(File.dirname(__FILE__), 'state_machine')
4
+ require File.join(File.dirname(__FILE__), 'persistence')
5
+ require File.join(File.dirname(__FILE__), 'integers')
6
+
7
+ module AASM
8
+ def self.Version
9
+ '2.0.5'
10
+ end
11
+
12
+ class InvalidTransition < RuntimeError
13
+ end
14
+
15
+ class UndefinedState < RuntimeError
16
+ end
17
+
18
+ def self.included(base) #:nodoc:
19
+ # TODO - need to ensure that a machine is being created because
20
+ # AASM was either included or arrived at via inheritance. It
21
+ # cannot be both.
22
+ base.extend AASM::ClassMethods
23
+ AASM::Persistence.set_persistence(base)
24
+ AASM::StateMachine[base] = AASM::StateMachine.new('')
25
+ end
26
+
27
+ module ClassMethods
28
+ def inherited(klass)
29
+ AASM::StateMachine[klass] = AASM::StateMachine[self].clone
30
+ super
31
+ end
32
+
33
+ def aasm_initial_state(set_state=nil)
34
+ if set_state
35
+ AASM::StateMachine[self].initial_state = set_state
36
+ else
37
+ AASM::StateMachine[self].initial_state
38
+ end
39
+ end
40
+
41
+ def aasm_initial_state=(state)
42
+ AASM::StateMachine[self].initial_state = state
43
+ end
44
+
45
+ def aasm_state(name, options={})
46
+ sm = AASM::StateMachine[self]
47
+ sm.create_state(name, options)
48
+ sm.initial_state = name unless sm.initial_state
49
+
50
+ define_method("#{name.to_s}?") do
51
+ aasm_current_state == name
52
+ end
53
+ end
54
+
55
+ def aasm_event(name, options = {}, &block)
56
+ sm = AASM::StateMachine[self]
57
+
58
+ unless sm.events.has_key?(name)
59
+ sm.events[name] = AASM::SupportingClasses::Event.new(name, options, &block)
60
+ end
61
+
62
+ define_method("#{name.to_s}!") do |*args|
63
+ aasm_fire_event(name, true, *args)
64
+ end
65
+
66
+ define_method("#{name.to_s}") do |*args|
67
+ aasm_fire_event(name, false, *args)
68
+ end
69
+ end
70
+
71
+ def aasm_states
72
+ AASM::StateMachine[self].states
73
+ end
74
+
75
+ def aasm_events
76
+ AASM::StateMachine[self].events
77
+ end
78
+
79
+ def aasm_states_for_select
80
+ AASM::StateMachine[self].states.map { |state| state.for_select }
81
+ end
82
+
83
+ end
84
+
85
+ # Instance methods
86
+ def aasm_current_state
87
+ return @aasm_current_state if @aasm_current_state
88
+
89
+ if self.respond_to?(:aasm_read_state) || self.private_methods.include?('aasm_read_state')
90
+ @aasm_current_state = aasm_read_state
91
+ end
92
+ return @aasm_current_state if @aasm_current_state
93
+ self.class.aasm_initial_state
94
+ end
95
+
96
+ def aasm_events_for_current_state
97
+ aasm_events_for_state(aasm_current_state)
98
+ end
99
+
100
+ def aasm_events_for_state(state)
101
+ events = self.class.aasm_events.values.select {|event| event.transitions_from_state?(state) }
102
+ events.map {|event| event.name}
103
+ end
104
+
105
+ private
106
+ def set_aasm_current_state_with_persistence(state)
107
+ save_success = true
108
+ if self.respond_to?(:aasm_write_state) || self.private_methods.include?('aasm_write_state')
109
+ save_success = aasm_write_state(state)
110
+ end
111
+ self.aasm_current_state = state if save_success
112
+
113
+ save_success
114
+ end
115
+
116
+ def aasm_current_state=(state)
117
+ if self.respond_to?(:aasm_write_state_without_persistence) || self.private_methods.include?('aasm_write_state_without_persistence')
118
+ aasm_write_state_without_persistence(state)
119
+ end
120
+ @aasm_current_state = state
121
+ end
122
+
123
+ def aasm_state_object_for_state(name)
124
+ obj = self.class.aasm_states.find {|s| s == name}
125
+ raise AASM::UndefinedState, "State :#{name} doesn't exist" if obj.nil?
126
+ obj
127
+ end
128
+
129
+ def aasm_fire_event(name, persist, *args)
130
+ aasm_state_object_for_state(aasm_current_state).call_action(:exit, self)
131
+
132
+ new_state = self.class.aasm_events[name].fire(self, *args)
133
+
134
+ unless new_state.nil?
135
+ aasm_state_object_for_state(new_state).call_action(:enter, self)
136
+
137
+ persist_successful = true
138
+ if persist
139
+ persist_successful = set_aasm_current_state_with_persistence(new_state)
140
+ self.class.aasm_events[name].execute_success_callback(self) if persist_successful
141
+ else
142
+ self.aasm_current_state = new_state
143
+ end
144
+
145
+ if persist_successful
146
+ self.aasm_event_fired(self.aasm_current_state, new_state) if self.respond_to?(:aasm_event_fired)
147
+ else
148
+ self.aasm_event_failed(name) if self.respond_to?(:aasm_event_failed)
149
+ end
150
+
151
+ persist_successful
152
+ else
153
+ if self.respond_to?(:aasm_event_failed)
154
+ self.aasm_event_failed(name)
155
+ end
156
+
157
+ false
158
+ end
159
+ end
160
+ end
data/lib/event.rb ADDED
@@ -0,0 +1,54 @@
1
+ require File.join(File.dirname(__FILE__), 'state_transition')
2
+
3
+ module AASM
4
+ module SupportingClasses
5
+ class Event
6
+ attr_reader :name, :success
7
+
8
+ def initialize(name, options = {}, &block)
9
+ @name = name
10
+ @success = options[:success]
11
+ @transitions = []
12
+ instance_eval(&block) if block
13
+ end
14
+
15
+ def fire(obj, to_state=nil, *args)
16
+ transitions = @transitions.select { |t| t.from == obj.aasm_current_state }
17
+ raise AASM::InvalidTransition, "Event '#{name}' cannot transition from '#{obj.aasm_current_state}'" if transitions.size == 0
18
+
19
+ next_state = nil
20
+ transitions.each do |transition|
21
+ next if to_state and !Array(transition.to).include?(to_state)
22
+ if transition.perform(obj)
23
+ next_state = to_state || Array(transition.to).first
24
+ transition.execute(obj, *args)
25
+ break
26
+ end
27
+ end
28
+ next_state
29
+ end
30
+
31
+ def transitions_from_state?(state)
32
+ @transitions.any? { |t| t.from == state }
33
+ end
34
+
35
+ def execute_success_callback(obj)
36
+ case success
37
+ when String, Symbol
38
+ obj.send(success)
39
+ when Array
40
+ success.each { |meth| obj.send(meth) }
41
+ when Proc
42
+ success.call(obj)
43
+ end
44
+ end
45
+
46
+ private
47
+ def transitions(trans_opts)
48
+ Array(trans_opts[:from]).each do |s|
49
+ @transitions << SupportingClasses::StateTransition.new(trans_opts.merge({:from => s.to_sym}))
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,16 @@
1
+ module AASM
2
+ module Persistence
3
+
4
+ # Checks to see this class or any of it's superclasses inherit from
5
+ # ActiveRecord::Base and if so includes ActiveRecordPersistence
6
+ def self.set_persistence(base)
7
+ # Use a fancier auto-loading thingy, perhaps. When there are more persistence engines.
8
+ hierarchy = base.ancestors.map {|klass| klass.to_s}
9
+
10
+ if hierarchy.include?("ActiveRecord::Base")
11
+ require File.join(File.dirname(__FILE__), 'persistence', 'active_record_persistence')
12
+ base.send(:include, AASM::Persistence::ActiveRecordPersistence)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,266 @@
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
+ alias_method :aasm_state_without_named_scope, :aasm_state
47
+ alias_method :aasm_state, :aasm_state_with_named_scope
48
+ end
49
+ end
50
+ end
51
+
52
+ base.before_validation_on_create :aasm_ensure_initial_state
53
+ end
54
+
55
+ module ClassMethods
56
+ # Maps to the aasm_column in the database. Deafults to "aasm_state". You can write:
57
+ #
58
+ # create_table :foos do |t|
59
+ # t.string :name
60
+ # t.string :aasm_state
61
+ # end
62
+ #
63
+ # class Foo < ActiveRecord::Base
64
+ # include AASM
65
+ # end
66
+ #
67
+ # OR:
68
+ #
69
+ # create_table :foos do |t|
70
+ # t.string :name
71
+ # t.string :status
72
+ # end
73
+ #
74
+ # class Foo < ActiveRecord::Base
75
+ # include AASM
76
+ # aasm_column :status
77
+ # end
78
+ #
79
+ # This method is both a getter and a setter
80
+ def aasm_column(column_name=nil)
81
+ if column_name
82
+ AASM::StateMachine[self].config.column = column_name.to_sym
83
+ # @aasm_column = column_name.to_sym
84
+ else
85
+ AASM::StateMachine[self].config.column ||= :aasm_state
86
+ # @aasm_column ||= :aasm_state
87
+ end
88
+ # @aasm_column
89
+ AASM::StateMachine[self].config.column
90
+ end
91
+
92
+ def find_in_state(number, state, *args)
93
+ with_state_scope state do
94
+ find(number, *args)
95
+ end
96
+ end
97
+
98
+ def count_in_state(state, *args)
99
+ with_state_scope state do
100
+ count(*args)
101
+ end
102
+ end
103
+
104
+ def calculate_in_state(state, *args)
105
+ with_state_scope state do
106
+ calculate(*args)
107
+ end
108
+ end
109
+
110
+ def aasm_integers
111
+ AASM::StateMachine[self].integers
112
+ end
113
+
114
+ # Get state name by integer if integers are set
115
+ def aasm_state_name(value)
116
+ aasm_integers.setted? && value.is_a?(Integer) ? aasm_integers.by_integer(value) : value.to_s
117
+ end
118
+
119
+ # Get state integer by symbol if integers are set
120
+ def aasm_state_integer(value)
121
+ aasm_integers.setted? && value.is_a?(Symbol) ? aasm_integers.by_state(value) : value
122
+ end
123
+
124
+
125
+ protected
126
+ def with_state_scope(state)
127
+ with_scope :find => {:conditions => ["#{table_name}.#{aasm_column} = ?", state.to_s]} do
128
+ yield if block_given?
129
+ end
130
+ end
131
+ end
132
+
133
+ module InstanceMethods
134
+
135
+ # Returns the current aasm_state of the object. Respects reload and
136
+ # any changes made to the aasm_state field directly
137
+ #
138
+ # Internally just calls <tt>aasm_read_state</tt>
139
+ #
140
+ # foo = Foo.find(1)
141
+ # foo.aasm_current_state # => :pending
142
+ # foo.aasm_state = "opened"
143
+ # foo.aasm_current_state # => :opened
144
+ # foo.close # => calls aasm_write_state_without_persistence
145
+ # foo.aasm_current_state # => :closed
146
+ # foo.reload
147
+ # foo.aasm_current_state # => :pending
148
+ #
149
+ def aasm_current_state
150
+ @current_state = aasm_read_state
151
+ end
152
+
153
+ def aasm_column_value
154
+ send(self.class.aasm_column)
155
+ end
156
+
157
+
158
+ private
159
+
160
+ # Ensures that if the aasm_state column is nil and the record is new
161
+ # that the initial state gets populated before validation on create
162
+ #
163
+ # foo = Foo.new
164
+ # foo.aasm_state # => nil
165
+ # foo.valid?
166
+ # foo.aasm_state # => "open" (where :open is the initial state)
167
+ #
168
+ #
169
+ # foo = Foo.find(:first)
170
+ # foo.aasm_state # => 1
171
+ # foo.aasm_state = nil
172
+ # foo.valid?
173
+ # foo.aasm_state # => nil
174
+ #
175
+ def aasm_ensure_initial_state
176
+ send("#{self.class.aasm_column}=", self.aasm_current_state.to_s)
177
+ end
178
+
179
+ end
180
+
181
+ module WriteStateWithoutPersistence
182
+ # Writes <tt>state</tt> to the state column, but does not persist it to the database
183
+ #
184
+ # foo = Foo.find(1)
185
+ # foo.aasm_current_state # => :opened
186
+ # foo.close
187
+ # foo.aasm_current_state # => :closed
188
+ # Foo.find(1).aasm_current_state # => :opened
189
+ # foo.save
190
+ # foo.aasm_current_state # => :closed
191
+ # Foo.find(1).aasm_current_state # => :closed
192
+ #
193
+ # NOTE: intended to be called from an event
194
+ def aasm_write_state_without_persistence(state)
195
+ write_attribute(self.class.aasm_column, self.class.aasm_state_integer(state))
196
+ end
197
+ end
198
+
199
+ module WriteState
200
+ # Writes <tt>state</tt> to the state column and persists it to the database
201
+ # using update_attribute (which bypasses validation)
202
+ #
203
+ # foo = Foo.find(1)
204
+ # foo.aasm_current_state # => :opened
205
+ # foo.close!
206
+ # foo.aasm_current_state # => :closed
207
+ # Foo.find(1).aasm_current_state # => :closed
208
+ #
209
+ # NOTE: intended to be called from an event
210
+ def aasm_write_state(state)
211
+ old_value = read_attribute(self.class.aasm_column)
212
+ write_attribute(self.class.aasm_column, self.class.aasm_state_integer(state))
213
+
214
+ unless self.save
215
+ write_attribute(self.class.aasm_column, old_value)
216
+ return false
217
+ end
218
+
219
+ true
220
+ end
221
+ end
222
+
223
+ module ReadState
224
+
225
+ # Returns the value of the aasm_column - called from <tt>aasm_current_state</tt>
226
+ #
227
+ # If it's a new record, and the aasm state column is blank it returns the initial state:
228
+ #
229
+ # class Foo < ActiveRecord::Base
230
+ # include AASM
231
+ # aasm_column :status
232
+ # aasm_state :opened
233
+ # aasm_state :closed
234
+ # end
235
+ #
236
+ # foo = Foo.new
237
+ # foo.current_state # => :opened
238
+ # foo.close
239
+ # foo.current_state # => :closed
240
+ #
241
+ # foo = Foo.find(1)
242
+ # foo.current_state # => :opened
243
+ # foo.aasm_state = nil
244
+ # foo.current_state # => nil
245
+ #
246
+ # NOTE: intended to be called from an event
247
+ #
248
+ # This allows for nil aasm states - be sure to add validation to your model
249
+ def aasm_read_state
250
+ state = if new_record?
251
+ aasm_column_value.blank? ? self.class.aasm_initial_state : self.class.aasm_state_name(aasm_column_value).to_sym
252
+ else
253
+ aasm_column_value.nil? ? nil : self.class.aasm_state_name(aasm_column_value).to_sym
254
+ end
255
+ end
256
+ end
257
+
258
+ module NamedScopeMethods
259
+ def aasm_state_with_named_scope name, options = {}
260
+ aasm_state_without_named_scope name, options
261
+ self.named_scope name, lambda { {:conditions => {self.aasm_column => self.aasm_state_integer(name)} } } unless self.respond_to?(name)
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end