Permalink
Browse files

Axe AM state machine

We're going do it eventually, get it done before 3.0 is final.
  • Loading branch information...
1 parent 657d855 commit db49c706b62e7ea2ab93f05399dbfddf5087ee0c @josh josh committed Jan 31, 2010
@@ -43,7 +43,6 @@ module ActiveModel
autoload :Observer, 'active_model/observing'
autoload :Observing
autoload :Serialization
- autoload :StateMachine
autoload :TestCase
autoload :Translation
autoload :VERSION
@@ -1,70 +0,0 @@
-module ActiveModel
- module StateMachine
- autoload :Event, 'active_model/state_machine/event'
- autoload :Machine, 'active_model/state_machine/machine'
- autoload :State, 'active_model/state_machine/state'
- autoload :StateTransition, 'active_model/state_machine/state_transition'
-
- extend ActiveSupport::Concern
-
- class InvalidTransition < Exception
- end
-
- module ClassMethods
- def inherited(klass)
- super
- klass.state_machines = state_machines
- end
-
- def state_machines
- @state_machines ||= {}
- end
-
- def state_machines=(value)
- @state_machines = value ? value.dup : nil
- end
-
- def state_machine(name = nil, options = {}, &block)
- if name.is_a?(Hash)
- options = name
- name = nil
- end
- name ||= :default
- state_machines[name] ||= Machine.new(self, name)
- block ? state_machines[name].update(options, &block) : state_machines[name]
- end
-
- def define_state_query_method(state_name)
- name = "#{state_name}?"
- undef_method(name) if method_defined?(name)
- class_eval "def #{name}; current_state.to_s == %(#{state_name}) end"
- end
- end
-
- def current_state(name = nil, new_state = nil, persist = false)
- sm = self.class.state_machine(name)
- ivar = sm.current_state_variable
- if name && new_state
- if persist && respond_to?(:write_state)
- write_state(sm, new_state)
- end
-
- if respond_to?(:write_state_without_persistence)
- write_state_without_persistence(sm, new_state)
- end
-
- instance_variable_set(ivar, new_state)
- else
- instance_variable_set(ivar, nil) unless instance_variable_defined?(ivar)
- value = instance_variable_get(ivar)
- return value if value
-
- if respond_to?(:read_state)
- value = instance_variable_set(ivar, read_state(sm))
- end
-
- value || sm.initial_state
- end
- end
- end
-end
@@ -1,62 +0,0 @@
-module ActiveModel
- module StateMachine
- class Event
- attr_reader :name, :success
-
- def initialize(machine, name, options = {}, &block)
- @machine, @name, @transitions = machine, name, []
- if machine
- machine.klass.send(:define_method, "#{name}!") do |*args|
- machine.fire_event(name, self, true, *args)
- end
-
- machine.klass.send(:define_method, name.to_s) do |*args|
- machine.fire_event(name, self, false, *args)
- end
- end
- update(options, &block)
- end
-
- def fire(obj, to_state = nil, *args)
- transitions = @transitions.select { |t| t.from == obj.current_state(@machine ? @machine.name : nil) }
- raise InvalidTransition if transitions.size == 0
-
- next_state = nil
- transitions.each do |transition|
- next if to_state && !Array(transition.to).include?(to_state)
- if transition.perform(obj)
- next_state = to_state || Array(transition.to).first
- transition.execute(obj, *args)
- break
- end
- end
- next_state
- end
-
- def transitions_from_state?(state)
- @transitions.any? { |t| t.from? state }
- end
-
- def ==(event)
- if event.is_a? Symbol
- name == event
- else
- name == event.name
- end
- end
-
- def update(options = {}, &block)
- if options.key?(:success) then @success = options[:success] end
- if block then instance_eval(&block) end
- self
- end
-
- private
- def transitions(trans_opts)
- Array(trans_opts[:from]).each do |s|
- @transitions << StateTransition.new(trans_opts.merge({:from => s.to_sym}))
- end
- end
- end
- end
-end
@@ -1,75 +0,0 @@
-module ActiveModel
- module StateMachine
- class Machine
- attr_writer :initial_state
- attr_accessor :states, :events, :state_index
- attr_reader :klass, :name
-
- def initialize(klass, name, options = {}, &block)
- @klass, @name, @states, @state_index, @events = klass, name, [], {}, {}
- update(options, &block)
- end
-
- def initial_state
- @initial_state ||= (states.first ? states.first.name : nil)
- end
-
- def update(options = {}, &block)
- if options.key?(:initial) then @initial_state = options[:initial] end
- if block then instance_eval(&block) end
- self
- end
-
- def fire_event(event, record, persist, *args)
- state_index[record.current_state(@name)].call_action(:exit, record)
- if new_state = @events[event].fire(record, *args)
- state_index[new_state].call_action(:enter, record)
-
- if record.respond_to?(event_fired_callback)
- record.send(event_fired_callback, record.current_state, new_state)
- end
-
- record.current_state(@name, new_state, persist)
- record.send(@events[event].success) if @events[event].success
- true
- else
- if record.respond_to?(event_failed_callback)
- record.send(event_failed_callback, event)
- end
-
- false
- end
- end
-
- def states_for_select
- states.map { |st| [st.display_name, st.name.to_s] }
- 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
- "@#{@name}_current_state"
- end
-
- private
- def state(name, options = {})
- @states << (state_index[name] ||= State.new(name, :machine => self)).update(options)
- end
-
- def event(name, options = {}, &block)
- (@events[name] ||= Event.new(self, name)).update(options, &block)
- end
-
- def event_fired_callback
- @event_fired_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_fired'
- end
-
- def event_failed_callback
- @event_failed_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_failed'
- end
- end
- end
-end
@@ -1,47 +0,0 @@
-module ActiveModel
- module StateMachine
- class State
- attr_reader :name, :options
-
- def initialize(name, options = {})
- @name = name
- if machine = options.delete(:machine)
- machine.klass.define_state_query_method(name)
- end
- update(options)
- end
-
- def ==(state)
- if state.is_a? Symbol
- name == state
- else
- name == state.name
- end
- end
-
- def call_action(action, record)
- action = @options[action]
- case action
- when Symbol, String
- record.send(action)
- when Proc
- action.call(record)
- end
- end
-
- def display_name
- @display_name ||= name.to_s.gsub(/_/, ' ').capitalize
- end
-
- def for_select
- [display_name, name.to_s]
- end
-
- def update(options = {})
- if options.key?(:display) then @display_name = options.delete(:display) end
- @options = options
- self
- end
- end
- end
-end
@@ -1,40 +0,0 @@
-module ActiveModel
- module StateMachine
- class StateTransition
- attr_reader :from, :to, :options
-
- def initialize(opts)
- @from, @to, @guard, @on_transition = opts[:from], opts[:to], opts[:guard], opts[:on_transition]
- @options = opts
- end
-
- def perform(obj)
- case @guard
- when Symbol, String
- obj.send(@guard)
- when Proc
- @guard.call(obj)
- else
- true
- end
- end
-
- def execute(obj, *args)
- case @on_transition
- when Symbol, String
- obj.send(@on_transition, *args)
- when Proc
- @on_transition.call(obj, *args)
- end
- end
-
- def ==(obj)
- @from == obj.from && @to == obj.to
- end
-
- def from?(value)
- @from == value
- end
- end
- end
-end
@@ -1,49 +0,0 @@
-require 'cases/helper'
-
-class EventTest < ActiveModel::TestCase
- def setup
- @state_name = :close_order
- @success = :success_callback
- end
-
- def new_event
- @event = ActiveModel::StateMachine::Event.new(nil, @state_name, {:success => @success}) do
- transitions :to => :closed, :from => [:open, :received]
- end
- end
-
- test 'should set the name' do
- assert_equal @state_name, new_event.name
- end
-
- test 'should set the success option' do
- assert_equal @success, new_event.success
- end
-
- test 'should create StateTransitions' do
- ActiveModel::StateMachine::StateTransition.expects(:new).with(:to => :closed, :from => :open)
- ActiveModel::StateMachine::StateTransition.expects(:new).with(:to => :closed, :from => :received)
- new_event
- end
-end
-
-class EventBeingFiredTest < ActiveModel::TestCase
- test 'should raise an AASM::InvalidTransition error if the transitions are empty' do
- event = ActiveModel::StateMachine::Event.new(nil, :event)
-
- assert_raise ActiveModel::StateMachine::InvalidTransition do
- event.fire(nil)
- end
- end
-
- test 'should return the state of the first matching transition it finds' do
- event = ActiveModel::StateMachine::Event.new(nil, :event) do
- transitions :to => :closed, :from => [:open, :received]
- end
-
- obj = stub
- obj.stubs(:current_state).returns(:open)
-
- assert_equal :closed, event.fire(obj)
- end
-end
Oops, something went wrong. Retry.

