# Copyright (c) 2009 Rick Olson # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files # (the "Software"), to deal in the Software without restriction, # including without limitation the rights to use, copy, modify, merge, # publish, distribute, sublicense, and/or sell copies of the Software, # and to permit persons to whom the Software is furnished to do so, # subject to the following conditions: # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. module Transitions class Machine attr_writer :initial_state attr_accessor :states, :events, :state_index attr_reader :klass, :auto_scopes def initialize(klass, options = {}, &block) @klass, @states, @state_index, @events = klass, [], {}, {} update(options, &block) end def initial_state @initial_state ||= (states.first ? states.first.name : nil) end def update(options = {}, &block) @initial_state = options[:initial] if options.key?(:initial) @auto_scopes = options[:auto_scopes] instance_eval(&block) if block include_scopes if @auto_scopes && ::Transitions.active_record_descendant?(klass) self end # TODO Refactor me please? def fire_event(event, record, persist, *args) state_index[record.current_state].call_action(:exit, record) begin if new_state = @events[event].fire(record, nil, *args) state_index[new_state].call_action(:enter, record) if record.respond_to?(:event_fired) record.send(:event_fired, record.current_state, new_state, event) end record.update_current_state(new_state, persist) @events[event].success.call(record) if @events[event].success return true else record.send(:event_failed, event) if record.respond_to?(:event_failed) return false end rescue => e if record.respond_to?(:event_failed) record.send(:event_failed, event) return false else raise e end end end def events_for(state) events = @events.values.select { |event| event.transitions_from_state?(state) } events.map! { |event| event.name } end def current_state_variable # TODO Refactor me away. :@current_state end private def state(name, options = {}) unless @state_index.key? name # Just ignore duplicates state = State.new(name, :machine => self) state.update options @state_index[name] = state @states << state end end def event(name, options = {}, &block) (@events[name] ||= Event.new(self, name)).update(options, &block) end def include_scopes @states.each do |state| state_name = state.name.to_s if @klass.respond_to?(state_name) raise InvalidMethodOverride, "Transitions: Can not define scope `#{state_name}` because there is already an equally named method defined - either rename the existing method or the state." end @klass.scope state_name, -> { @klass.where(@klass.state_machine.attribute_name => state_name) } end end end end