diff --git a/lib/aasm/aasm.rb b/lib/aasm/aasm.rb index d2d2af4..e9ffdc1 100644 --- a/lib/aasm/aasm.rb +++ b/lib/aasm/aasm.rb @@ -28,20 +28,20 @@ module AASM def aasm(*args, &block) if args[0].is_a?(Symbol) || args[0].is_a?(String) # using custom name - name = args[0].to_sym + state_machine_name = args[0].to_sym options = args[1] || {} else - # using the default name - name = :default + # using the default state_machine_name + state_machine_name = :default options = args[0] || {} end - AASM::StateMachine[self][name] ||= AASM::StateMachine.new + AASM::StateMachine[self][state_machine_name] ||= AASM::StateMachine.new(state_machine_name) @aasm ||= {} - @aasm[name] ||= AASM::Base.new(self, name, AASM::StateMachine[self][name], options) - @aasm[name].instance_eval(&block) if block # new DSL - @aasm[name] + @aasm[state_machine_name] ||= AASM::Base.new(self, state_machine_name, AASM::StateMachine[self][state_machine_name], options) + @aasm[state_machine_name].instance_eval(&block) if block # new DSL + @aasm[state_machine_name] end end # ClassMethods @@ -76,7 +76,7 @@ private event.fire_callbacks( :before, self, - *process_args(event, aasm.current_state, *args) + *process_args(event, aasm(state_machine_name).current_state, *args) ) if may_fire_to = event.may_fire?(self, *args) @@ -146,7 +146,7 @@ private end if AASM::StateMachine[self.class][state_machine_name].config.whiny_transitions - raise AASM::InvalidTransition.new(self, event_name) + raise AASM::InvalidTransition.new(self, event_name, state_machine_name) else false end diff --git a/lib/aasm/base.rb b/lib/aasm/base.rb index 071be04..85bb053 100644 --- a/lib/aasm/base.rb +++ b/lib/aasm/base.rb @@ -75,7 +75,7 @@ module AASM # define an event def event(name, options={}, &block) - @state_machine.events[name] = AASM::Core::Event.new(name, options, &block) + @state_machine.add_event(name, options, &block) if @klass.instance_methods.include?("may_#{name}?") warn "The event name #{name} is already used!" diff --git a/lib/aasm/core/event.rb b/lib/aasm/core/event.rb index aca4a1d..9aea427 100644 --- a/lib/aasm/core/event.rb +++ b/lib/aasm/core/event.rb @@ -2,10 +2,11 @@ module AASM::Core class Event include DslHelper - attr_reader :name, :options + attr_reader :name, :state_machine, :options - def initialize(name, options = {}, &block) + def initialize(name, state_machine, options = {}, &block) @name = name + @state_machine = state_machine @transitions = [] @guards = Array(options[:guard] || options[:guards] || options[:if]) @unless = Array(options[:unless]) #TODO: This could use a better name @@ -61,11 +62,11 @@ module AASM::Core if definitions # define new transitions # Create a separate transition for each from-state to the given state Array(definitions[:from]).each do |s| - @transitions << AASM::Core::Transition.new(attach_event_guards(definitions.merge(:from => s.to_sym)), &block) + @transitions << AASM::Core::Transition.new(self, attach_event_guards(definitions.merge(:from => s.to_sym)), &block) end # Create a transition if :to is specified without :from (transitions from ANY state) if @transitions.empty? && definitions[:to] - @transitions << AASM::Core::Transition.new(attach_event_guards(definitions), &block) + @transitions << AASM::Core::Transition.new(self, attach_event_guards(definitions), &block) end end @transitions @@ -89,7 +90,7 @@ module AASM::Core def _fire(obj, options={}, to_state=nil, *args) result = options[:test_only] ? false : nil if @transitions.map(&:from).any? - transitions = @transitions.select { |t| t.from == obj.aasm.current_state } + transitions = @transitions.select { |t| t.from == obj.aasm(state_machine.name).current_state } return result if transitions.size == 0 else transitions = @transitions diff --git a/lib/aasm/core/state.rb b/lib/aasm/core/state.rb index 20b549f..3ce3fab 100644 --- a/lib/aasm/core/state.rb +++ b/lib/aasm/core/state.rb @@ -1,10 +1,11 @@ module AASM::Core class State - attr_reader :name, :options + attr_reader :name, :state_machine, :options - def initialize(name, klass, options={}) + def initialize(name, klass, state_machine, options={}) @name = name @klass = klass + @state_machine = state_machine update(options) end diff --git a/lib/aasm/core/transition.rb b/lib/aasm/core/transition.rb index e4a80af..d13fa9a 100644 --- a/lib/aasm/core/transition.rb +++ b/lib/aasm/core/transition.rb @@ -2,12 +2,13 @@ module AASM::Core class Transition include DslHelper - attr_reader :from, :to, :opts + attr_reader :from, :to, :event, :opts alias_method :options, :opts - def initialize(opts, &block) + def initialize(event, opts, &block) add_options_from_dsl(opts, [:on_transition, :guard, :after], &block) if block + @event = event @from = opts[:from] @to = opts[:to] @guards = Array(opts[:guards]) + Array(opts[:guard]) + Array(opts[:if]) @@ -44,8 +45,8 @@ module AASM::Core def invoke_callbacks_compatible_with_guard(code, record, args, options={}) if record.respond_to?(:aasm) - record.aasm.from_state = @from if record.aasm.respond_to?(:from_state=) - record.aasm.to_state = @to if record.aasm.respond_to?(:to_state=) + record.aasm(event.state_machine.name).from_state = @from if record.aasm(event.state_machine.name).respond_to?(:from_state=) + record.aasm(event.state_machine.name).to_state = @to if record.aasm(event.state_machine.name).respond_to?(:to_state=) end case code diff --git a/lib/aasm/errors.rb b/lib/aasm/errors.rb index 7e36fd8..9c8637e 100644 --- a/lib/aasm/errors.rb +++ b/lib/aasm/errors.rb @@ -1,13 +1,14 @@ module AASM class InvalidTransition < RuntimeError - attr_reader :object, :event_name - def initialize(object, event_name) - @object, @event_name = object, event_name + attr_reader :object, :event_name, :state_machine_name + + def initialize(object, event_name, state_machine_name) + @object, @event_name, @state_machine_name = object, event_name, state_machine_name end def message - "Event '#{event_name}' cannot transition from '#{object.aasm.current_state}'" + "Event '#{event_name}' cannot transition from '#{object.aasm(state_machine_name).current_state}'" end end diff --git a/lib/aasm/instance_base.rb b/lib/aasm/instance_base.rb index a5c0b8e..64c7f58 100644 --- a/lib/aasm/instance_base.rb +++ b/lib/aasm/instance_base.rb @@ -30,7 +30,7 @@ module AASM end def human_state - AASM::Localizer.new.human_state_name(@instance.class, current_state) + AASM::Localizer.new.human_state_name(@instance.class, state_object_for_name(current_state)) end def states(options={}) diff --git a/lib/aasm/localizer.rb b/lib/aasm/localizer.rb index e1cd126..ec2ab44 100644 --- a/lib/aasm/localizer.rb +++ b/lib/aasm/localizer.rb @@ -21,7 +21,7 @@ module AASM def item_for(klass, state, ancestor, options={}) separator = options[:old_style] ? '.' : '/' - :"#{i18n_scope(klass)}.attributes.#{i18n_klass(ancestor)}.#{klass.aasm.attribute_name}#{separator}#{state}" + :"#{i18n_scope(klass)}.attributes.#{i18n_klass(ancestor)}.#{klass.aasm(state.state_machine.name).attribute_name}#{separator}#{state}" end def translate_queue(checklist) diff --git a/lib/aasm/state_machine.rb b/lib/aasm/state_machine.rb index d327f5a..001387a 100644 --- a/lib/aasm/state_machine.rb +++ b/lib/aasm/state_machine.rb @@ -12,11 +12,12 @@ module AASM attr_accessor :states, :events, :initial_state, :config, :name - def initialize + def initialize(name) @initial_state = nil @states = [] @events = {} @config = AASM::Configuration.new + @name = name end # called internally by Ruby 1.9 after clone() @@ -26,13 +27,17 @@ module AASM @events = @events.dup end - def add_state(name, klass, options) - set_initial_state(name, options) + def add_state(state_name, klass, options) + set_initial_state(state_name, options) # allow reloading, extending or redefining a state - @states.delete(name) if @states.include?(name) + @states.delete(state_name) if @states.include?(state_name) - @states << AASM::Core::State.new(name, klass, options) + @states << AASM::Core::State.new(state_name, klass, self, options) + end + + def add_event(name, options, &block) + @events[name] = AASM::Core::Event.new(name, self, options, &block) end private diff --git a/spec/unit/event_spec.rb b/spec/unit/event_spec.rb index fb0bc24..cddeea9 100644 --- a/spec/unit/event_spec.rb +++ b/spec/unit/event_spec.rb @@ -1,8 +1,9 @@ require 'spec_helper' describe 'adding an event' do + let(:state_machine) { AASM::StateMachine.new(:name) } let(:event) do - AASM::Core::Event.new(:close_order, {:success => :success_callback}) do + AASM::Core::Event.new(:close_order, state_machine, {:success => :success_callback}) do before :before_callback after :after_callback transitions :to => :closed, :from => [:open, :received] @@ -35,8 +36,9 @@ describe 'adding an event' do end describe 'transition inspection' do + let(:state_machine) { AASM::StateMachine.new(:name) } let(:event) do - AASM::Core::Event.new(:run) do + AASM::Core::Event.new(:run, state_machine) do transitions :to => :running, :from => :sleeping end end @@ -59,8 +61,9 @@ describe 'transition inspection' do end describe 'transition inspection without from' do + let(:state_machine) { AASM::StateMachine.new(:name) } let(:event) do - AASM::Core::Event.new(:run) do + AASM::Core::Event.new(:run, state_machine) do transitions :to => :running end end @@ -76,15 +79,17 @@ describe 'transition inspection without from' do end describe 'firing an event' do + let(:state_machine) { AASM::StateMachine.new(:name) } + it 'should return nil if the transitions are empty' do obj = double('object', :aasm => double('aasm', :current_state => 'open')) - event = AASM::Core::Event.new(:event) + event = AASM::Core::Event.new(:event, state_machine) expect(event.fire(obj)).to be_nil end it 'should return the state of the first matching transition it finds' do - event = AASM::Core::Event.new(:event) do + event = AASM::Core::Event.new(:event, state_machine) do transitions :to => :closed, :from => [:open, :received] end @@ -94,7 +99,7 @@ describe 'firing an event' do end it 'should call the guard with the params passed in' do - event = AASM::Core::Event.new(:event) do + event = AASM::Core::Event.new(:event, state_machine) do transitions :to => :closed, :from => [:open, :received], :guard => :guard_fn end diff --git a/spec/unit/state_spec.rb b/spec/unit/state_spec.rb index f4ab6a8..8897b6d 100644 --- a/spec/unit/state_spec.rb +++ b/spec/unit/state_spec.rb @@ -1,13 +1,15 @@ require 'spec_helper' describe AASM::Core::State do + let(:state_machine) { AASM::StateMachine.new(:name) } + before(:each) do @name = :astate @options = { :crazy_custom_key => 'key' } end def new_state(options={}) - AASM::Core::State.new(@name, Conversation, @options.merge(options)) + AASM::Core::State.new(@name, Conversation, state_machine, @options.merge(options)) end it 'should set the name' do diff --git a/spec/unit/transition_spec.rb b/spec/unit/transition_spec.rb index 6d9194c..d10fef5 100644 --- a/spec/unit/transition_spec.rb +++ b/spec/unit/transition_spec.rb @@ -57,9 +57,12 @@ describe 'blocks' do end describe AASM::Core::Transition do + let(:state_machine) { AASM::StateMachine.new(:name) } + let(:event) { AASM::Core::Event.new(:event, state_machine) } + it 'should set from, to, and opts attr readers' do opts = {:from => 'foo', :to => 'bar', :guard => 'g'} - st = AASM::Core::Transition.new(opts) + st = AASM::Core::Transition.new(event, opts) expect(st.from).to eq(opts[:from]) expect(st.to).to eq(opts[:to]) @@ -71,7 +74,7 @@ describe AASM::Core::Transition do st = AASM::Core::Transition.allocate expect(st).to receive(:warn).with('[DEPRECATION] :on_transition is deprecated, use :after instead') - st.send :initialize, opts do + st.send :initialize, event, opts do guard :gg on_transition :after_callback end @@ -81,7 +84,7 @@ describe AASM::Core::Transition do it 'should set after and guard from dsl' do opts = {:from => 'foo', :to => 'bar', :guard => 'g'} - st = AASM::Core::Transition.new(opts) do + st = AASM::Core::Transition.new(event, opts) do guard :gg after :after_callback end @@ -92,7 +95,7 @@ describe AASM::Core::Transition do it 'should pass equality check if from and to are the same' do opts = {:from => 'foo', :to => 'bar', :guard => 'g'} - st = AASM::Core::Transition.new(opts) + st = AASM::Core::Transition.new(event, opts) obj = double('object') allow(obj).to receive(:from).and_return(opts[:from]) @@ -103,7 +106,7 @@ describe AASM::Core::Transition do it 'should fail equality check if from are not the same' do opts = {:from => 'foo', :to => 'bar', :guard => 'g'} - st = AASM::Core::Transition.new(opts) + st = AASM::Core::Transition.new(event, opts) obj = double('object') allow(obj).to receive(:from).and_return('blah') @@ -114,7 +117,7 @@ describe AASM::Core::Transition do it 'should fail equality check if to are not the same' do opts = {:from => 'foo', :to => 'bar', :guard => 'g'} - st = AASM::Core::Transition.new(opts) + st = AASM::Core::Transition.new(event, opts) obj = double('object') allow(obj).to receive(:from).and_return(opts[:from]) @@ -125,16 +128,19 @@ describe AASM::Core::Transition do end describe AASM::Core::Transition, '- when performing guard checks' do + let(:state_machine) { AASM::StateMachine.new(:name) } + let(:event) { AASM::Core::Event.new(:event, state_machine) } + it 'should return true of there is no guard' do opts = {:from => 'foo', :to => 'bar'} - st = AASM::Core::Transition.new(opts) + st = AASM::Core::Transition.new(event, opts) expect(st.allowed?(nil)).to be_truthy end it 'should call the method on the object if guard is a symbol' do opts = {:from => 'foo', :to => 'bar', :guard => :test} - st = AASM::Core::Transition.new(opts) + st = AASM::Core::Transition.new(event, opts) obj = double('object') expect(obj).to receive(:test) @@ -144,7 +150,7 @@ describe AASM::Core::Transition, '- when performing guard checks' do it 'should call the method on the object if unless is a symbol' do opts = {:from => 'foo', :to => 'bar', :unless => :test} - st = AASM::Core::Transition.new(opts) + st = AASM::Core::Transition.new(event, opts) obj = double('object') expect(obj).to receive(:test) @@ -154,7 +160,7 @@ describe AASM::Core::Transition, '- when performing guard checks' do it 'should call the method on the object if guard is a string' do opts = {:from => 'foo', :to => 'bar', :guard => 'test'} - st = AASM::Core::Transition.new(opts) + st = AASM::Core::Transition.new(event, opts) obj = double('object') expect(obj).to receive(:test) @@ -164,7 +170,7 @@ describe AASM::Core::Transition, '- when performing guard checks' do it 'should call the method on the object if unless is a string' do opts = {:from => 'foo', :to => 'bar', :unless => 'test'} - st = AASM::Core::Transition.new(opts) + st = AASM::Core::Transition.new(event, opts) obj = double('object') expect(obj).to receive(:test) @@ -174,7 +180,7 @@ describe AASM::Core::Transition, '- when performing guard checks' do it 'should call the proc passing the object if the guard is a proc' do opts = {:from => 'foo', :to => 'bar', :guard => Proc.new { test }} - st = AASM::Core::Transition.new(opts) + st = AASM::Core::Transition.new(event, opts) obj = double('object') expect(obj).to receive(:test) @@ -184,9 +190,12 @@ describe AASM::Core::Transition, '- when performing guard checks' do end describe AASM::Core::Transition, '- when executing the transition with a Proc' do + let(:state_machine) { AASM::StateMachine.new(:name) } + let(:event) { AASM::Core::Event.new(:event, state_machine) } + it 'should call a Proc on the object with args' do opts = {:from => 'foo', :to => 'bar', :after => Proc.new {|a| test(a) }} - st = AASM::Core::Transition.new(opts) + st = AASM::Core::Transition.new(event, opts) args = {:arg1 => '1', :arg2 => '2'} obj = double('object', :aasm => 'aasm') @@ -202,7 +211,7 @@ describe AASM::Core::Transition, '- when executing the transition with a Proc' d prc = Proc.new { prc_object = self } opts = {:from => 'foo', :to => 'bar', :after => prc } - st = AASM::Core::Transition.new(opts) + st = AASM::Core::Transition.new(event, opts) args = {:arg1 => '1', :arg2 => '2'} obj = double('object', :aasm => 'aasm') @@ -212,9 +221,12 @@ describe AASM::Core::Transition, '- when executing the transition with a Proc' d end describe AASM::Core::Transition, '- when executing the transition with an :after method call' do + let(:state_machine) { AASM::StateMachine.new(:name) } + let(:event) { AASM::Core::Event.new(:event, state_machine) } + it 'should accept a String for the method name' do opts = {:from => 'foo', :to => 'bar', :after => 'test'} - st = AASM::Core::Transition.new(opts) + st = AASM::Core::Transition.new(event, opts) args = {:arg1 => '1', :arg2 => '2'} obj = double('object', :aasm => 'aasm') @@ -225,7 +237,7 @@ describe AASM::Core::Transition, '- when executing the transition with an :after it 'should accept a Symbol for the method name' do opts = {:from => 'foo', :to => 'bar', :after => :test} - st = AASM::Core::Transition.new(opts) + st = AASM::Core::Transition.new(event, opts) args = {:arg1 => '1', :arg2 => '2'} obj = double('object', :aasm => 'aasm') @@ -236,7 +248,7 @@ describe AASM::Core::Transition, '- when executing the transition with an :after it 'should pass args if the target method accepts them' do opts = {:from => 'foo', :to => 'bar', :after => :test} - st = AASM::Core::Transition.new(opts) + st = AASM::Core::Transition.new(event, opts) args = {:arg1 => '1', :arg2 => '2'} obj = double('object', :aasm => 'aasm') @@ -251,7 +263,7 @@ describe AASM::Core::Transition, '- when executing the transition with an :after it 'should NOT pass args if the target method does NOT accept them' do opts = {:from => 'foo', :to => 'bar', :after => :test} - st = AASM::Core::Transition.new(opts) + st = AASM::Core::Transition.new(event, opts) args = {:arg1 => '1', :arg2 => '2'} obj = double('object', :aasm => 'aasm') @@ -266,7 +278,7 @@ describe AASM::Core::Transition, '- when executing the transition with an :after it 'should allow accessing the from_state and the to_state' do opts = {:from => 'foo', :to => 'bar', :after => :test} - transition = AASM::Core::Transition.new(opts) + transition = AASM::Core::Transition.new(event, opts) args = {:arg1 => '1', :arg2 => '2'} obj = double('object', :aasm => AASM::InstanceBase.new('object'))