9 comments on commit db49c70

@stephencelis

Was this extracted? Or just given the boot?

AASM is broken in Rails 3 (though easy to fix), so is there no current state machine solution without some hacking?

@bokmann

I hope to see this go back in - your comment is a bit ambiguous... will this be added back bafore 3.0 goes final? What was the problem that made you axe it from the beta?

@mikel
Member
mikel commented on db49c70 Feb 10, 2010

It was given the boot. Basically was not ready to ship as part of Rails 3.

Mainly in terms of the API it provided.

Please feel free to fix AASM so that we have a working State Machine implementation for Rails 3.

Mikel

@josh
Member
josh commented on db49c70 Feb 10, 2010

The lib was totally fine, it was just out of scope for core. Its not coming back.

We could turn it into a plugin if anyone will volunteer to maintain it.

@qoobaa
qoobaa commented on db49c70 Feb 20, 2010

I've extracted the code to gem: http://github.com/qoobaa/state_machine

Still few things need to be done:

  • invent a different name (activemodel-state_machine? isn't it too long?)
  • put appropriate copyright notice (who is the author of the code?)
  • write a documentation
@josh
Member
josh commented on db49c70 Feb 20, 2010

Rick Olson is the original author.

@qoobaa
qoobaa commented on db49c70 Feb 21, 2010

Thanks, I've put the copyright notices on the top of source files (I hope it was released on the MIT license as well).
Any suggestions about the name? :-)

@jnicklas

qoobaa, you might want to give it a different name, since there's already state_machine by pluginaweek (http://github.com/pluginaweek/state_machine) It'd be very confusing to have two different plugins that do the same thing with the same name.

I really encourage everyone here to take a look at state_machine (the pluginaweek solution) it really kicks AASM's butt pretty comprehensively. It also has a lot of features that the ActiveModel solution didn't have.

IMHO it's a good thing to leave this in plugin land, don't put it back in.

@ncr
ncr commented on db49c70 Feb 21, 2010

fsm would be cool but we need to get this out of the way: http://rubygems.org/gems/fsm

Please sign in to comment.