require File.expand_path(File.dirname(__FILE__) + '/../../test_helper') require 'active_record' FIXTURES_ROOT = File.dirname(__FILE__) + '/../../fixtures/' # Load TestCase helpers require 'active_support/test_case' require 'active_record/fixtures' begin require 'active_record/test_case' rescue LoadError class ActiveRecord::TestCase < ActiveSupport::TestCase self.fixture_path = FIXTURES_ROOT self.use_instantiated_fixtures = false self.use_transactional_fixtures = true end end require 'active_record/version' if ActiveRecord::VERSION::MAJOR >= 4 require 'rails/observers/activerecord/active_record' require 'active_record/mass_assignment_security' end # Establish database connection ActiveRecord::Base.establish_connection('adapter' => RUBY_PLATFORM == 'java' ? 'jdbcsqlite3' : 'sqlite3', 'database' => ':memory:') ActiveRecord::Base.logger = Logger.new("#{File.dirname(__FILE__)}/../../active_record.log") module ActiveRecordTest class BaseTestCase < ActiveRecord::TestCase def default_test end protected # Creates a new ActiveRecord model (and the associated table) def new_model(create_table = :foo, &block) name = create_table || :foo table_name = "#{name}_#{rand(1000000)}" model = Class.new(ActiveRecord::Base) do self.table_name = table_name.to_s connection.create_table(table_name, :force => true) {|t| t.string(:state)} if create_table (class << self; self; end).class_eval do define_method(:name) { "ActiveRecordTest::#{name.to_s.capitalize}" } end end model.class_eval(&block) if block_given? model.reset_column_information if create_table model end # Creates a new ActiveRecord observer def new_observer(model, &block) observer = Class.new(ActiveRecord::Observer) do attr_accessor :notifications def initialize super @notifications = [] end end (class << observer; self; end).class_eval do define_method(:name) do "#{model.name}Observer" end end observer.observe(model) observer.class_eval(&block) if block_given? observer end end class IntegrationTest < BaseTestCase def test_should_have_an_integration_name assert_equal :active_record, EnumStateMachine::Integrations::ActiveRecord.integration_name end def test_should_be_available assert EnumStateMachine::Integrations::ActiveRecord.available? end def test_should_match_if_class_inherits_from_active_record assert EnumStateMachine::Integrations::ActiveRecord.matches?(new_model) end def test_should_not_match_if_class_does_not_inherit_from_active_record assert !EnumStateMachine::Integrations::ActiveRecord.matches?(Class.new) end def test_should_have_defaults assert_equal({:action => :save}, EnumStateMachine::Integrations::ActiveRecord.defaults) end def test_should_have_a_locale_path assert_not_nil EnumStateMachine::Integrations::ActiveRecord.locale_path end end class MachineWithoutDatabaseTest < BaseTestCase def setup @model = new_model(false) do # Simulate the database not being available entirely def self.connection raise ActiveRecord::ConnectionNotEstablished end def self.connected? false end end end def test_should_allow_machine_creation assert_nothing_raised { EnumStateMachine::Machine.new(@model) } end end class MachineUnmigratedTest < BaseTestCase def setup @model = new_model(false) # Drop the table so that it definitely doesn't exist @model.connection.drop_table(@model.table_name) if @model.table_exists? end def test_should_allow_machine_creation assert_nothing_raised { EnumStateMachine::Machine.new(@model) } end end class MachineByDefaultTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model) end def test_should_use_save_as_action assert_equal :save, @machine.action end def test_should_use_transactions assert_equal true, @machine.use_transactions end def test_should_create_notifier_before_callback assert_equal 1, @machine.callbacks[:before].size end def test_should_create_notifier_after_callback assert_equal 1, @machine.callbacks[:after].size end end class MachineWithStatesTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model) @machine.state :first_gear end def test_should_humanize_name assert_equal 'first gear', @machine.state(:first_gear).human_name end end class MachineWithStaticInitialStateTest < BaseTestCase def setup @model = new_model(:vehicle) do attr_accessor :value end @machine = EnumStateMachine::Machine.new(@model, :initial => :parked) end def test_should_set_initial_state_on_created_object record = @model.new assert_equal 'parked', record.state end def test_should_set_initial_state_with_nil_attributes record = @model.new(nil) assert_equal 'parked', record.state end def test_should_still_set_attributes record = @model.new(:value => 1) assert_equal 1, record.value end def test_should_still_allow_initialize_blocks block_args = nil record = @model.new do |*args| block_args = args end assert_equal [record], block_args end def test_should_set_attributes_prior_to_initialize_block state = nil @model.new do |record| state = record.state end assert_equal 'parked', state end def test_should_set_attributes_prior_to_after_initialize_hook state = nil @model.class_eval {define_method(:after_initialize) {}} if ActiveRecord::VERSION::MAJOR <= 2 @model.after_initialize do |record| state = record.state end @model.new assert_equal 'parked', state end def test_should_set_initial_state_before_setting_attributes @model.class_eval do attr_accessor :state_during_setter remove_method :value= define_method(:value=) do |value| self.state_during_setter = state end end record = @model.new(:value => 1) assert_equal 'parked', record.state_during_setter end def test_should_not_set_initial_state_after_already_initialized record = @model.new(:value => 1) assert_equal 'parked', record.state record.state = 'idling' record.attributes = {} assert_equal 'idling', record.state end def test_should_persist_initial_state record = @model.new record.save record.reload assert_equal 'parked', record.state end unless ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0 def test_should_persist_initial_state_on_dup record = @model.create.dup record.save record.reload assert_equal 'parked', record.state end end def test_should_use_stored_values_when_loading_from_database @machine.state :idling record = @model.find(@model.create(:state => 'idling').id) assert_equal 'idling', record.state end def test_should_use_stored_values_when_loading_from_database_with_nil_state @machine.state nil record = @model.find(@model.create(:state => nil).id) assert_nil record.state end def test_should_use_stored_values_when_loading_for_many_association @machine.state :idling @model.connection.add_column @model.table_name, :owner_id, :integer @model.reset_column_information ActiveRecordTest.const_set('Vehicle', @model) owner_model = new_model(:owner) do has_many :vehicles, :class_name => 'ActiveRecordTest::Vehicle' end ActiveRecordTest.const_set('Owner', owner_model) owner = owner_model.create record = @model.create(:state => 'idling', :owner_id => owner.id) assert_equal 'idling', owner.vehicles[0].state end def test_should_use_stored_values_when_loading_for_one_association @machine.state :idling @model.connection.add_column @model.table_name, :owner_id, :integer @model.reset_column_information ActiveRecordTest.const_set('Vehicle', @model) owner_model = new_model(:owner) do has_one :vehicle, :class_name => 'ActiveRecordTest::Vehicle' end ActiveRecordTest.const_set('Owner', owner_model) owner = owner_model.create record = @model.create(:state => 'idling', :owner_id => owner.id) assert_equal 'idling', owner.vehicle.state end def test_should_use_stored_values_when_loading_for_belongs_to_association @machine.state :idling ActiveRecordTest.const_set('Vehicle', @model) driver_model = new_model(:driver) do connection.add_column table_name, :vehicle_id, :integer belongs_to :vehicle, :class_name => 'ActiveRecordTest::Vehicle' end ActiveRecordTest.const_set('Driver', driver_model) record = @model.create(:state => 'idling') driver = driver_model.create(:vehicle_id => record.id) assert_equal 'idling', driver.vehicle.state end def teardown ActiveRecordTest.class_eval do remove_const('Vehicle') if defined?(ActiveRecordTest::Vehicle) remove_const('Owner') if defined?(ActiveRecordTest::Owner) remove_const('Driver') if defined?(ActiveRecordTest::Driver) end ActiveSupport::Dependencies.clear if defined?(ActiveSupport::Dependencies) super end end class MachineWithDynamicInitialStateTest < BaseTestCase def setup @model = new_model do attr_accessor :value end @machine = EnumStateMachine::Machine.new(@model, :initial => lambda {|object| :parked}) @machine.state :parked end def test_should_set_initial_state_on_created_object record = @model.new assert_equal 'parked', record.state end def test_should_still_set_attributes record = @model.new(:value => 1) assert_equal 1, record.value end def test_should_still_allow_initialize_blocks block_args = nil record = @model.new do |*args| block_args = args end assert_equal [record], block_args end def test_should_set_attributes_prior_to_initialize_block state = nil @model.new do |record| state = record.state end assert_equal 'parked', state end def test_should_set_attributes_prior_to_after_initialize_hook state = nil @model.class_eval {define_method(:after_initialize) {}} if ActiveRecord::VERSION::MAJOR <= 2 @model.after_initialize do |record| state = record.state end @model.new assert_equal 'parked', state end def test_should_set_initial_state_after_setting_attributes @model.class_eval do attr_accessor :state_during_setter remove_method :value= define_method(:value=) do |value| self.state_during_setter = state || 'nil' end end record = @model.new(:value => 1) assert_equal 'nil', record.state_during_setter end def test_should_not_set_initial_state_after_already_initialized record = @model.new(:value => 1) assert_equal 'parked', record.state record.state = 'idling' record.attributes = {} assert_equal 'idling', record.state end def test_should_persist_initial_state record = @model.new record.save record.reload assert_equal 'parked', record.state end unless ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0 def test_should_persist_initial_state_on_dup record = @model.create.dup record.save record.reload assert_equal 'parked', record.state end end def test_should_use_stored_values_when_loading_from_database @machine.state :idling record = @model.find(@model.create(:state => 'idling').id) assert_equal 'idling', record.state end def test_should_use_stored_values_when_loading_from_database_with_nil_state @machine.state nil record = @model.find(@model.create(:state => nil).id) assert_nil record.state end end class MachineWithEventsTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model) @machine.event :shift_up end def test_should_humanize_name assert_equal 'shift up', @machine.event(:shift_up).human_name end end class MachineWithSameColumnDefaultTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @model = new_model do connection.add_column table_name, :status, :string, :default => 'parked' end @machine = EnumStateMachine::Machine.new(@model, :status, :initial => :parked) @record = @model.new end def test_should_use_machine_default assert_equal 'parked', @record.status end def test_should_not_generate_a_warning assert_no_match(/have defined a different default/, $stderr.string) end def teardown $stderr = @original_stderr super end end class MachineWithDifferentColumnDefaultTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @model = new_model do connection.add_column table_name, :status, :string, :default => 'idling' end @machine = EnumStateMachine::Machine.new(@model, :status, :initial => :parked) @record = @model.new end def test_should_use_machine_default assert_equal 'parked', @record.status end def test_should_generate_a_warning assert_match(/Both ActiveRecordTest::Foo and its :status machine have defined a different default for "status". Use only one or the other for defining defaults to avoid unexpected behaviors\./, $stderr.string) end def teardown $stderr = @original_stderr super end end class MachineWithDifferentIntegerColumnDefaultTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @model = new_model do connection.add_column table_name, :status, :integer, :default => 0 end @machine = EnumStateMachine::Machine.new(@model, :status, :initial => :parked) @machine.state :parked, :value => 1 @record = @model.new end def test_should_use_machine_default assert_equal 1, @record.status end def test_should_generate_a_warning assert_match(/Both ActiveRecordTest::Foo and its :status machine have defined a different default for "status". Use only one or the other for defining defaults to avoid unexpected behaviors\./, $stderr.string) end def teardown $stderr = @original_stderr super end end class MachineWithConflictingPredicateTest < BaseTestCase def setup @model = new_model do def state?(*args) true end end @machine = EnumStateMachine::Machine.new(@model) @record = @model.new end def test_should_not_define_attribute_predicate assert @record.state? end end class MachineWithConflictingStateNameTest < BaseTestCase def setup require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new @model = new_model end def test_should_output_warning_with_same_machine_name @machine = EnumStateMachine::Machine.new(@model) @machine.state :state assert_match(/^Instance method "state\?" is already defined in ActiveRecordTest::Foo, use generic helper instead.*\n$/, $stderr.string) end def test_should_output_warning_with_same_machine_attribute @machine = EnumStateMachine::Machine.new(@model, :public_state, :attribute => :state) @machine.state :state assert_match(/^Instance method "state\?" is already defined in ActiveRecordTest::Foo, use generic helper instead.*\n$/, $stderr.string) end def teardown $stderr = @original_stderr super end end class MachineWithColumnStateAttributeTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model, :initial => :parked) @machine.other_states(:idling) @record = @model.new end def test_should_not_override_the_column_reader @record[:state] = 'parked' assert_equal 'parked', @record.state end def test_should_not_override_the_column_writer @record.state = 'parked' assert_equal 'parked', @record[:state] end def test_should_have_an_attribute_predicate assert @record.respond_to?(:state?) end def test_should_test_for_existence_on_predicate_without_parameters assert @record.state? @record.state = nil assert !@record.state? end def test_should_return_false_for_predicate_if_does_not_match_current_value assert !@record.state?(:idling) end def test_should_return_true_for_predicate_if_matches_current_value assert @record.state?(:parked) end def test_should_raise_exception_for_predicate_if_invalid_state_specified assert_raise(IndexError) { @record.state?(:invalid) } end end class MachineWithNonColumnStateAttributeUndefinedTest < BaseTestCase def setup @model = new_model do def initialize # Skip attribute initialization @initialized_state_machines = true super end end @machine = EnumStateMachine::Machine.new(@model, :status, :initial => :parked) @machine.other_states(:idling) @record = @model.new end def test_should_not_define_a_column_for_the_attribute assert_nil @model.columns_hash['status'] end def test_should_define_a_reader_attribute_for_the_attribute assert @record.respond_to?(:status) end def test_should_define_a_writer_attribute_for_the_attribute assert @record.respond_to?(:status=) end def test_should_define_an_attribute_predicate assert @record.respond_to?(:status?) end end class MachineWithNonColumnStateAttributeDefinedTest < BaseTestCase def setup @model = new_model do def status=(value) self['status'] = value end def status self['status'] end end @machine = EnumStateMachine::Machine.new(@model, :status, :initial => :parked) @machine.other_states(:idling) @record = @model.new end def test_should_return_false_for_predicate_if_does_not_match_current_value assert !@record.status?(:idling) end def test_should_return_true_for_predicate_if_matches_current_value assert @record.status?(:parked) end def test_should_raise_exception_for_predicate_if_invalid_state_specified assert_raise(IndexError) { @record.status?(:invalid) } end def test_should_set_initial_state_on_created_object assert_equal 'parked', @record.status end end class MachineWithAliasedAttributeTest < BaseTestCase def setup @model = new_model do alias_attribute :vehicle_status, :state end @machine = EnumStateMachine::Machine.new(@model, :status, :attribute => :vehicle_status) @machine.state :parked @record = @model.new end def test_should_check_custom_attribute_for_predicate @record.vehicle_status = nil assert !@record.status?(:parked) @record.vehicle_status = 'parked' assert @record.status?(:parked) end end class MachineWithCustomAttributeTest < BaseTestCase def setup require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new @model = new_model @machine = EnumStateMachine::Machine.new(@model, :public_state, :attribute => :state) @record = @model.new end def test_should_not_delegate_attribute_predicate_with_different_attribute assert_raise(ArgumentError) { @record.public_state? } end def teardown $stderr = @original_stderr super end end class MachineWithInitializedStateTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model, :initial => :parked) @machine.state :idling end def test_should_allow_nil_initial_state_when_static @machine.state nil record = @model.new(:state => nil) assert_nil record.state end def test_should_allow_nil_initial_state_when_dynamic @machine.state nil @machine.initial_state = lambda {:parked} record = @model.new(:state => nil) assert_nil record.state end def test_should_allow_different_initial_state_when_static record = @model.new(:state => 'idling') assert_equal 'idling', record.state end def test_should_allow_different_initial_state_when_dynamic @machine.initial_state = lambda {:parked} record = @model.new(:state => 'idling') assert_equal 'idling', record.state end def test_should_use_default_state_if_protected @model.class_eval do attr_protected :state end record = @model.new(:state => 'idling') assert_equal 'parked', record.state end end class MachineMultipleTest < BaseTestCase def setup @model = new_model do connection.add_column table_name, :status, :string end @state_machine = EnumStateMachine::Machine.new(@model, :initial => :parked) @status_machine = EnumStateMachine::Machine.new(@model, :status, :initial => :idling) end def test_should_should_initialize_each_state record = @model.new assert_equal 'parked', record.state assert_equal 'idling', record.status end end class MachineWithLoopbackTest < BaseTestCase def setup @model = new_model do connection.add_column table_name, :updated_at, :datetime end @machine = EnumStateMachine::Machine.new(@model, :initial => :parked) @machine.event :park @record = @model.create(:updated_at => Time.now - 1) @transition = EnumStateMachine::Transition.new(@record, @machine, :park, :parked, :parked) @timestamp = @record.updated_at @transition.perform end if ActiveRecord.const_defined?(:Dirty) || ActiveRecord::AttributeMethods.const_defined?(:Dirty) def test_should_not_update_record assert_equal @timestamp, @record.updated_at end else def test_should_update_record assert_not_equal @timestamp, @record.updated_at end end end if ActiveRecord.const_defined?(:Dirty) || ActiveRecord::AttributeMethods.const_defined?(:Dirty) class MachineWithDirtyAttributesTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model, :initial => :parked) @machine.event :ignite @machine.state :idling @record = @model.create @transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @transition.perform(false) end def test_should_include_state_in_changed_attributes assert_equal %w(state), @record.changed end def test_should_track_attribute_change assert_equal %w(parked idling), @record.changes['state'] end def test_should_not_reset_changes_on_multiple_transitions transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling) transition.perform(false) assert_equal %w(parked idling), @record.changes['state'] end def test_should_not_have_changes_when_loaded_from_database record = @model.find(@record.id) assert !record.changed? end end class MachineWithDirtyAttributesDuringLoopbackTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model, :initial => :parked) @machine.event :park @record = @model.create @transition = EnumStateMachine::Transition.new(@record, @machine, :park, :parked, :parked) @transition.perform(false) end def test_should_not_include_state_in_changed_attributes assert_equal [], @record.changed end def test_should_not_track_attribute_changes assert_equal nil, @record.changes['state'] end end class MachineWithDirtyAttributesAndCustomAttributeTest < BaseTestCase def setup @model = new_model do connection.add_column table_name, :status, :string end @machine = EnumStateMachine::Machine.new(@model, :status, :initial => :parked) @machine.event :ignite @machine.state :idling @record = @model.create @transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @transition.perform(false) end def test_should_include_state_in_changed_attributes assert_equal %w(status), @record.changed end def test_should_track_attribute_change assert_equal %w(parked idling), @record.changes['status'] end def test_should_not_reset_changes_on_multiple_transitions transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling) transition.perform(false) assert_equal %w(parked idling), @record.changes['status'] end end class MachineWithDirtyAttributeAndCustomAttributesDuringLoopbackTest < BaseTestCase def setup @model = new_model do connection.add_column table_name, :status, :string end @machine = EnumStateMachine::Machine.new(@model, :status, :initial => :parked) @machine.event :park @record = @model.create @transition = EnumStateMachine::Transition.new(@record, @machine, :park, :parked, :parked) @transition.perform(false) end def test_should_not_include_state_in_changed_attributes assert_equal [], @record.changed end def test_should_not_track_attribute_changes assert_equal nil, @record.changes['status'] end end class MachineWithDirtyAttributeAndStateEventsTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model, :initial => :parked) @machine.event :ignite @record = @model.create @record.state_event = 'ignite' end def test_should_not_include_state_in_changed_attributes assert_equal [], @record.changed end def test_should_not_track_attribute_change assert_equal nil, @record.changes['state'] end end else $stderr.puts 'Skipping ActiveRecord Dirty tests.' end class MachineWithoutTransactionsTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model, :use_transactions => false) end def test_should_not_rollback_transaction_if_false @machine.within_transaction(@model.new) do @model.create false end assert_equal 1, @model.count end def test_should_not_rollback_transaction_if_true @machine.within_transaction(@model.new) do @model.create true end assert_equal 1, @model.count end end class MachineWithTransactionsTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model, :use_transactions => true) end def test_should_rollback_transaction_if_false @machine.within_transaction(@model.new) do @model.create false end assert_equal 0, @model.count end def test_should_not_rollback_transaction_if_true @machine.within_transaction(@model.new) do @model.create true end assert_equal 1, @model.count end end class MachineWithCallbacksTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model, :initial => :parked) @machine.other_states :idling @machine.event :ignite @record = @model.new(:state => 'parked') @transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) end def test_should_run_before_callbacks called = false @machine.before_transition {called = true} @transition.perform assert called end def test_should_pass_record_to_before_callbacks_with_one_argument record = nil @machine.before_transition {|arg| record = arg} @transition.perform assert_equal @record, record end def test_should_pass_record_and_transition_to_before_callbacks_with_multiple_arguments callback_args = nil @machine.before_transition {|*args| callback_args = args} @transition.perform assert_equal [@record, @transition], callback_args end def test_should_run_before_callbacks_outside_the_context_of_the_record context = nil @machine.before_transition {context = self} @transition.perform assert_equal self, context end def test_should_run_after_callbacks called = false @machine.after_transition {called = true} @transition.perform assert called end def test_should_pass_record_to_after_callbacks_with_one_argument record = nil @machine.after_transition {|arg| record = arg} @transition.perform assert_equal @record, record end def test_should_pass_record_and_transition_to_after_callbacks_with_multiple_arguments callback_args = nil @machine.after_transition {|*args| callback_args = args} @transition.perform assert_equal [@record, @transition], callback_args end def test_should_run_after_callbacks_outside_the_context_of_the_record context = nil @machine.after_transition {context = self} @transition.perform assert_equal self, context end def test_should_run_after_callbacks_if_model_callback_added_prior_to_state_machine_definition model = new_model do after_save { nil } end machine = EnumStateMachine::Machine.new(model, :initial => :parked) machine.other_states :idling machine.event :ignite after_called = false machine.after_transition {after_called = true} record = model.new(:state => 'parked') transition = EnumStateMachine::Transition.new(record, machine, :ignite, :parked, :idling) transition.perform assert_equal true, after_called end def test_should_run_around_callbacks before_called = false after_called = false ensure_called = 0 @machine.around_transition do |block| before_called = true begin block.call ensure ensure_called += 1 end after_called = true end @transition.perform assert before_called assert after_called assert_equal ensure_called, 1 end def test_should_include_transition_states_in_known_states @machine.before_transition :to => :first_gear, :do => lambda {} assert_equal [:parked, :idling, :first_gear], @machine.states.map {|state| state.name} end def test_should_allow_symbolic_callbacks callback_args = nil klass = class << @record; self; end klass.send(:define_method, :after_ignite) do |*args| callback_args = args end @machine.before_transition(:after_ignite) @transition.perform assert_equal [@transition], callback_args end def test_should_allow_string_callbacks class << @record attr_reader :callback_result end @machine.before_transition('@callback_result = [1, 2, 3]') @transition.perform assert_equal [1, 2, 3], @record.callback_result end def test_should_run_in_expected_order expected = [ :before_transition, :before_validation, :after_validation, :before_save, :before_create, :after_create, :after_save, :after_transition ] callbacks = [] @model.before_validation { callbacks << :before_validation } @model.after_validation { callbacks << :after_validation } @model.before_save { callbacks << :before_save } @model.before_create { callbacks << :before_create } @model.after_create { callbacks << :after_create } @model.after_save { callbacks << :after_save } if @model.respond_to?(:after_commit) @model.after_commit { callbacks << :after_commit } expected << :after_commit end @machine.before_transition { callbacks << :before_transition } @machine.after_transition { callbacks << :after_transition } @transition.perform assert_equal expected, callbacks end end class MachineWithFailedBeforeCallbacksTest < BaseTestCase def setup @callbacks = [] @model = new_model @machine = EnumStateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @machine.before_transition {@callbacks << :before_1; false} @machine.before_transition {@callbacks << :before_2} @machine.after_transition {@callbacks << :after} @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after} @record = @model.new(:state => 'parked') @transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform end def test_should_not_be_successful assert !@result end def test_should_not_change_current_state assert_equal 'parked', @record.state end def test_should_not_run_action assert @record.new_record? end def test_should_not_run_further_callbacks assert_equal [:before_1], @callbacks end end class MachineNestedActionTest < BaseTestCase def setup @callbacks = [] @model = new_model @machine = EnumStateMachine::Machine.new(@model) @machine.event :ignite do transition :parked => :idling end @record = @model.new(:state => 'parked') end def test_should_allow_transition_prior_to_creation_if_skipping_action record = @record @model.before_create { record.ignite(false) } result = @record.save assert_equal true, result assert_equal "idling", @record.state @record.reload assert_equal "idling", @record.state end def test_should_allow_transition_after_creation record = @record @model.after_create { record.ignite } result = @record.save assert_equal true, result assert_equal "idling", @record.state @record.reload assert_equal "idling", @record.state end end class MachineWithFailedActionTest < BaseTestCase def setup @model = new_model do validates_inclusion_of :state, :in => %w(first_gear) end @machine = EnumStateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @callbacks = [] @machine.before_transition {@callbacks << :before} @machine.after_transition {@callbacks << :after} @machine.after_failure {@callbacks << :after_failure} @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after} @record = @model.new(:state => 'parked') @transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform end def test_should_not_be_successful assert !@result end def test_should_not_change_current_state assert_equal 'parked', @record.state end def test_should_not_save_record assert @record.new_record? end def test_should_run_before_callbacks_and_after_callbacks_with_failures assert_equal [:before, :around_before, :after_failure], @callbacks end end class MachineWithFailedAfterCallbacksTest < BaseTestCase def setup @callbacks = [] @model = new_model @machine = EnumStateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @machine.after_transition {@callbacks << :after_1; false} @machine.after_transition {@callbacks << :after_2} @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after} @record = @model.new(:state => 'parked') @transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform end def test_should_be_successful assert @result end def test_should_change_current_state assert_equal 'idling', @record.state end def test_should_save_record assert !@record.new_record? end def test_should_not_run_further_after_callbacks assert_equal [:around_before, :around_after, :after_1], @callbacks end end class MachineWithValidationsTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model) @machine.state :parked @record = @model.new end def test_should_invalidate_using_errors I18n.backend = I18n::Backend::Simple.new if Object.const_defined?(:I18n) @record.state = 'parked' @machine.invalidate(@record, :state, :invalid_transition, [[:event, 'park']]) assert_equal ['State cannot transition via "park"'], @record.errors.full_messages end def test_should_auto_prefix_custom_attributes_on_invalidation @machine.invalidate(@record, :event, :invalid) assert_equal ['State event is invalid'], @record.errors.full_messages end def test_should_clear_errors_on_reset @record.state = 'parked' @record.errors.add(:state, 'is invalid') @machine.reset(@record) assert_equal [], @record.errors.full_messages end def test_should_be_valid_if_state_is_known @record.state = 'parked' assert @record.valid? end def test_should_not_be_valid_if_state_is_unknown @record.state = 'invalid' assert !@record.valid? assert_equal ['State is invalid'], @record.errors.full_messages end end class MachineWithValidationsAndCustomAttributeTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model, :status, :attribute => :state) @machine.state :parked @record = @model.new end def test_should_add_validation_errors_to_custom_attribute @record.state = 'invalid' assert !@record.valid? assert_equal ['State is invalid'], @record.errors.full_messages @record.state = 'parked' assert @record.valid? end end class MachineErrorsTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model) @record = @model.new end def test_should_be_able_to_describe_current_errors @record.errors.add(:id, 'cannot be blank') @record.errors.add(:state, 'is invalid') assert_equal ['Id cannot be blank', 'State is invalid'], @machine.errors_for(@record).split(', ').sort end def test_should_describe_as_halted_with_no_errors assert_equal 'Transition halted', @machine.errors_for(@record) end end class MachineWithStateDrivenValidationsTest < BaseTestCase def setup @model = new_model do attr_accessor :seatbelt end @machine = EnumStateMachine::Machine.new(@model) @machine.state :first_gear, :second_gear do validates_presence_of :seatbelt end @machine.other_states :parked end def test_should_be_valid_if_validation_fails_outside_state_scope record = @model.new(:state => 'parked', :seatbelt => nil) assert record.valid? end def test_should_be_invalid_if_validation_fails_within_state_scope record = @model.new(:state => 'first_gear', :seatbelt => nil) assert !record.valid? end def test_should_be_valid_if_validation_succeeds_within_state_scope record = @model.new(:state => 'second_gear', :seatbelt => true) assert record.valid? end end class MachineWithEventAttributesOnValidationTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model) @machine.event :ignite do transition :parked => :idling end @record = @model.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_fail_if_event_is_invalid @record.state_event = 'invalid' assert !@record.valid? assert_equal ['State event is invalid'], @record.errors.full_messages end def test_should_fail_if_event_has_no_transition @record.state = 'idling' assert !@record.valid? assert_equal ['State event cannot transition when idling'], @record.errors.full_messages end def test_should_be_successful_if_event_has_transition assert @record.valid? end def test_should_run_before_callbacks ran_callback = false @machine.before_transition { ran_callback = true } @record.valid? assert ran_callback end def test_should_run_around_callbacks_before_yield ran_callback = false @machine.around_transition {|block| ran_callback = true; block.call } begin @record.valid? rescue ArgumentError raise if EnumStateMachine::Transition.pause_supported? end assert ran_callback end def test_should_persist_new_state @record.valid? assert_equal 'idling', @record.state end def test_should_not_run_after_callbacks ran_callback = false @machine.after_transition { ran_callback = true } @record.valid? assert !ran_callback end def test_should_not_run_after_callbacks_with_failures_disabled_if_validation_fails @model.class_eval do attr_accessor :seatbelt validates_presence_of :seatbelt end ran_callback = false @machine.after_transition { ran_callback = true } @record.valid? assert !ran_callback end def test_should_run_after_callbacks_if_validation_fails @model.class_eval do attr_accessor :seatbelt validates_presence_of :seatbelt end ran_callback = false @machine.after_failure { ran_callback = true } @record.valid? assert ran_callback end def test_should_not_run_around_callbacks_after_yield ran_callback = false @machine.around_transition {|block| block.call; ran_callback = true } begin @record.valid? rescue ArgumentError raise if EnumStateMachine::Transition.pause_supported? end assert !ran_callback end def test_should_not_run_around_callbacks_after_yield_with_failures_disabled_if_validation_fails @model.class_eval do attr_accessor :seatbelt validates_presence_of :seatbelt end ran_callback = false @machine.around_transition {|block| block.call; ran_callback = true } @record.valid? assert !ran_callback end def test_should_not_run_before_transitions_within_transaction @machine.before_transition { @model.create; raise ActiveRecord::Rollback } begin @record.valid? rescue Exception end assert_equal 1, @model.count end end class MachineWithEventAttributesOnSaveTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model) @machine.event :ignite do transition :parked => :idling end @record = @model.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_fail_if_event_is_invalid @record.state_event = 'invalid' assert_equal false, @record.save end def test_should_fail_if_event_has_no_transition @record.state = 'idling' assert_equal false, @record.save end def test_should_run_before_callbacks ran_callback = false @machine.before_transition { ran_callback = true } @record.save assert ran_callback end def test_should_run_before_callbacks_once before_count = 0 @machine.before_transition { before_count += 1 } @record.save assert_equal 1, before_count end def test_should_run_around_callbacks_before_yield ran_callback = false @machine.around_transition {|block| ran_callback = true; block.call } @record.save assert ran_callback end def test_should_run_around_callbacks_before_yield_once around_before_count = 0 @machine.around_transition {|block| around_before_count += 1; block.call } @record.save assert_equal 1, around_before_count end def test_should_persist_new_state @record.save assert_equal 'idling', @record.state end def test_should_run_after_callbacks ran_callback = false @machine.after_transition { ran_callback = true } @record.save assert ran_callback end def test_should_not_run_after_callbacks_with_failures_disabled_if_fails @model.before_create {|record| false} ran_callback = false @machine.after_transition { ran_callback = true } begin; @record.save; rescue; end assert !ran_callback end def test_should_run_failure_callbacks__if_fails @model.before_create {|record| false} ran_callback = false @machine.after_failure { ran_callback = true } begin; @record.save; rescue; end assert ran_callback end def test_should_not_run_around_callbacks_if_fails @model.before_create {|record| false} ran_callback = false @machine.around_transition {|block| block.call; ran_callback = true } begin; @record.save; rescue; end assert !ran_callback end def test_should_run_around_callbacks_after_yield ran_callback = false @machine.around_transition {|block| block.call; ran_callback = true } @record.save assert ran_callback end def test_should_run_before_transitions_within_transaction @machine.before_transition { @model.create; raise ActiveRecord::Rollback } begin @record.save rescue Exception end assert_equal 0, @model.count end def test_should_run_after_transitions_within_transaction @machine.after_transition { @model.create; raise ActiveRecord::Rollback } begin @record.save rescue Exception end assert_equal 0, @model.count end def test_should_run_around_transition_within_transaction @machine.around_transition { @model.create; raise ActiveRecord::Rollback } begin @record.save rescue Exception end assert_equal 0, @model.count end def test_should_allow_additional_transitions_to_new_state_in_after_transitions @machine.event :park do transition :idling => :parked end @machine.after_transition(:on => :ignite) { @record.park } @record.save assert_equal 'parked', @record.state @record.reload assert_equal 'parked', @record.state end def test_should_allow_additional_transitions_to_previous_state_in_after_transitions @machine.event :shift_up do transition :idling => :first_gear end @machine.after_transition(:on => :ignite) { @record.shift_up } @record.save assert_equal 'first_gear', @record.state @record.reload assert_equal 'first_gear', @record.state end def test_should_return_nil_on_manual_rollback @machine.before_transition { raise ActiveRecord::Rollback } assert_equal nil, @record.save end end if ActiveRecord::VERSION::MAJOR >= 3 || ActiveRecord::VERSION::MINOR >= 3 class MachineWithEventAttributesOnAutosaveTest < BaseTestCase def setup @vehicle_model = new_model(:vehicle) do connection.add_column table_name, :owner_id, :integer end ActiveRecordTest.const_set('Vehicle', @vehicle_model) @owner_model = new_model(:owner) ActiveRecordTest.const_set('Owner', @owner_model) machine = EnumStateMachine::Machine.new(@vehicle_model) machine.event :ignite do transition :parked => :idling end @owner = @owner_model.create @vehicle = @vehicle_model.create(:state => 'parked', :owner_id => @owner.id) end def test_should_persist_has_one_autosave @owner_model.has_one :vehicle, :class_name => 'ActiveRecordTest::Vehicle', :autosave => true @owner.vehicle.state_event = 'ignite' @owner.save @vehicle.reload assert_equal 'idling', @vehicle.state end def test_should_persist_has_many_autosave @owner_model.has_many :vehicles, :class_name => 'ActiveRecordTest::Vehicle', :autosave => true @owner.vehicles[0].state_event = 'ignite' @owner.save @vehicle.reload assert_equal 'idling', @vehicle.state end def teardown ActiveRecordTest.class_eval do remove_const('Vehicle') remove_const('Owner') end ActiveSupport::Dependencies.clear if defined?(ActiveSupport::Dependencies) super end end end class MachineWithEventAttributesOnSaveBangTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model) @machine.event :ignite do transition :parked => :idling end @record = @model.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_fail_if_event_is_invalid @record.state_event = 'invalid' assert_raise(ActiveRecord::RecordInvalid) { @record.save! } end def test_should_fail_if_event_has_no_transition @record.state = 'idling' assert_raise(ActiveRecord::RecordInvalid) { @record.save! } end def test_should_be_successful_if_event_has_transition assert_equal true, @record.save! end def test_should_run_before_callbacks ran_callback = false @machine.before_transition { ran_callback = true } @record.save! assert ran_callback end def test_should_run_before_callbacks_once before_count = 0 @machine.before_transition { before_count += 1 } @record.save! assert_equal 1, before_count end def test_should_run_around_callbacks_before_yield ran_callback = false @machine.around_transition {|block| ran_callback = true; block.call } @record.save! assert ran_callback end def test_should_run_around_callbacks_before_yield_once around_before_count = 0 @machine.around_transition {|block| around_before_count += 1; block.call } @record.save! assert_equal 1, around_before_count end def test_should_persist_new_state @record.save! assert_equal 'idling', @record.state end def test_should_run_after_callbacks ran_callback = false @machine.after_transition { ran_callback = true } @record.save! assert ran_callback end def test_should_run_around_callbacks_after_yield ran_callback = false @machine.around_transition {|block| block.call; ran_callback = true } @record.save! assert ran_callback end end class MachineWithEventAttributesOnCustomActionTest < BaseTestCase def setup @superclass = new_model do def persist create_or_update end end @model = Class.new(@superclass) @machine = EnumStateMachine::Machine.new(@model, :action => :persist) @machine.event :ignite do transition :parked => :idling end @record = @model.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_not_transition_on_valid? @record.valid? assert_equal 'parked', @record.state end def test_should_not_transition_on_save @record.save assert_equal 'parked', @record.state end def test_should_not_transition_on_save! @record.save! assert_equal 'parked', @record.state end def test_should_transition_on_custom_action @record.persist assert_equal 'idling', @record.state end end class MachineWithObserversTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @record = @model.new(:state => 'parked') @transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) end def test_should_call_all_transition_callback_permutations callbacks = [ :before_ignite_from_parked_to_idling, :before_ignite_from_parked, :before_ignite_to_idling, :before_ignite, :before_transition_state_from_parked_to_idling, :before_transition_state_from_parked, :before_transition_state_to_idling, :before_transition_state, :before_transition ] observer = new_observer(@model) do callbacks.each do |callback| define_method(callback) do |*args| notifications << callback end end end instance = observer.instance @transition.perform assert_equal callbacks, instance.notifications end def test_should_call_no_transition_callbacks_when_observers_disabled return unless ActiveRecord::VERSION::MAJOR >= 3 && ActiveRecord::VERSION::MINOR >= 1 callbacks = [ :before_ignite, :before_transition ] observer = new_observer(@model) do callbacks.each do |callback| define_method(callback) do |*args| notifications << callback end end end instance = observer.instance @model.observers.disable(observer) do @transition.perform end assert_equal [], instance.notifications end def test_should_pass_record_and_transition_to_before_callbacks observer = new_observer(@model) do def before_transition(*args) notifications << args end end instance = observer.instance @transition.perform assert_equal [[@record, @transition]], instance.notifications end def test_should_pass_record_and_transition_to_after_callbacks observer = new_observer(@model) do def after_transition(*args) notifications << args end end instance = observer.instance @transition.perform assert_equal [[@record, @transition]], instance.notifications end def test_should_call_methods_outside_the_context_of_the_record observer = new_observer(@model) do def before_ignite(*args) notifications << self end end instance = observer.instance @transition.perform assert_equal [instance], instance.notifications end def test_should_continue_to_handle_non_state_machine_callbacks observer = new_observer(@model) do def before_save(object) notifications << [:before_save, object] end def before_ignite(*args) notifications << :before_ignite end end instance = observer.instance @transition.perform assert_equal [:before_ignite, [:before_save, @record]], instance.notifications end def test_should_support_nil_from_states callbacks = [ :before_ignite_from_nil_to_idling, :before_ignite_from_nil, :before_transition_state_from_nil_to_idling, :before_transition_state_from_nil ] observer = new_observer(@model) do callbacks.each do |callback| define_method(callback) do |*args| notifications << callback end end end instance = observer.instance transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, nil, :idling) transition.perform assert_equal callbacks, instance.notifications end def test_should_support_nil_to_states callbacks = [ :before_ignite_from_parked_to_nil, :before_ignite_to_nil, :before_transition_state_from_parked_to_nil, :before_transition_state_to_nil ] observer = new_observer(@model) do callbacks.each do |callback| define_method(callback) do |*args| notifications << callback end end end instance = observer.instance transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :parked, nil) transition.perform assert_equal callbacks, instance.notifications end end class MachineWithNamespacedObserversTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model, :state, :namespace => 'alarm') @machine.state :active, :off @machine.event :enable @record = @model.new(:state => 'off') @transition = EnumStateMachine::Transition.new(@record, @machine, :enable, :off, :active) end def test_should_call_namespaced_before_event_method observer = new_observer(@model) do def before_enable_alarm(*args) notifications << args end end instance = observer.instance @transition.perform assert_equal [[@record, @transition]], instance.notifications end def test_should_call_namespaced_after_event_method observer = new_observer(@model) do def after_enable_alarm(*args) notifications << args end end instance = observer.instance @transition.perform assert_equal [[@record, @transition]], instance.notifications end end class MachineWithFailureCallbacksTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @record = @model.new(:state => 'parked') @transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @notifications = [] # Create callbacks @machine.before_transition {false} @machine.after_failure {@notifications << :callback_after_failure} # Create observer callbacks observer = new_observer(@model) do def after_failure_to_ignite(*args) notifications << :observer_after_failure_ignite end def after_failure_to_transition(*args) notifications << :observer_after_failure_transition end end instance = observer.instance instance.notifications = @notifications @transition.perform end def test_should_invoke_callbacks_in_specific_order expected = [ :callback_after_failure, :observer_after_failure_ignite, :observer_after_failure_transition ] assert_equal expected, @notifications end end class MachineWithMixedCallbacksTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @record = @model.new(:state => 'parked') @transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @notifications = [] # Create callbacks @machine.before_transition {@notifications << :callback_before_transition} @machine.after_transition {@notifications << :callback_after_transition} @machine.around_transition do |block| @notifications << :callback_around_before_transition block.call @notifications << :callback_arond_after_transition end # Create observer callbacks observer = new_observer(@model) do def before_ignite(*args) notifications << :observer_before_ignite end def before_transition(*args) notifications << :observer_before_transition end def after_ignite(*args) notifications << :observer_after_ignite end def after_transition(*args) notifications << :observer_after_transition end end instance = observer.instance instance.notifications = @notifications @transition.perform end def test_should_invoke_callbacks_in_specific_order expected = [ :callback_before_transition, :callback_around_before_transition, :observer_before_ignite, :observer_before_transition, :callback_arond_after_transition, :callback_after_transition, :observer_after_ignite, :observer_after_transition ] assert_equal expected, @notifications end end if ActiveRecord.const_defined?(:NamedScope) class MachineWithScopesTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model) @machine.state :parked, :first_gear @machine.state :idling, :value => lambda {'idling'} end def test_should_create_singular_with_scope assert @model.respond_to?(:with_state) end def test_should_only_include_records_with_state_in_singular_with_scope parked = @model.create :state => 'parked' @model.create :state => 'idling' assert_equal [parked], @model.with_state(:parked).find(:all) end def test_should_create_plural_with_scope assert @model.respond_to?(:with_states) end def test_should_only_include_records_with_states_in_plural_with_scope parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [parked, idling], @model.with_states(:parked, :idling).find(:all) end def test_should_allow_lookup_by_string_name parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [parked, idling], @model.with_states('parked', 'idling').find(:all) end def test_should_create_singular_without_scope assert @model.respond_to?(:without_state) end def test_should_only_include_records_without_state_in_singular_without_scope parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [parked], @model.without_state(:idling).find(:all) end def test_should_create_plural_without_scope assert @model.respond_to?(:without_states) end def test_should_only_include_records_without_states_in_plural_without_scope parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' first_gear = @model.create :state => 'first_gear' assert_equal [parked, idling], @model.without_states(:first_gear).find(:all) end def test_should_allow_chaining_scopes parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [idling], @model.without_state(:parked).with_state(:idling).find(:all) end end class MachineWithScopesAndOwnerSubclassTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model, :state) @subclass = Class.new(@model) @subclass_machine = @subclass.state_machine(:state) {} @subclass_machine.state :parked, :idling, :first_gear end def test_should_only_include_records_with_subclass_states_in_with_scope parked = @subclass.create :state => 'parked' idling = @subclass.create :state => 'idling' assert_equal [parked, idling], @subclass.with_states(:parked, :idling).find(:all) end def test_should_only_include_records_without_subclass_states_in_without_scope parked = @subclass.create :state => 'parked' idling = @subclass.create :state => 'idling' first_gear = @subclass.create :state => 'first_gear' assert_equal [parked, idling], @subclass.without_states(:first_gear).find(:all) end end class MachineWithComplexPluralizationScopesTest < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model, :status) end def test_should_create_singular_with_scope assert @model.respond_to?(:with_status) end def test_should_create_plural_with_scope assert @model.respond_to?(:with_statuses) end end class MachineWithScopesAndJoinsTest < BaseTestCase def setup @company = new_model(:company) ActiveRecordTest.const_set('Company', @company) @vehicle = new_model(:vehicle) do connection.add_column table_name, :company_id, :integer belongs_to :company, :class_name => 'ActiveRecordTest::Company' end ActiveRecordTest.const_set('Vehicle', @vehicle) @company_machine = EnumStateMachine::Machine.new(@company, :initial => :active) @vehicle_machine = EnumStateMachine::Machine.new(@vehicle, :initial => :parked) @vehicle_machine.state :idling @ford = @company.create @mustang = @vehicle.create(:company => @ford) end def test_should_find_records_in_with_scope assert_equal [@mustang], @vehicle.with_states(:parked).find(:all, :joins => :company, :conditions => "#{@company.table_name}.state = \"active\"") end def test_should_find_records_in_without_scope assert_equal [@mustang], @vehicle.without_states(:idling).find(:all, :joins => :company, :conditions => "#{@company.table_name}.state = \"active\"") end def teardown ActiveRecordTest.class_eval do remove_const('Vehicle') remove_const('Company') end ActiveSupport::Dependencies.clear if defined?(ActiveSupport::Dependencies) super end end else $stderr.puts 'Skipping ActiveRecord Scope tests.' end if ActiveRecord.const_defined?(:Relation) class MachineWithDefaultScope < BaseTestCase def setup @model = new_model @machine = EnumStateMachine::Machine.new(@model, :initial => :parked) @machine.state :idling @model.class_eval do default_scope { with_state(:parked, :idling) } end end def test_should_set_initial_state_on_created_object object = @model.new assert_equal 'parked', object.state end end else $stderr.puts 'Skipping ActiveRecord Default Scope tests.' end if Object.const_defined?(:I18n) class MachineWithInternationalizationTest < BaseTestCase def setup I18n.backend = I18n::Backend::Simple.new # Initialize the backend EnumStateMachine::Machine.new(new_model) I18n.backend.translate(:en, 'activerecord.errors.messages.invalid_transition', :event => 'ignite', :value => 'idling') @model = new_model end def test_should_use_defaults I18n.backend.store_translations(:en, { :activerecord => {:errors => {:messages => {:invalid_transition => "cannot #{interpolation_key('event')}"}}} }) machine = EnumStateMachine::Machine.new(@model) machine.state :parked, :idling machine.event :ignite record = @model.new(:state => 'idling') machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot ignite'], record.errors.full_messages end def test_should_allow_customized_error_key I18n.backend.store_translations(:en, { :activerecord => {:errors => {:messages => {:bad_transition => "cannot #{interpolation_key('event')}"}}} }) machine = EnumStateMachine::Machine.new(@model, :messages => {:invalid_transition => :bad_transition}) machine.state :parked, :idling record = @model.new(:state => 'idling') machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot ignite'], record.errors.full_messages end def test_should_allow_customized_error_string machine = EnumStateMachine::Machine.new(@model, :messages => {:invalid_transition => "cannot #{interpolation_key('event')}"}) machine.state :parked, :idling record = @model.new(:state => 'idling') machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot ignite'], record.errors.full_messages end def test_should_allow_customized_state_key_scoped_to_class_and_machine I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:'active_record_test/foo' => {:state => {:states => {:parked => 'shutdown'}}}}} }) machine = EnumStateMachine::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_allow_customized_state_key_scoped_to_class I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:'active_record_test/foo' => {:states => {:parked => 'shutdown'}}}} }) machine = EnumStateMachine::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_allow_customized_state_key_scoped_to_machine I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:state => {:states => {:parked => 'shutdown'}}}} }) machine = EnumStateMachine::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_allow_customized_state_key_unscoped I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:states => {:parked => 'shutdown'}}} }) machine = EnumStateMachine::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_support_nil_state_key I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:states => {:nil => 'empty'}}} }) machine = EnumStateMachine::Machine.new(@model) assert_equal 'empty', machine.state(nil).human_name end def test_should_allow_customized_event_key_scoped_to_class_and_machine I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:'active_record_test/foo' => {:state => {:events => {:park => 'stop'}}}}} }) machine = EnumStateMachine::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_allow_customized_event_key_scoped_to_class I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:'active_record_test/foo' => {:events => {:park => 'stop'}}}} }) machine = EnumStateMachine::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_allow_customized_event_key_scoped_to_machine I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:state => {:events => {:park => 'stop'}}}} }) machine = EnumStateMachine::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_allow_customized_event_key_unscoped I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:events => {:park => 'stop'}}} }) machine = EnumStateMachine::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_only_add_locale_once_in_load_path assert_equal 1, I18n.load_path.select {|path| path =~ %r{active_record/locale\.rb$}}.length # Create another ActiveRecord model that will triger the i18n feature new_model assert_equal 1, I18n.load_path.select {|path| path =~ %r{active_record/locale\.rb$}}.length end def test_should_add_locale_to_beginning_of_load_path @original_load_path = I18n.load_path I18n.backend = I18n::Backend::Simple.new app_locale = File.dirname(__FILE__) + '/../../files/en.yml' default_locale = File.dirname(__FILE__) + '/../../../lib/state_machine/integrations/active_record/locale.rb' I18n.load_path = [app_locale] EnumStateMachine::Machine.new(@model) assert_equal [default_locale, app_locale].map {|path| File.expand_path(path)}, I18n.load_path.map {|path| File.expand_path(path)} ensure I18n.load_path = @original_load_path end def test_should_prefer_other_locales_first @original_load_path = I18n.load_path I18n.backend = I18n::Backend::Simple.new I18n.load_path = [File.dirname(__FILE__) + '/../../files/en.yml'] machine = EnumStateMachine::Machine.new(@model) machine.state :parked, :idling machine.event :ignite record = @model.new(:state => 'idling') machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot transition'], record.errors.full_messages ensure I18n.load_path = @original_load_path end private def interpolation_key(key) !defined?(I18n::VERSION) || I18n::VERSION < '0.4.0' ? "{{#{key}}}" : "%{#{key}}" end end else $stderr.puts 'Skipping ActiveRecord I18n tests.' end end