This repository is private.
All pages are served over SSL and all pushing and pulling is done over SSH.
No one may fork, clone, or view it unless they are added as a member.
Every repository with this icon (
) is private.
Every repository with this icon (
This repository is public.
Anyone may fork, clone, or view it.
Every repository with this icon (
) is public.
Every repository with this icon (
commit 13a0906c1186ce9250b508296d4a860edc097c93
tree 010ea8b41d94a8512b654087bf5c4e53aad6a2dc
parent 6d213798a6088c75c2f882fcef4b67bb2275464a
tree 010ea8b41d94a8512b654087bf5c4e53aad6a2dc
parent 6d213798a6088c75c2f882fcef4b67bb2275464a
| f3565041 » | obrie | 2008-05-04 | 1 | require 'state_machine/event' | |
| 2 | |||||
| 3 | module PluginAWeek #:nodoc: | ||||
| 4 | module StateMachine | ||||
| 179ce54f » | obrie | 2008-07-05 | 5 | # Represents a state machine for a particular attribute. State machines | |
| 6 | # consist of events (a.k.a. actions) and a set of transitions that define | ||||
| 7 | # how the state changes after a particular event is fired. | ||||
| f3565041 » | obrie | 2008-05-04 | 8 | # | |
| 179ce54f » | obrie | 2008-07-05 | 9 | # A state machine may not necessarily know all of the possible states for | |
| 6a94afcc » | obrie | 2008-09-06 | 10 | # an object since they can be any arbitrary value. As a result, anything | |
| 11 | # that relies on a list of all possible states should keep in mind that if | ||||
| 12 | # a state has not been referenced *anywhere* in the state machine definition, | ||||
| 13 | # then it will *not* be a known state. | ||||
| f3565041 » | obrie | 2008-05-04 | 14 | # | |
| 179ce54f » | obrie | 2008-07-05 | 15 | # == Callbacks | |
| 16 | # | ||||
| 6a94afcc » | obrie | 2008-09-06 | 17 | # Callbacks are supported for hooking before and after every possible | |
| 18 | # transition in the machine. Each callback is invoked in the order in which | ||||
| 19 | # it was defined. See PluginAWeek::StateMachine::Machine#before_transition | ||||
| 20 | # and PluginAWeek::StateMachine::Machine#after_transition for documentation | ||||
| 21 | # on how to define new callbacks. | ||||
| 22 | # | ||||
| 23 | # === Cancelling callbacks | ||||
| 24 | # | ||||
| 25 | # If a +before+ callback returns +false+, all the later callbacks and | ||||
| 26 | # associated transition are cancelled. If an +after+ callback returns false, | ||||
| 27 | # the later callbacks are cancelled, but the transition is still successful. | ||||
| 28 | # This is the same behavior as exposed by ActiveRecord's callback support. | ||||
| 29 | # | ||||
| 30 | # *Note* that if a +before+ callback fails and the bang version of an event | ||||
| 31 | # was invoked, an exception will be raised instead of returning false. | ||||
| 32 | # | ||||
| 33 | # == Observers | ||||
| 34 | # | ||||
| 35 | # ActiveRecord observers can also hook into state machines in addition to | ||||
| 36 | # the conventional before_save, after_save, etc. behaviors. The following | ||||
| 37 | # types of behaviors can be observed: | ||||
| 38 | # * events (e.g. before_park/after_park, before_ignite/after_ignite) | ||||
| 39 | # * transitions (before_transition/after_transition) | ||||
| 40 | # | ||||
| 41 | # Each method takes a set of parameters that provides additional information | ||||
| 42 | # about the transition that caused the observer to be notified. Below are | ||||
| 43 | # examples of defining observers for the following state machine: | ||||
| 44 | # | ||||
| 45 | # class Vehicle < ActiveRecord::Base | ||||
| 46 | # state_machine do | ||||
| 47 | # event :park do | ||||
| 48 | # transition :to => 'parked', :from => 'idling' | ||||
| 49 | # end | ||||
| 50 | # ... | ||||
| 51 | # end | ||||
| 52 | # ... | ||||
| 53 | # end | ||||
| 54 | # | ||||
| 55 | # Event behaviors: | ||||
| 56 | # | ||||
| 57 | # class VehicleObserver < ActiveRecord::Observer | ||||
| 58 | # def before_park(vehicle, from_state, to_state) | ||||
| 59 | # logger.info "Vehicle #{vehicle.id} instructed to park... state is: #{from_state}, state will be: #{to_state}" | ||||
| 60 | # end | ||||
| 61 | # | ||||
| 62 | # def after_park(vehicle, from_state, to_state) | ||||
| 63 | # logger.info "Vehicle #{vehicle.id} instructed to park... state was: #{from_state}, state is: #{to_state}" | ||||
| 64 | # end | ||||
| 65 | # end | ||||
| 66 | # | ||||
| 67 | # Transition behaviors: | ||||
| 68 | # | ||||
| 69 | # class VehicleObserver < ActiveRecord::Observer | ||||
| 70 | # def before_transition(vehicle, attribute, event, from_state, to_state) | ||||
| 71 | # logger.info "Vehicle #{vehicle.id} instructed to #{event}... #{attribute} is: #{from_state}, #{attribute} will be: #{to_state}" | ||||
| 72 | # end | ||||
| 73 | # | ||||
| 74 | # def after_transition(vehicle, attribute, event, from_state, to_state) | ||||
| 75 | # logger.info "Vehicle #{vehicle.id} instructed to #{event}... #{attribute} was: #{from_state}, #{attribute} is: #{to_state}" | ||||
| 76 | # end | ||||
| 77 | # end | ||||
| 78 | # | ||||
| 79 | # One common callback is to record transitions for all models in the system | ||||
| 80 | # for audit/debugging purposes. Below is an example of an observer that can | ||||
| 81 | # easily automate this process for all models: | ||||
| 82 | # | ||||
| 83 | # class StateMachineObserver < ActiveRecord::Observer | ||||
| 84 | # observe Vehicle, Switch, AutoShop | ||||
| 85 | # | ||||
| 86 | # def before_transition(record, attribute, event, from_state, to_state) | ||||
| 87 | # transition = StateTransition.build(:record => record, :attribute => attribute, :event => event, :from_state => from_state, :to_state => to_state) | ||||
| 88 | # transition.save # Will cancel rollback/cancel transition if this fails | ||||
| 89 | # end | ||||
| 90 | # end | ||||
| f3565041 » | obrie | 2008-05-04 | 91 | class Machine | |
| 6a94afcc » | obrie | 2008-09-06 | 92 | # The class that the machine is defined for | |
| 93 | attr_reader :owner_class | ||||
| 26d60afa » | obrie | 2008-07-03 | 94 | ||
| f3565041 » | obrie | 2008-05-04 | 95 | # The attribute for which the state machine is being defined | |
| 6a94afcc » | obrie | 2008-09-06 | 96 | attr_reader :attribute | |
| f3565041 » | obrie | 2008-05-04 | 97 | ||
| 6a94afcc » | obrie | 2008-09-06 | 98 | # The initial state that the machine will be in when a record is created | |
| f3565041 » | obrie | 2008-05-04 | 99 | attr_reader :initial_state | |
| 100 | |||||
| 6a94afcc » | obrie | 2008-09-06 | 101 | # A list of the states defined in the transitions of all of the events | |
| 102 | attr_reader :states | ||||
| 103 | |||||
| 104 | # The events that trigger transitions | ||||
| 105 | attr_reader :events | ||||
| f3565041 » | obrie | 2008-05-04 | 106 | ||
| 107 | # Creates a new state machine for the given attribute | ||||
| 108 | # | ||||
| 109 | # Configuration options: | ||||
| 179ce54f » | obrie | 2008-07-05 | 110 | # * +initial+ - The initial value to set the attribute to. This can be an actual value or a proc, which will be evaluated at runtime. | |
| f3565041 » | obrie | 2008-05-04 | 111 | # | |
| 112 | # == Scopes | ||||
| 113 | # | ||||
| 179ce54f » | obrie | 2008-07-05 | 114 | # This will automatically create a named scope called with_#{attribute} | |
| f3565041 » | obrie | 2008-05-04 | 115 | # that will find all records that have the attribute set to a given value. | |
| 116 | # For example, | ||||
| 117 | # | ||||
| 118 | # Switch.with_state('on') # => Finds all switches where the state is on | ||||
| 119 | # Switch.with_states('on', 'off') # => Finds all switches where the state is either on or off | ||||
| 6a94afcc » | obrie | 2008-09-06 | 120 | # | |
| 121 | # *Note* that if class methods already exist with those names (i.e. "with_state" | ||||
| 122 | # or "with_states"), then a scope will not be defined for that name. | ||||
| 179ce54f » | obrie | 2008-07-05 | 123 | def initialize(owner_class, attribute = 'state', options = {}) | |
| 6a94afcc » | obrie | 2008-09-06 | 124 | set_context(owner_class, options) | |
| f3565041 » | obrie | 2008-05-04 | 125 | ||
| 126 | @attribute = attribute.to_s | ||||
| 26d60afa » | obrie | 2008-07-03 | 127 | @states = [] | |
| 6a94afcc » | obrie | 2008-09-06 | 128 | @events = {} | |
| f3565041 » | obrie | 2008-05-04 | 129 | ||
| 6a94afcc » | obrie | 2008-09-06 | 130 | add_transition_callbacks | |
| f3565041 » | obrie | 2008-05-04 | 131 | add_named_scopes | |
| 132 | end | ||||
| 133 | |||||
| 6a94afcc » | obrie | 2008-09-06 | 134 | # Creates a copy of this machine in addition to copies of each associated | |
| 135 | # event, so that the list of transitions for each event don't conflict | ||||
| 136 | # with different machines | ||||
| 137 | def initialize_copy(orig) #:nodoc: | ||||
| 138 | super | ||||
| 139 | |||||
| 140 | @states = @states.dup | ||||
| 141 | @events = @events.inject({}) do |events, (name, event)| | ||||
| 142 | event = event.dup | ||||
| 143 | event.machine = self | ||||
| 144 | events[name] = event | ||||
| 145 | events | ||||
| 146 | end | ||||
| f3565041 » | obrie | 2008-05-04 | 147 | end | |
| 148 | |||||
| 6a94afcc » | obrie | 2008-09-06 | 149 | # Creates a copy of this machine within the context of the given class. | |
| 150 | # This should be used for inheritance support of state machines. | ||||
| 151 | def within_context(owner_class, options = {}) #:nodoc: | ||||
| 152 | machine = dup | ||||
| 153 | machine.set_context(owner_class, options) | ||||
| 154 | machine | ||||
| f3565041 » | obrie | 2008-05-04 | 155 | end | |
| 156 | |||||
| 6a94afcc » | obrie | 2008-09-06 | 157 | # Changes the context of this machine to the given class so that new | |
| 158 | # events and transitions are created in the proper context. | ||||
| 159 | def set_context(owner_class, options = {}) #:nodoc: | ||||
| 160 | options.assert_valid_keys(:initial) | ||||
| 161 | |||||
| 162 | @owner_class = owner_class | ||||
| 163 | @initial_state = options[:initial] if options[:initial] | ||||
| 164 | end | ||||
| 165 | |||||
| 166 | # Gets the initial state of the machine for the given record. If a record | ||||
| 167 | # is specified a and a dynamic initial state was configured for the machine, | ||||
| 168 | # then that record will be passed into the proc to help determine the actual | ||||
| 169 | # value of the initial state. | ||||
| f3565041 » | obrie | 2008-05-04 | 170 | # | |
| 6a94afcc » | obrie | 2008-09-06 | 171 | # == Examples | |
| 172 | # | ||||
| 173 | # With normal initial state: | ||||
| 174 | # | ||||
| 175 | # class Vehicle < ActiveRecord::Base | ||||
| 176 | # state_machine :initial => 'parked' do | ||||
| 177 | # ... | ||||
| 178 | # end | ||||
| 179 | # end | ||||
| 180 | # | ||||
| 181 | # Vehicle.state_machines['state'].initial_state(@vehicle) # => "parked" | ||||
| 182 | # | ||||
| 183 | # With dynamic initial state: | ||||
| 184 | # | ||||
| 185 | # class Vehicle < ActiveRecord::Base | ||||
| 186 | # state_machine :initial => lambda {|vehicle| vehicle.force_idle ? 'idling' : 'parked'} do | ||||
| 187 | # ... | ||||
| 188 | # end | ||||
| 189 | # end | ||||
| 190 | # | ||||
| 191 | # Vehicle.state_machines['state'].initial_state(@vehicle) # => "idling" | ||||
| 192 | def initial_state(record) | ||||
| 193 | @initial_state.is_a?(Proc) ? @initial_state.call(record) : @initial_state | ||||
| 194 | end | ||||
| 195 | |||||
| 196 | # Defines an event of the system | ||||
| f3565041 » | obrie | 2008-05-04 | 197 | # | |
| 198 | # == Instance methods | ||||
| 199 | # | ||||
| 200 | # The following instance methods are generated when a new event is defined | ||||
| 201 | # (the "park" event is used as an example): | ||||
| 6a94afcc » | obrie | 2008-09-06 | 202 | # * <tt>park</tt> - Fires the "park" event, transitioning from the current state to the next valid state. | |
| 203 | # * <tt>park!</tt> - Fires the "park" event, transitioning from the current state to the next valid state. If the transition cannot happen (for validation, database, etc. reasons), then an error will be raised. | ||||
| 204 | # * <tt>can_park?</tt> - Checks whether the "park" event can be fired given the current state of the record. | ||||
| f3565041 » | obrie | 2008-05-04 | 205 | # | |
| 206 | # == Defining transitions | ||||
| 207 | # | ||||
| 208 | # +event+ requires a block which allows you to define the possible | ||||
| 209 | # transitions that can happen as a result of that event. For example, | ||||
| 210 | # | ||||
| 211 | # event :park do | ||||
| 212 | # transition :to => 'parked', :from => 'idle' | ||||
| 213 | # end | ||||
| 214 | # | ||||
| 215 | # event :first_gear do | ||||
| 216 | # transition :to => 'first_gear', :from => 'parked', :if => :seatbelt_on? | ||||
| 217 | # end | ||||
| 218 | # | ||||
| 219 | # See PluginAWeek::StateMachine::Event#transition for more information on | ||||
| 220 | # the possible options that can be passed in. | ||||
| 221 | # | ||||
| 179ce54f » | obrie | 2008-07-05 | 222 | # *Note* that this block is executed within the context of the actual event | |
| 223 | # object. As a result, you will not be able to reference any class methods | ||||
| 224 | # on the model without referencing the class itself. For example, | ||||
| 225 | # | ||||
| 226 | # class Car < ActiveRecord::Base | ||||
| 227 | # def self.safe_states | ||||
| 6a94afcc » | obrie | 2008-09-06 | 228 | # %w(parked idling stalled) | |
| 179ce54f » | obrie | 2008-07-05 | 229 | # end | |
| 230 | # | ||||
| 231 | # state_machine :state do | ||||
| 8d3df6f1 » | obrie | 2008-07-05 | 232 | # event :park do | |
| 233 | # transition :to => 'parked', :from => Car.safe_states | ||||
| 234 | # end | ||||
| 235 | # end | ||||
| 236 | # end | ||||
| 179ce54f » | obrie | 2008-07-05 | 237 | # | |
| f3565041 » | obrie | 2008-05-04 | 238 | # == Example | |
| 239 | # | ||||
| 240 | # class Car < ActiveRecord::Base | ||||
| 241 | # state_machine(:state, :initial => 'parked') do | ||||
| 242 | # event :park, :after => :release_seatbelt do | ||||
| 243 | # transition :to => 'parked', :from => %w(first_gear reverse) | ||||
| 244 | # end | ||||
| 245 | # ... | ||||
| 246 | # end | ||||
| 247 | # end | ||||
| 6a94afcc » | obrie | 2008-09-06 | 248 | def event(name, &block) | |
| f3565041 » | obrie | 2008-05-04 | 249 | name = name.to_s | |
| 6a94afcc » | obrie | 2008-09-06 | 250 | event = events[name] ||= Event.new(self, name) | |
| f3565041 » | obrie | 2008-05-04 | 251 | event.instance_eval(&block) | |
| 26d60afa » | obrie | 2008-07-03 | 252 | ||
| 6a94afcc » | obrie | 2008-09-06 | 253 | # Record the states so that the machine can keep a list of all known | |
| 254 | # states that have been defined | ||||
| 26d60afa » | obrie | 2008-07-03 | 255 | event.transitions.each do |transition| | |
| 6a94afcc » | obrie | 2008-09-06 | 256 | @states |= [transition.options[:to]] + Array(transition.options[:from]) + Array(transition.options[:except_from]) | |
| 257 | @states.sort! | ||||
| 26d60afa » | obrie | 2008-07-03 | 258 | end | |
| 259 | |||||
| f3565041 » | obrie | 2008-05-04 | 260 | event | |
| 261 | end | ||||
| 262 | |||||
| 6a94afcc » | obrie | 2008-09-06 | 263 | # Creates a callback that will be invoked *before* a transition has been | |
| 264 | # performed, so long as the given configuration options match the transition. | ||||
| 265 | # Each part of the transition (to state, from state, and event) must match | ||||
| 266 | # in order for the callback to get invoked. | ||||
| 267 | # | ||||
| 268 | # Configuration options: | ||||
| 269 | # * +to+ - One or more states being transitioned to. If none are specified, then all states will match. | ||||
| 270 | # * +from+ - One or more states being transitioned from. If none are specified, then all states will match. | ||||
| 271 | # * +on+ - One or more events that fired the transition. If none are specified, then all events will match. | ||||
| 272 | # * +except_to+ - One more states *not* being transitioned to | ||||
| 273 | # * +except_from+ - One or more states *not* being transitioned from | ||||
| 274 | # * +except_on+ - One or more events that *did not* fire the transition | ||||
| 275 | # * +do+ - The callback to invoke when a transition matches. This can be a method, proc or string. | ||||
| 276 | # * +if+ - A method, proc or string to call to determine if the callback should occur (e.g. :if => :allow_callbacks, or :if => lambda {|user| user.signup_step > 2}). The method, proc or string should return or evaluate to a true or false value. | ||||
| 277 | # * +unless+ - A method, proc or string to call to determine if the callback should not occur (e.g. :unless => :skip_callbacks, or :unless => lambda {|user| user.signup_step <= 2}). The method, proc or string should return or evaluate to a true or false value. | ||||
| 278 | # | ||||
| 279 | # The +except+ group of options (+except_to+, +exception_from+, and | ||||
| 280 | # +except_on+) acts as the +unless+ equivalent of their counterparts (+to+, | ||||
| 281 | # +from+, and +on+, respectively) | ||||
| 282 | # | ||||
| 283 | # == The callback | ||||
| 284 | # | ||||
| 285 | # When defining additional configuration options, callbacks must be defined | ||||
| 286 | # in the :do option like so: | ||||
| 287 | # | ||||
| 288 | # class Vehicle < ActiveRecord::Base | ||||
| 289 | # state_machine do | ||||
| 290 | # before_transition :to => 'parked', :do => :set_alarm | ||||
| 291 | # ... | ||||
| 292 | # end | ||||
| 293 | # end | ||||
| 294 | # | ||||
| 295 | # == Examples | ||||
| 296 | # | ||||
| 297 | # Below is an example of a model with one state machine and various types | ||||
| 298 | # of +before+ transitions defined for it: | ||||
| 299 | # | ||||
| 300 | # class Vehicle < ActiveRecord::Base | ||||
| 301 | # state_machine do | ||||
| 302 | # # Before all transitions | ||||
| 303 | # before_transition :update_dashboard | ||||
| 304 | # | ||||
| 305 | # # Before specific transition: | ||||
| 306 | # before_transition :to => 'parked', :from => %w(first_gear idling), :on => 'park', :do => :take_off_seatbelt | ||||
| 307 | # | ||||
| 308 | # # With conditional callback: | ||||
| 309 | # before_transition :to => 'parked', :do => :take_off_seatbelt, :if => :seatbelt_on? | ||||
| 310 | # | ||||
| 311 | # # Using :except counterparts: | ||||
| 312 | # before_transition :except_to => 'stalled', :except_from => 'stalled', :except_on => 'crash', :do => :update_dashboard | ||||
| 313 | # ... | ||||
| 314 | # end | ||||
| 315 | # end | ||||
| 316 | # | ||||
| 317 | # As can be seen, any number of transitions can be created using various | ||||
| 318 | # combinations of configuration options. | ||||
| 319 | def before_transition(options = {}) | ||||
| 320 | add_transition_callback(:before, options) | ||||
| 321 | end | ||||
| 322 | |||||
| 323 | # Creates a callback that will be invoked *after* a transition has been | ||||
| 324 | # performed, so long as the given configuration options match the transition. | ||||
| 325 | # Each part of the transition (to state, from state, and event) must match | ||||
| 326 | # in order for the callback to get invoked. | ||||
| 327 | # | ||||
| 328 | # Configuration options: | ||||
| 329 | # * +to+ - One or more states being transitioned to. If none are specified, then all states will match. | ||||
| 330 | # * +from+ - One or more states being transitioned from. If none are specified, then all states will match. | ||||
| 331 | # * +on+ - One or more events that fired the transition. If none are specified, then all events will match. | ||||
| 332 | # * +except_to+ - One more states *not* being transitioned to | ||||
| 333 | # * +except_from+ - One or more states *not* being transitioned from | ||||
| 334 | # * +except_on+ - One or more events that *did not* fire the transition | ||||
| 335 | # * +do+ - The callback to invoke when a transition matches. This can be a method, proc or string. | ||||
| 336 | # * +if+ - A method, proc or string to call to determine if the callback should occur (e.g. :if => :allow_callbacks, or :if => lambda {|user| user.signup_step > 2}). The method, proc or string should return or evaluate to a true or false value. | ||||
| 337 | # * +unless+ - A method, proc or string to call to determine if the callback should not occur (e.g. :unless => :skip_callbacks, or :unless => lambda {|user| user.signup_step <= 2}). The method, proc or string should return or evaluate to a true or false value. | ||||
| 338 | # | ||||
| 339 | # The +except+ group of options (+except_to+, +exception_from+, and | ||||
| 340 | # +except_on+) acts as the +unless+ equivalent of their counterparts (+to+, | ||||
| 341 | # +from+, and +on+, respectively) | ||||
| 342 | # | ||||
| 343 | # == The callback | ||||
| 344 | # | ||||
| 345 | # When defining additional configuration options, callbacks must be defined | ||||
| 346 | # in the :do option like so: | ||||
| 347 | # | ||||
| 348 | # class Vehicle < ActiveRecord::Base | ||||
| 349 | # state_machine do | ||||
| 350 | # after_transition :to => 'parked', :do => :set_alarm | ||||
| 351 | # ... | ||||
| 352 | # end | ||||
| 353 | # end | ||||
| 354 | # | ||||
| 355 | # == Examples | ||||
| 356 | # | ||||
| 357 | # Below is an example of a model with one state machine and various types | ||||
| 358 | # of +after+ transitions defined for it: | ||||
| 359 | # | ||||
| 360 | # class Vehicle < ActiveRecord::Base | ||||
| 361 | # state_machine do | ||||
| 362 | # # After all transitions | ||||
| 363 | # after_transition :update_dashboard | ||||
| 364 | # | ||||
| 365 | # # After specific transition: | ||||
| 366 | # after_transition :to => 'parked', :from => %w(first_gear idling), :on => 'park', :do => :take_off_seatbelt | ||||
| 367 | # | ||||
| 368 | # # With conditional callback: | ||||
| 369 | # after_transition :to => 'parked', :do => :take_off_seatbelt, :if => :seatbelt_on? | ||||
| 370 | # | ||||
| 371 | # # Using :except counterparts: | ||||
| 372 | # after_transition :except_to => 'stalled', :except_from => 'stalled', :except_on => 'crash', :do => :update_dashboard | ||||
| 373 | # ... | ||||
| 374 | # end | ||||
| 375 | # end | ||||
| 376 | # | ||||
| 377 | # As can be seen, any number of transitions can be created using various | ||||
| 378 | # combinations of configuration options. | ||||
| 379 | def after_transition(options = {}) | ||||
| 380 | add_transition_callback(:after, options) | ||||
| f3565041 » | obrie | 2008-05-04 | 381 | end | |
| 382 | |||||
| 383 | private | ||||
| b3b009b1 » | obrie | 2008-06-29 | 384 | # Adds the given callback to the callback chain during a state transition | |
| 6a94afcc » | obrie | 2008-09-06 | 385 | def add_transition_callback(type, options) | |
| 386 | options = {:do => options} unless options.is_a?(Hash) | ||||
| 387 | options.assert_valid_keys(:to, :from, :on, :except_to, :except_from, :except_on, :do, :if, :unless) | ||||
| 388 | |||||
| 389 | # The actual callback (defined in the :do option) must be defined | ||||
| 390 | raise ArgumentError, ':do callback must be specified' unless options[:do] | ||||
| 391 | |||||
| 392 | # Create the callback | ||||
| 393 | owner_class.send("#{type}_transition_#{attribute}", options.delete(:do), options) | ||||
| 394 | end | ||||
| 395 | |||||
| 396 | # Add before/after callbacks for when the attribute transitions to a | ||||
| 397 | # different value | ||||
| 398 | def add_transition_callbacks | ||||
| 399 | %w(before after).each {|type| owner_class.define_callbacks("#{type}_transition_#{attribute}") } | ||||
| b3b009b1 » | obrie | 2008-06-29 | 400 | end | |
| 401 | |||||
| 402 | # Add named scopes for finding records with a particular value or values | ||||
| 403 | # for the attribute | ||||
| f3565041 » | obrie | 2008-05-04 | 404 | def add_named_scopes | |
| 6a94afcc » | obrie | 2008-09-06 | 405 | [attribute, attribute.pluralize].uniq.each do |name| | |
| 13a0906c » | sob | 2008-11-10 | 406 | with_name = "with_#{name}" | |
| 407 | without_name = "without_#{name}" | ||||
| 408 | owner_class.named_scope with_name.to_sym, lambda {|*values| {:conditions => {attribute => values.flatten}}} unless owner_class.respond_to?(with_name) | ||||
| 409 | owner_class.named_scope without_name.to_sym, lambda {|*values| {:conditions => ["#{attribute} NOT IN (?)", values.flatten]}} unless owner_class.respond_to?(without_name) | ||||
| f3565041 » | obrie | 2008-05-04 | 410 | end | |
| 411 | end | ||||
| 412 | end | ||||
| 413 | end | ||||
| 414 | end | ||||








