state_machines-activerecord-0.9.0/0000755000004100000410000000000014452121716017212 5ustar www-datawww-datastate_machines-activerecord-0.9.0/test/0000755000004100000410000000000014452121716020171 5ustar www-datawww-datastate_machines-activerecord-0.9.0/test/machine_with_default_scope_test.rb0000644000004100000410000000066414452121716027117 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithDefaultScopeTest < BaseTestCase def setup @model = new_model @machine = StateMachines::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 state_machines-activerecord-0.9.0/test/machine_with_validations_and_custom_attribute_test.rb0000644000004100000410000000101514452121716033105 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithValidationsAndCustomAttributeTest < BaseTestCase def setup @model = new_model @machine = StateMachines::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' refute @record.valid? assert_equal ['State is invalid'], @record.errors.full_messages @record.state = 'parked' assert @record.valid? end end state_machines-activerecord-0.9.0/test/machine_with_scopes_and_joins_test.rb0000644000004100000410000000237614452121716027624 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithScopesAndJoinsTest < BaseTestCase def setup @company = new_model(:company) MachineWithScopesAndJoinsTest.const_set('Company', @company) @vehicle = new_model(:vehicle) do connection.add_column table_name, :company_id, :integer belongs_to :company, :class_name => 'MachineWithScopesAndJoinsTest::Company' end MachineWithScopesAndJoinsTest.const_set('Vehicle', @vehicle) @company_machine = StateMachines::Machine.new(@company, :initial => :active) @vehicle_machine = StateMachines::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).joins(:company).where("#{@company.table_name}.state = \"active\"") end def test_should_find_records_in_without_scope assert_equal [@mustang], @vehicle.without_states(:idling).joins(:company).where("#{@company.table_name}.state = \"active\"") end def teardown MachineWithScopesAndJoinsTest.class_eval do remove_const('Vehicle') remove_const('Company') end clear_active_support_dependencies end end state_machines-activerecord-0.9.0/test/machine_with_state_driven_validations_test.rb0000644000004100000410000000153014452121716031357 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithStateDrivenValidationsTest < BaseTestCase def setup @model = new_model do attr_accessor :seatbelt end @machine = StateMachines::Machine.new(@model) @machine.state :first_gear, :second_gear do validates :seatbelt, presence: true 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) refute 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 state_machines-activerecord-0.9.0/test/machine_with_dirty_attribute_and_state_events_test.rb0000644000004100000410000000076214452121716033125 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithDirtyAttributeAndStateEventsTest < BaseTestCase def setup @model = new_model @machine = StateMachines::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_nil @record.changes['state'] end end state_machines-activerecord-0.9.0/test/machine_with_failed_action_test.rb0000644000004100000410000000221714452121716027057 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithFailedActionTest < BaseTestCase def setup @model = new_model do validates_inclusion_of :state, :in => %w(first_gear) end @machine = StateMachines::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 = StateMachines::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform end def test_should_not_be_successful refute @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 state_machines-activerecord-0.9.0/test/machine_with_conflicting_predicate_test.rb0000644000004100000410000000053614452121716030617 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithConflictingPredicateTest < BaseTestCase def setup @model = new_model do def state?(*args) true end end @machine = StateMachines::Machine.new(@model) @record = @model.new end def test_should_not_define_attribute_predicate assert @record.state? end end state_machines-activerecord-0.9.0/test/machine_with_different_integer_column_default_test.rb0000644000004100000410000000147614452121716033050 0ustar www-datawww-datarequire_relative 'test_helper' require 'stringio' 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 = StateMachines::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 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 state_machines-activerecord-0.9.0/test/machine_with_same_column_default_test.rb0000644000004100000410000000121314452121716030277 0ustar www-datawww-datarequire_relative 'test_helper' 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 = StateMachines::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 state_machines-activerecord-0.9.0/test/model_test.rb0000644000004100000410000000056114452121716022657 0ustar www-datawww-datarequire_relative 'test_helper' require_relative 'files/models/post' class ModelTest < ActiveSupport::TestCase def test_should_have_draft_state_in_defaut_machine assert_equal 'draft', Post.new.state end def test_should_have_the_correct_integration assert_equal StateMachines::Integrations::ActiveRecord, StateMachines::Integrations.match(Post) end end state_machines-activerecord-0.9.0/test/machine_multiple_test.rb0000644000004100000410000000077514452121716025105 0ustar www-datawww-datarequire_relative 'test_helper' class MachineMultipleTest < BaseTestCase def setup @model = new_model do connection.add_column table_name, :status, :string end @state_machine = StateMachines::Machine.new(@model, :initial => :parked) @status_machine = StateMachines::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 state_machines-activerecord-0.9.0/test/machine_with_aliased_attribute_test.rb0000644000004100000410000000103214452121716027755 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithAliasedAttributeTest < BaseTestCase def setup @model = new_model do alias_attribute :vehicle_status, :state end @machine = StateMachines::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 refute @record.status?(:parked) @record.vehicle_status = 'parked' assert @record.status?(:parked) end end state_machines-activerecord-0.9.0/test/machine_with_custom_attribute_test.rb0000644000004100000410000000100714452121716027667 0ustar www-datawww-datarequire_relative 'test_helper' require 'stringio' class MachineWithCustomAttributeTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @model = new_model @machine = StateMachines::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 state_machines-activerecord-0.9.0/test/machine_with_event_attributes_on_validation_test.rb0000644000004100000410000000642314452121716032576 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithEventAttributesOnValidationTest < BaseTestCase def setup @model = new_model @machine = StateMachines::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' refute @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' refute @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 StateMachines::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? refute ran_callback end def test_should_not_run_after_callbacks_with_failures_disabled_if_validation_fails @model.class_eval do attr_accessor :seatbelt validates :seatbelt, presence: true end ran_callback = false @machine.after_transition { ran_callback = true } @record.valid? refute ran_callback end def test_should_run_after_callbacks_if_validation_fails @model.class_eval do attr_accessor :seatbelt validates :seatbelt, presence: true 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 StateMachines::Transition.pause_supported? end refute 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 :seatbelt, presence: true end ran_callback = false @machine.around_transition { |block| block.call; ran_callback = true } @record.valid? refute ran_callback end def test_should_rollback_before_transitions_with_raise @machine.before_transition { @model.create; raise ActiveRecord::Rollback } begin @record.valid? rescue Exception end assert_equal 0, @model.count end def test_should_rollback_before_transitions_with_false @machine.before_transition { @model.create; false } begin @record.valid? rescue Exception end assert_equal 0, @model.count end end state_machines-activerecord-0.9.0/test/machine_nested_action_test.rb0000644000004100000410000000160414452121716026061 0ustar www-datawww-datarequire_relative 'test_helper' class MachineNestedActionTest < BaseTestCase def setup @callbacks = [] @model = new_model @machine = StateMachines::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 state_machines-activerecord-0.9.0/test/machine_with_loopback_test.rb0000644000004100000410000000112314452121716026063 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithLoopbackTest < BaseTestCase def setup @model = new_model do connection.add_column table_name, :updated_at, :datetime end @machine = StateMachines::Machine.new(@model, :initial => :parked) @machine.event :park @record = @model.create(:updated_at => Time.now - 1) @transition = StateMachines::Transition.new(@record, @machine, :park, :parked, :parked) @timestamp = @record.updated_at @transition.perform end def test_should_not_update_record assert_equal @timestamp, @record.updated_at end end state_machines-activerecord-0.9.0/test/machine_with_dynamic_initial_state_test.rb0000644000004100000410000000454614452121716030642 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithDynamicInitialStateTest < BaseTestCase def setup @model = new_model do attr_accessor :value end @machine = StateMachines::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.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 def test_should_persist_initial_state_on_dup record = @model.create.dup record.save record.reload assert_equal 'parked', record.state 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 state_machines-activerecord-0.9.0/test/machine_with_dirty_attributes_test.rb0000644000004100000410000000175714452121716027707 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithDirtyAttributesTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model, :initial => :parked) @machine.event :ignite @machine.state :idling @record = @model.create @transition = StateMachines::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 = StateMachines::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) refute record.changed? end end state_machines-activerecord-0.9.0/test/machine_with_conflicting_state_name_test.rb0000644000004100000410000000150514452121716030774 0ustar www-datawww-datarequire_relative 'test_helper' require 'stringio' class MachineWithConflictingStateNameTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @model = new_model end def test_should_output_warning_with_same_machine_name @machine = StateMachines::Machine.new(@model) @machine.state :state assert_match(/^Instance method "state\?" is already defined in Foo, use generic helper instead.*\n$/, $stderr.string) end def test_should_output_warning_with_same_machine_attribute @machine = StateMachines::Machine.new(@model, :public_state, :attribute => :state) @machine.state :state assert_match(/^Instance method "state\?" is already defined in Foo, use generic helper instead.*\n$/, $stderr.string) end def teardown $stderr = @original_stderr super end end state_machines-activerecord-0.9.0/test/machine_with_initialized_state_test.rb0000644000004100000410000000212014452121716027774 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithInitializedStateTest < BaseTestCase def setup @model = new_model @machine = StateMachines::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_using_create_with record = @model.create_with(state: 'idling').new 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 end state_machines-activerecord-0.9.0/test/machine_with_static_initial_state_test.rb0000644000004100000410000001120514452121716030473 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithStaticInitialStateTest < BaseTestCase def setup @model = new_model(:vehicle) do attr_accessor :value end @machine = StateMachines::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.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 record.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 def test_should_persist_initial_state_on_dup record = @model.create.dup record.save record.reload assert_equal 'parked', record.state 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 MachineWithStaticInitialStateTest.const_set('Vehicle', @model) owner_model = new_model(:owner) do has_many :vehicles, :class_name => 'MachineWithStaticInitialStateTest::Vehicle' end MachineWithStaticInitialStateTest.const_set('Owner', owner_model) owner = owner_model.create @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 MachineWithStaticInitialStateTest.const_set('Vehicle', @model) owner_model = new_model(:owner) do has_one :vehicle, :class_name => 'MachineWithStaticInitialStateTest::Vehicle' end MachineWithStaticInitialStateTest.const_set('Owner', owner_model) owner = owner_model.create @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 MachineWithStaticInitialStateTest.const_set('Vehicle', @model) driver_model = new_model(:driver) do connection.add_column table_name, :vehicle_id, :integer belongs_to :vehicle, :class_name => 'MachineWithStaticInitialStateTest::Vehicle' end MachineWithStaticInitialStateTest.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 MachineWithStaticInitialStateTest.class_eval do remove_const('Vehicle') if defined?(MachineWithStaticInitialStateTest::Vehicle) remove_const('Owner') if defined?(MachineWithStaticInitialStateTest::Owner) remove_const('Driver') if defined?(MachineWithStaticInitialStateTest::Driver) end clear_active_support_dependencies super end end state_machines-activerecord-0.9.0/test/machine_with_event_attributes_on_save_bang_test.rb0000644000004100000410000000372214452121716032370 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithEventAttributesOnSaveBangTest < BaseTestCase def setup @model = new_model @machine = StateMachines::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 state_machines-activerecord-0.9.0/test/machine_with_scopes_and_owner_subclass_test.rb0000644000004100000410000000162014452121716031522 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithScopesAndOwnerSubclassTest < BaseTestCase def setup @model = new_model @machine = StateMachines::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).all end def test_should_only_include_records_without_subclass_states_in_without_scope parked = @subclass.create :state => 'parked' idling = @subclass.create :state => 'idling' @subclass.create :state => 'first_gear' assert_equal [parked, idling], @subclass.without_states(:first_gear).all end end state_machines-activerecord-0.9.0/test/machine_with_column_state_attribute_test.rb0000644000004100000410000000216314452121716031056 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithColumnStateAttributeTest < BaseTestCase def setup @model = new_model @machine = StateMachines::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 refute @record.state? end def test_should_return_false_for_predicate_if_does_not_match_current_value refute @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_raises(IndexError) { @record.state?(:invalid) } end end state_machines-activerecord-0.9.0/test/machine_with_different_column_default_test.rb0000644000004100000410000000143314452121716031324 0ustar www-datawww-datarequire_relative 'test_helper' require 'stringio' 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 = StateMachines::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 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 state_machines-activerecord-0.9.0/test/machine_with_scopes_test.rb0000644000004100000410000000404514452121716025573 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithScopesTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model) @machine.state :parked, :first_gear @machine.state :idling, :value => -> { '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).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).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').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' @model.create :state => 'idling' assert_equal [parked], @model.without_state(:idling).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' @model.create :state => 'first_gear' assert_equal [parked, idling], @model.without_states(:first_gear).all end def test_should_allow_chaining_scopes @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [idling], @model.without_state(:parked).with_state(:idling).all end end state_machines-activerecord-0.9.0/test/machine_with_same_integer_column_default_test.rb0000644000004100000410000000130514452121716032016 0ustar www-datawww-datarequire_relative 'test_helper' require 'stringio' class MachineWithSameIntegerColumnDefaultTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @model = new_model do connection.add_column table_name, :status, :integer, :default => 1 end @machine = StateMachines::Machine.new(@model, :status, :initial => :parked) do state :parked, :value => 1 end @record = @model.new end def test_should_use_machine_default assert_equal 1, @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 state_machines-activerecord-0.9.0/test/machine_with_failed_after_callbacks_test.rb0000644000004100000410000000177114452121716030706 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithFailedAfterCallbacksTest < BaseTestCase def setup @callbacks = [] @model = new_model @machine = StateMachines::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 = StateMachines::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 refute @record.new_record? end def test_should_not_run_further_after_callbacks assert_equal [:around_before, :around_after, :after_1], @callbacks end end ././@LongLink0000644000000000000000000000016200000000000011602 Lustar rootrootstate_machines-activerecord-0.9.0/test/machine_with_dirty_attribute_and_custom_attributes_during_loopback_test.rbstate_machines-activerecord-0.9.0/test/machine_with_dirty_attribute_and_custom_attributes_during_loo0000644000004100000410000000125314452121716034755 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithDirtyAttributeAndCustomAttributesDuringLoopbackTest < BaseTestCase def setup @model = new_model do connection.add_column table_name, :status, :string end @machine = StateMachines::Machine.new(@model, :status, :initial => :parked) @machine.event :park @record = @model.create @transition = StateMachines::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_nil @record.changes['status'] end end state_machines-activerecord-0.9.0/test/machine_with_validations_test.rb0000644000004100000410000000227114452121716026613 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithValidationsTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model) @machine.state :parked @record = @model.new end def test_should_invalidate_using_errors I18n.backend = I18n::Backend::Simple.new @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' refute @record.valid? assert_equal ['State is invalid'], @record.errors.full_messages end end state_machines-activerecord-0.9.0/test/machine_with_transactions_test.rb0000644000004100000410000000105514452121716027005 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithTransactionsTest < BaseTestCase def setup @model = new_model @machine = StateMachines::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 state_machines-activerecord-0.9.0/test/machine_with_internationalization_test.rb0000644000004100000410000001463514452121716030552 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithInternationalizationTest < BaseTestCase def setup I18n.backend = I18n::Backend::Simple.new # Initialize the backend StateMachines::Machine.new(new_model) @model = new_model end def test_should_use_defaults I18n.backend.store_translations(:en, { :activerecord => {:errors => {:messages => {:invalid_transition => "cannot #{interpolation_key('event')}"}}} }) machine = StateMachines::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 via "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 = StateMachines::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 = StateMachines::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 => {:'foo' => {:state => {:states => {:parked => 'shutdown'}}}}} }) machine = StateMachines::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 => {:'foo' => {:states => {:parked => 'shutdown'}}}} }) machine = StateMachines::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 = StateMachines::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 = StateMachines::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 = StateMachines::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 => {:'foo' => {:state => {:events => {:park => 'stop'}}}}} }) machine = StateMachines::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 => {:'foo' => {:events => {:park => 'stop'}}}} }) machine = StateMachines::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 = StateMachines::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 = StateMachines::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 trigger the i18n feature new_model assert_equal 1, I18n.load_path.select { |path| path =~ %r{active_record/locale\.rb$} }.length 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 = StateMachines::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) "%{#{key}}" end end state_machines-activerecord-0.9.0/test/machine_with_states_test.rb0000644000004100000410000000046614452121716025605 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithStatesTest < BaseTestCase def setup @model = new_model @machine = StateMachines::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 state_machines-activerecord-0.9.0/test/machine_with_event_attributes_on_save_test.rb0000644000004100000410000001426114452121716031401 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithEventAttributesOnSaveTest < BaseTestCase def setup @model = new_model @machine = StateMachines::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| abort_from_callback } ran_callback = false @machine.after_transition { ran_callback = true } begin ; @record.save; rescue; end refute ran_callback end def test_should_run_failure_callbacks__if_fails @model.before_create { |record| abort_from_callback } 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| abort_from_callback } ran_callback = false @machine.around_transition { |block| block.call; ran_callback = true } begin ; @record.save; rescue; end refute 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_yield_one_model! assert_equal true, @record.save! assert_equal 1, @model.count end # explicit tests of #save and #save! to ensure expected behavior def test_should_yield_two_models_with_before @machine.before_transition { @model.create! } assert_equal true, @record.save assert_equal 2, @model.count end def test_should_yield_two_models_with_before! @machine.before_transition { @model.create! } assert_equal true, @record.save! assert_equal 2, @model.count end def test_should_raise_on_around_transition_rollback! @machine.before_transition { @model.create! } @machine.around_transition { @model.create!; raise ActiveRecord::Rollback } raised = false begin @record.save! rescue Exception raised = true end assert_equal true, raised assert_equal 0, @model.count end def test_should_return_nil_on_around_transition_rollback @machine.before_transition { @model.create! } @machine.around_transition { @model.create!; raise ActiveRecord::Rollback } assert_nil @record.save assert_equal 0, @model.count end def test_should_return_nil_on_before_transition_rollback @machine.before_transition { raise ActiveRecord::Rollback } assert_nil @record.save assert_equal 0, @model.count end # # @rosskevin - This fails and I'm not sure why, it was existing behavior. # see: https://github.com/state-machines/state_machines-activerecord/pull/26#issuecomment-112911886 # # def test_should_yield_three_models_with_before_and_around_save # @machine.before_transition { @model.create!; puts "before ran, now #{@model.count}" } # @machine.around_transition { @model.create!; puts "around ran, now #{@model.count}" } # # assert_equal true, @record.save # assert_equal 3, @model.count # end # # def test_should_yield_three_models_with_before_and_around_save! # @machine.before_transition { @model.create!; puts "before ran, now #{@model.count}" } # @machine.around_transition { @model.create!; puts "around ran, now #{@model.count}" } # # assert_equal true, @record.save! # assert_equal 3, @model.count # end end state_machines-activerecord-0.9.0/test/machine_errors_test.rb0000644000004100000410000000110014452121716024545 0ustar www-datawww-datarequire_relative 'test_helper' class MachineErrorsTest < BaseTestCase def setup @model = new_model @machine = StateMachines::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 state_machines-activerecord-0.9.0/test/machine_with_complex_pluralization_scopes_test.rb0000644000004100000410000000060714452121716032277 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithComplexPluralizationScopesTest < BaseTestCase def setup @model = new_model @machine = StateMachines::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 state_machines-activerecord-0.9.0/test/machine_with_failed_before_callbacks_test.rb0000644000004100000410000000203414452121716031040 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithFailedBeforeCallbacksTest < BaseTestCase def setup @callbacks = [] @model = new_model @machine = StateMachines::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 = StateMachines::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform end def test_should_not_be_successful refute @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 state_machines-activerecord-0.9.0/test/machine_unmigrated_test.rb0000644000004100000410000000056514452121716025406 0ustar www-datawww-datarequire_relative 'test_helper' 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 { StateMachines::Machine.new(@model) } end endstate_machines-activerecord-0.9.0/test/machine_with_dirty_attributes_during_loopback_test.rb0000644000004100000410000000111314452121716033113 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithDirtyAttributesDuringLoopbackTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model, :initial => :parked) @machine.event :park @record = @model.create @transition = StateMachines::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_nil @record.changes['state'] end end state_machines-activerecord-0.9.0/test/machine_with_events_test.rb0000644000004100000410000000046014452121716025600 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithEventsTest < BaseTestCase def setup @model = new_model @machine = StateMachines::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 state_machines-activerecord-0.9.0/test/integration_test.rb0000644000004100000410000000161114452121716024077 0ustar www-datawww-datarequire_relative 'test_helper' class IntegrationTest < BaseTestCase def test_should_have_an_integration_name assert_equal :active_record, StateMachines::Integrations::ActiveRecord.integration_name end def test_should_be_before_activemodel integrations = StateMachines::Integrations.list.to_a assert StateMachines::Integrations::ActiveRecord, integrations.first assert StateMachines::Integrations::ActiveModel, integrations.last end def test_should_match_if_class_inherits_from_active_record assert StateMachines::Integrations::ActiveRecord.matches?(new_model) end def test_should_not_match_if_class_does_not_inherit_from_active_record refute StateMachines::Integrations::ActiveRecord.matches?(Class.new) end def test_should_have_defaults assert_equal({action: :save, use_transactions: true}, StateMachines::Integrations::ActiveRecord.defaults) end end state_machines-activerecord-0.9.0/test/machine_with_callbacks_test.rb0000644000004100000410000001114514452121716026215 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithCallbacksTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model, :initial => :parked) @machine.other_states :idling @machine.event :ignite @record = @model.new(:state => 'parked') @transition = StateMachines::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 = StateMachines::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 = StateMachines::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 } @model.after_commit { callbacks << :after_commit } expected << :after_commit @machine.before_transition { callbacks << :before_transition } @machine.after_transition { callbacks << :after_transition } @transition.perform assert_equal expected, callbacks end end state_machines-activerecord-0.9.0/test/machine_with_event_attributes_on_autosave_test.rb0000644000004100000410000000266214452121716032274 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithEventAttributesOnAutosaveTest < BaseTestCase def setup @vehicle_model = new_model(:vehicle) do connection.add_column table_name, :owner_id, :integer end MachineWithEventAttributesOnAutosaveTest.const_set('Vehicle', @vehicle_model) @owner_model = new_model(:owner) MachineWithEventAttributesOnAutosaveTest.const_set('Owner', @owner_model) machine = StateMachines::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 => 'MachineWithEventAttributesOnAutosaveTest::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 => 'MachineWithEventAttributesOnAutosaveTest::Vehicle', :autosave => true @owner.vehicles[0].state_event = 'ignite' @owner.save @vehicle.reload assert_equal 'idling', @vehicle.state end def teardown MachineWithEventAttributesOnAutosaveTest.class_eval do remove_const('Vehicle') remove_const('Owner') end clear_active_support_dependencies super end end state_machines-activerecord-0.9.0/test/machine_by_default_test.rb0000644000004100000410000000053014452121716025355 0ustar www-datawww-datarequire_relative 'test_helper' class MachineByDefaultTest < BaseTestCase def setup @model = new_model @machine = StateMachines::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 end state_machines-activerecord-0.9.0/test/machine_with_non_column_state_attribute_defined_test.rb0000644000004100000410000000144014452121716033403 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithNonColumnStateAttributeDefinedTest < BaseTestCase def setup @model = new_model do attr_accessor :status end @machine = StateMachines::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 refute @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 state_machines-activerecord-0.9.0/test/machine_without_database_test.rb0000644000004100000410000000070714452121716026574 0ustar www-datawww-datarequire_relative 'test_helper' 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 { StateMachines::Machine.new(@model) } end end state_machines-activerecord-0.9.0/test/files/0000755000004100000410000000000014452121716021273 5ustar www-datawww-datastate_machines-activerecord-0.9.0/test/files/models/0000755000004100000410000000000014452121716022556 5ustar www-datawww-datastate_machines-activerecord-0.9.0/test/files/models/post.rb0000644000004100000410000000032414452121716024067 0ustar www-datawww-dataActiveRecord::Base.connection.create_table(:posts, :force => true) do |t| t.string :title t.string :content t.string :state end class Post < ActiveRecord::Base state_machine initial: :draft do end endstate_machines-activerecord-0.9.0/test/files/en.yml0000644000004100000410000000013714452121716022421 0ustar www-datawww-dataen: activerecord: errors: messages: invalid_transition: "cannot transition"state_machines-activerecord-0.9.0/test/machine_with_event_attributes_on_custom_action_test.rb0000644000004100000410000000163214452121716033310 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithEventAttributesOnCustomActionTest < BaseTestCase def setup @superclass = new_model do def persist create_or_update end end @model = Class.new(@superclass) @machine = StateMachines::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 state_machines-activerecord-0.9.0/test/test_helper.rb0000644000004100000410000000316114452121716023035 0ustar www-datawww-databegin require 'pry-byebug' rescue LoadError end require 'minitest/reporters' Minitest::Reporters.use!(Minitest::Reporters::SpecReporter.new) require 'state_machines-activerecord' require 'minitest/autorun' require 'securerandom' # Establish database connection ActiveRecord::Base.establish_connection('adapter' => 'sqlite3', 'database' => ':memory:') ActiveRecord::Base.logger = Logger.new("#{File.dirname(__FILE__)}/../log/active_record.log") ActiveSupport.test_order = :random class BaseTestCase < ActiveSupport::TestCase protected # Creates a new ActiveRecord model (and the associated table) def new_model(create_table = :foo, &block) name = create_table || :foo table_name = "#{name}_#{SecureRandom.hex(6)}" 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 define_method(:abort_from_callback) do throw :abort end ( class << self; self; end).class_eval do define_method(:name) { "#{name.to_s.capitalize}" } end end model.class_eval(&block) if block_given? model.reset_column_information if create_table model end def clear_active_support_dependencies return unless defined?(ActiveSupport::Dependencies) if ActiveSupport::Dependencies.respond_to?(:autoloader=) ActiveSupport::Dependencies.autoloader ||= stubbed_autoloader end ActiveSupport::Dependencies.clear end def stubbed_autoloader Object.new.tap do |obj| obj.define_singleton_method(:reload) {} end end end state_machines-activerecord-0.9.0/test/machine_without_transactions_test.rb0000644000004100000410000000106514452121716027536 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithoutTransactionsTest < BaseTestCase def setup @model = new_model @machine = StateMachines::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 state_machines-activerecord-0.9.0/test/machine_with_dirty_attributes_and_custom_attribute_test.rb0000644000004100000410000000171514452121716034200 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithDirtyAttributesAndCustomAttributeTest < BaseTestCase def setup @model = new_model do connection.add_column table_name, :status, :string end @machine = StateMachines::Machine.new(@model, :status, :initial => :parked) @machine.event :ignite @machine.state :idling @record = @model.create @transition = StateMachines::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 = StateMachines::Transition.new(@record, @machine, :ignite, :idling, :idling) transition.perform(false) assert_equal %w(parked idling), @record.changes['status'] end end state_machines-activerecord-0.9.0/state_machines-activerecord.gemspec0000644000004100000410000001756514452121716026234 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: state_machines-activerecord 0.9.0 ruby lib Gem::Specification.new do |s| s.name = "state_machines-activerecord".freeze s.version = "0.9.0" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Abdelkader Boudih".freeze, "Aaron Pfeifer".freeze] s.date = "2023-06-30" s.description = "Adds support for creating state machines for attributes on ActiveRecord".freeze s.email = ["terminale@gmail.com".freeze, "aaron@pluginaweek.org".freeze] s.files = ["LICENSE.txt".freeze, "README.md".freeze, "lib/state_machines-activerecord.rb".freeze, "lib/state_machines/integrations/active_record.rb".freeze, "lib/state_machines/integrations/active_record/locale.rb".freeze, "lib/state_machines/integrations/active_record/version.rb".freeze, "test/files/en.yml".freeze, "test/files/models/post.rb".freeze, "test/integration_test.rb".freeze, "test/machine_by_default_test.rb".freeze, "test/machine_errors_test.rb".freeze, "test/machine_multiple_test.rb".freeze, "test/machine_nested_action_test.rb".freeze, "test/machine_unmigrated_test.rb".freeze, "test/machine_with_aliased_attribute_test.rb".freeze, "test/machine_with_callbacks_test.rb".freeze, "test/machine_with_column_state_attribute_test.rb".freeze, "test/machine_with_complex_pluralization_scopes_test.rb".freeze, "test/machine_with_conflicting_predicate_test.rb".freeze, "test/machine_with_conflicting_state_name_test.rb".freeze, "test/machine_with_custom_attribute_test.rb".freeze, "test/machine_with_default_scope_test.rb".freeze, "test/machine_with_different_column_default_test.rb".freeze, "test/machine_with_different_integer_column_default_test.rb".freeze, "test/machine_with_dirty_attribute_and_custom_attributes_during_loopback_test.rb".freeze, "test/machine_with_dirty_attribute_and_state_events_test.rb".freeze, "test/machine_with_dirty_attributes_and_custom_attribute_test.rb".freeze, "test/machine_with_dirty_attributes_during_loopback_test.rb".freeze, "test/machine_with_dirty_attributes_test.rb".freeze, "test/machine_with_dynamic_initial_state_test.rb".freeze, "test/machine_with_event_attributes_on_autosave_test.rb".freeze, "test/machine_with_event_attributes_on_custom_action_test.rb".freeze, "test/machine_with_event_attributes_on_save_bang_test.rb".freeze, "test/machine_with_event_attributes_on_save_test.rb".freeze, "test/machine_with_event_attributes_on_validation_test.rb".freeze, "test/machine_with_events_test.rb".freeze, "test/machine_with_failed_action_test.rb".freeze, "test/machine_with_failed_after_callbacks_test.rb".freeze, "test/machine_with_failed_before_callbacks_test.rb".freeze, "test/machine_with_initialized_state_test.rb".freeze, "test/machine_with_internationalization_test.rb".freeze, "test/machine_with_loopback_test.rb".freeze, "test/machine_with_non_column_state_attribute_defined_test.rb".freeze, "test/machine_with_same_column_default_test.rb".freeze, "test/machine_with_same_integer_column_default_test.rb".freeze, "test/machine_with_scopes_and_joins_test.rb".freeze, "test/machine_with_scopes_and_owner_subclass_test.rb".freeze, "test/machine_with_scopes_test.rb".freeze, "test/machine_with_state_driven_validations_test.rb".freeze, "test/machine_with_states_test.rb".freeze, "test/machine_with_static_initial_state_test.rb".freeze, "test/machine_with_transactions_test.rb".freeze, "test/machine_with_validations_and_custom_attribute_test.rb".freeze, "test/machine_with_validations_test.rb".freeze, "test/machine_without_database_test.rb".freeze, "test/machine_without_transactions_test.rb".freeze, "test/model_test.rb".freeze, "test/test_helper.rb".freeze] s.homepage = "https://github.com/state-machines/state_machines-activerecord/".freeze s.licenses = ["MIT".freeze] s.required_ruby_version = Gem::Requirement.new(">= 3.0".freeze) s.rubygems_version = "3.2.5".freeze s.summary = "State machines Active Record Integration".freeze s.test_files = ["test/files/en.yml".freeze, "test/files/models/post.rb".freeze, "test/integration_test.rb".freeze, "test/machine_by_default_test.rb".freeze, "test/machine_errors_test.rb".freeze, "test/machine_multiple_test.rb".freeze, "test/machine_nested_action_test.rb".freeze, "test/machine_unmigrated_test.rb".freeze, "test/machine_with_aliased_attribute_test.rb".freeze, "test/machine_with_callbacks_test.rb".freeze, "test/machine_with_column_state_attribute_test.rb".freeze, "test/machine_with_complex_pluralization_scopes_test.rb".freeze, "test/machine_with_conflicting_predicate_test.rb".freeze, "test/machine_with_conflicting_state_name_test.rb".freeze, "test/machine_with_custom_attribute_test.rb".freeze, "test/machine_with_default_scope_test.rb".freeze, "test/machine_with_different_column_default_test.rb".freeze, "test/machine_with_different_integer_column_default_test.rb".freeze, "test/machine_with_dirty_attribute_and_custom_attributes_during_loopback_test.rb".freeze, "test/machine_with_dirty_attribute_and_state_events_test.rb".freeze, "test/machine_with_dirty_attributes_and_custom_attribute_test.rb".freeze, "test/machine_with_dirty_attributes_during_loopback_test.rb".freeze, "test/machine_with_dirty_attributes_test.rb".freeze, "test/machine_with_dynamic_initial_state_test.rb".freeze, "test/machine_with_event_attributes_on_autosave_test.rb".freeze, "test/machine_with_event_attributes_on_custom_action_test.rb".freeze, "test/machine_with_event_attributes_on_save_bang_test.rb".freeze, "test/machine_with_event_attributes_on_save_test.rb".freeze, "test/machine_with_event_attributes_on_validation_test.rb".freeze, "test/machine_with_events_test.rb".freeze, "test/machine_with_failed_action_test.rb".freeze, "test/machine_with_failed_after_callbacks_test.rb".freeze, "test/machine_with_failed_before_callbacks_test.rb".freeze, "test/machine_with_initialized_state_test.rb".freeze, "test/machine_with_internationalization_test.rb".freeze, "test/machine_with_loopback_test.rb".freeze, "test/machine_with_non_column_state_attribute_defined_test.rb".freeze, "test/machine_with_same_column_default_test.rb".freeze, "test/machine_with_same_integer_column_default_test.rb".freeze, "test/machine_with_scopes_and_joins_test.rb".freeze, "test/machine_with_scopes_and_owner_subclass_test.rb".freeze, "test/machine_with_scopes_test.rb".freeze, "test/machine_with_state_driven_validations_test.rb".freeze, "test/machine_with_states_test.rb".freeze, "test/machine_with_static_initial_state_test.rb".freeze, "test/machine_with_transactions_test.rb".freeze, "test/machine_with_validations_and_custom_attribute_test.rb".freeze, "test/machine_with_validations_test.rb".freeze, "test/machine_without_database_test.rb".freeze, "test/machine_without_transactions_test.rb".freeze, "test/model_test.rb".freeze, "test/test_helper.rb".freeze] if s.respond_to? :specification_version then s.specification_version = 4 end if s.respond_to? :add_runtime_dependency then s.add_runtime_dependency(%q.freeze, [">= 6.0"]) s.add_development_dependency(%q.freeze, [">= 1"]) s.add_development_dependency(%q.freeze, [">= 5.4.0"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, ["~> 13.0"]) s.add_development_dependency(%q.freeze, ["~> 1.3"]) s.add_runtime_dependency(%q.freeze, [">= 0.9.0"]) else s.add_dependency(%q.freeze, [">= 6.0"]) s.add_dependency(%q.freeze, [">= 1"]) s.add_dependency(%q.freeze, [">= 5.4.0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, ["~> 13.0"]) s.add_dependency(%q.freeze, ["~> 1.3"]) s.add_dependency(%q.freeze, [">= 0.9.0"]) end end state_machines-activerecord-0.9.0/README.md0000644000004100000410000000547014452121716020477 0ustar www-datawww-data[![Build Status](https://travis-ci.com/state-machines/state_machines-activerecord.svg?branch=master)](https://travis-ci.com/state-machines/state_machines-activerecord) [![Code Climate](https://codeclimate.com/github/state-machines/state_machines-activerecord.svg)](https://codeclimate.com/github/state-machines/state_machines-activerecord) # StateMachines Active Record Integration The Active Record 5.1+ integration adds support for database transactions, automatically saving the record, named scopes, validation errors. ## Installation Add this line to your application's Gemfile: gem 'state_machines-activerecord' And then execute: $ bundle Or install it yourself as: $ gem install state_machines-activerecord ## Usage For the complete usage guide, see http://www.rubydoc.info/github/state-machines/state_machines-activerecord/StateMachines/Integrations/ActiveRecord ### Example ```ruby class Vehicle < ApplicationRecord state_machine :initial => :parked do before_transition :parked => any - :parked, :do => :put_on_seatbelt after_transition any => :parked do |vehicle, transition| vehicle.seatbelt = 'off' end around_transition :benchmark event :ignite do transition :parked => :idling end state :first_gear, :second_gear do validates :seatbelt_on, presence: true end end def put_on_seatbelt ... end def benchmark ... yield ... end end ``` ### Scopes Usage of the generated scopes (assuming default column `state`): ```ruby Vehicle.with_state(:parked) # also plural #with_states Vehicle.without_states(:first_gear, :second_gear) # also singular #without_state ``` ### State driven validations As mentioned in `StateMachines::Machine#state`, you can define behaviors, like validations, that only execute for certain states. One *important* caveat here is that, due to a constraint in ActiveRecord's validation framework, custom validators will not work as expected when defined to run in multiple states. For example: ```ruby class Vehicle < ApplicationRecord state_machine do state :first_gear, :second_gear do validate :speed_is_legal end end end ``` In this case, the :speed_is_legal validation will only get run for the :second_gear state. To avoid this, you can define your custom validation like so: ```ruby class Vehicle < ApplicationRecord state_machine do state :first_gear, :second_gear do validate {|vehicle| vehicle.speed_is_legal} end end end ``` ## Contributing 1. Fork it ( https://github.com/state-machines/state_machines-activerecord/fork ) 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create a new Pull Request state_machines-activerecord-0.9.0/lib/0000755000004100000410000000000014452121716017760 5ustar www-datawww-datastate_machines-activerecord-0.9.0/lib/state_machines/0000755000004100000410000000000014452121716022747 5ustar www-datawww-datastate_machines-activerecord-0.9.0/lib/state_machines/integrations/0000755000004100000410000000000014452121716025455 5ustar www-datawww-datastate_machines-activerecord-0.9.0/lib/state_machines/integrations/active_record.rb0000644000004100000410000004743114452121716030624 0ustar www-datawww-datarequire 'state_machines-activemodel' require 'active_record' require 'state_machines/integrations/active_record/version' module StateMachines module Integrations #:nodoc: # Adds support for integrating state machines with ActiveRecord models. # # == Examples # # Below is an example of a simple state machine defined within an # ActiveRecord model: # # class Vehicle < ApplicationRecord # state_machine :initial => :parked do # event :ignite do # transition :parked => :idling # end # end # end # # The examples in the sections below will use the above class as a # reference. # # == Actions # # By default, the action that will be invoked when a state is transitioned # is the +save+ action. This will cause the record to save the changes # made to the state machine's attribute. *Note* that if any other changes # were made to the record prior to transition, then those changes will # be saved as well. # # For example, # # vehicle = Vehicle.create # => # # vehicle.name = 'Ford Explorer' # vehicle.ignite # => true # vehicle.reload # => # # # *Note* that if you want a transition to update additional attributes of the record, # either the changes need to be made in a +before_transition+ callback or you need # to save the record manually. # # == Events # # As described in StateMachines::InstanceMethods#state_machine, event # attributes are created for every machine that allow transitions to be # performed automatically when the object's action (in this case, :save) # is called. # # In ActiveRecord, these automated events are run in the following order: # * before validation - Run before callbacks and persist new states, then validate # * before save - If validation was skipped, run before callbacks and persist new states, then save # * after save - Run after callbacks # # For example, # # vehicle = Vehicle.create # => # # vehicle.state_event # => nil # vehicle.state_event = 'invalid' # vehicle.valid? # => false # vehicle.errors.full_messages # => ["State event is invalid"] # # vehicle.state_event = 'ignite' # vehicle.valid? # => true # vehicle.save # => true # vehicle.state # => "idling" # vehicle.state_event # => nil # # Note that this can also be done on a mass-assignment basis: # # vehicle = Vehicle.create(:state_event => 'ignite') # => # # vehicle.state # => "idling" # # This technique is always used for transitioning states when the +save+ # action (which is the default) is configured for the machine. # # === Security implications # # Beware that public event attributes mean that events can be fired # whenever mass-assignment is being used. If you want to prevent malicious # users from tampering with events through URLs / forms, the attribute # should be protected like so: # # class Vehicle < ApplicationRecord # attr_protected :state_event # # attr_accessible ... # Alternative technique # # state_machine do # ... # end # end # # If you want to only have *some* events be able to fire via mass-assignment, # you can build two state machines (one public and one protected) like so: # # class Vehicle < ApplicationRecord # attr_protected :state_event # Prevent access to events in the first machine # # state_machine do # # Define private events here # end # # # Public machine targets the same state as the private machine # state_machine :public_state, :attribute => :state do # # Define public events here # end # end # # == Transactions # # In order to ensure that any changes made during transition callbacks # are rolled back during a failed attempt, every transition is wrapped # within a transaction. # # For example, # # class Message < ApplicationRecord # end # # Vehicle.state_machine do # before_transition do |vehicle, transition| # Message.create(:content => transition.inspect) # false # end # end # # vehicle = Vehicle.create # => # # vehicle.ignite # => false # Message.count # => 0 # # *Note* that only before callbacks that halt the callback chain and # failed attempts to save the record will result in the transaction being # rolled back. If an after callback halts the chain, the previous result # still applies and the transaction is *not* rolled back. # # To turn off transactions: # # class Vehicle < ApplicationRecord # state_machine :initial => :parked, :use_transactions => false do # ... # end # end # # == Validations # # As mentioned in StateMachines::Machine#state, you can define behaviors, # like validations, that only execute for certain states. One *important* # caveat here is that, due to a constraint in ActiveRecord's validation # framework, custom validators will not work as expected when defined to run # in multiple states. For example: # # class Vehicle < ApplicationRecord # state_machine do # ... # state :first_gear, :second_gear do # validate :speed_is_legal # end # end # end # # In this case, the :speed_is_legal validation will only get run # for the :second_gear state. To avoid this, you can define your # custom validation like so: # # class Vehicle < ApplicationRecord # state_machine do # ... # state :first_gear, :second_gear do # validate {|vehicle| vehicle.speed_is_legal} # end # end # end # # == Validation errors # # If an event fails to successfully fire because there are no matching # transitions for the current record, a validation error is added to the # record's state attribute to help in determining why it failed and for # reporting via the UI. # # For example, # # vehicle = Vehicle.create(:state => 'idling') # => # # vehicle.ignite # => false # vehicle.errors.full_messages # => ["State cannot transition via \"ignite\""] # # If an event fails to fire because of a validation error on the record and # *not* because a matching transition was not available, no error messages # will be added to the state attribute. # # In addition, if you're using the ignite! version of the event, # then the failure reason (such as the current validation errors) will be # included in the exception that gets raised when the event fails. For # example, assuming there's a validation on a field called +name+ on the class: # # vehicle = Vehicle.new # vehicle.ignite! # => StateMachines::InvalidTransition: Cannot transition state via :ignite from :parked (Reason(s): Name cannot be blank) # # == Scopes # # To assist in filtering models with specific states, a series of named # scopes are defined on the model for finding records with or without a # particular set of states. # # These named scopes are essentially the functional equivalent of the # following definitions: # # class Vehicle < ApplicationRecord # # with_states also aliased to with_state # # named_scope :without_states, lambda {|*states| {:conditions => ['state NOT IN (?)', states]}} # # without_states also aliased to without_state # end # # *Note*, however, that the states are converted to their stored values # before being passed into the query. # # Because of the way named scopes work in ActiveRecord, they can be # chained like so: # # Vehicle.with_state(:parked).all(:order => 'id DESC') # # Note that states can also be referenced by the string version of their # name: # # Vehicle.with_state('parked') # # == Callbacks # # All before/after transition callbacks defined for ActiveRecord models # behave in the same way that other ActiveRecord callbacks behave. The # object involved in the transition is passed in as an argument. # # For example, # # class Vehicle < ApplicationRecord # state_machine :initial => :parked do # before_transition any => :idling do |vehicle| # vehicle.put_on_seatbelt # end # # before_transition do |vehicle, transition| # # log message # end # # event :ignite do # transition :parked => :idling # end # end # # def put_on_seatbelt # ... # end # end # # Note, also, that the transition can be accessed by simply defining # additional arguments in the callback block. # # === Failure callbacks # # +after_failure+ callbacks allow you to execute behaviors when a transition # is allowed, but fails to save. This could be useful for something like # auditing transition attempts. Since callbacks run within transactions in # ActiveRecord, a save failure will cause any records that get created in # your callback to roll back. You can work around this issue like so: # # class TransitionLog < ApplicationRecord # establish_connection Rails.env.to_sym # end # # class Vehicle < ApplicationRecord # state_machine do # after_failure do |vehicle, transition| # TransitionLog.create(:vehicle => vehicle, :transition => transition) # end # # ... # end # end # # The +TransitionLog+ model establishes a second connection to the database # that allows new records to be saved without being affected by rollbacks # in the +Vehicle+ model's transaction. # # === Callback Order # # Callbacks occur in the following order. Callbacks specific to state_machine # are bolded. The remaining callbacks are part of ActiveRecord. # # * (-) save # * (-) begin transaction (if enabled) # * (1) *before_transition* # * (-) valid # * (2) before_validation # * (-) validate # * (3) after_validation # * (4) before_save # * (5) before_create # * (-) create # * (6) after_create # * (7) after_save # * (8) *after_transition* # * (-) end transaction (if enabled) # * (9) after_commit # # == Observers # # In addition to support for ActiveRecord-like hooks, there is additional # support for ActiveRecord observers. Because of the way ActiveRecord # observers are designed, there is less flexibility around the specific # transitions that can be hooked in. However, a large number of hooks # *are* supported. For example, if a transition for a record's +state+ # attribute changes the state from +parked+ to +idling+ via the +ignite+ # event, the following observer methods are supported: # * before/after/after_failure_to-_ignite_from_parked_to_idling # * before/after/after_failure_to-_ignite_from_parked # * before/after/after_failure_to-_ignite_to_idling # * before/after/after_failure_to-_ignite # * before/after/after_failure_to-_transition_state_from_parked_to_idling # * before/after/after_failure_to-_transition_state_from_parked # * before/after/after_failure_to-_transition_state_to_idling # * before/after/after_failure_to-_transition_state # * before/after/after_failure_to-_transition # # The following class shows an example of some of these hooks: # # class VehicleObserver < ActiveRecord::Observer # def before_save(vehicle) # # log message # end # # # Callback for :ignite event *before* the transition is performed # def before_ignite(vehicle, transition) # # log message # end # # # Callback for :ignite event *after* the transition has been performed # def after_ignite(vehicle, transition) # # put on seatbelt # end # # # Generic transition callback *before* the transition is performed # def after_transition(vehicle, transition) # Audit.log(vehicle, transition) # end # end # # More flexible transition callbacks can be defined directly within the # model as described in StateMachines::Machine#before_transition # and StateMachines::Machine#after_transition. # # To define a single observer for multiple state machines: # # class StateMachineObserver < ActiveRecord::Observer # observe Vehicle, Switch, Project # # def after_transition(record, transition) # Audit.log(record, transition) # end # end # # == Internationalization # # In Rails 2.2+, any error message that is generated from performing invalid # transitions can be localized. The following default translations are used: # # en: # activerecord: # errors: # messages: # invalid: "is invalid" # # %{value} = attribute value, %{state} = Human state name # invalid_event: "cannot transition when %{state}" # # %{value} = attribute value, %{event} = Human event name, %{state} = Human current state name # invalid_transition: "cannot transition via %{event}" # # Notice that the interpolation syntax is %{key} in Rails 3+. In Rails 2.x, # the appropriate syntax is {{key}}. # # You can override these for a specific model like so: # # en: # activerecord: # errors: # models: # user: # invalid: "is not valid" # # In addition to the above, you can also provide translations for the # various states / events in each state machine. Using the Vehicle example, # state translations will be looked for using the following keys, where # +model_name+ = "vehicle", +machine_name+ = "state" and +state_name+ = "parked": # * activerecord.state_machines.#{model_name}.#{machine_name}.states.#{state_name} # * activerecord.state_machines.#{model_name}.states.#{state_name} # * activerecord.state_machines.#{machine_name}.states.#{state_name} # * activerecord.state_machines.states.#{state_name} # # Event translations will be looked for using the following keys, where # +model_name+ = "vehicle", +machine_name+ = "state" and +event_name+ = "ignite": # * activerecord.state_machines.#{model_name}.#{machine_name}.events.#{event_name} # * activerecord.state_machines.#{model_name}.events.#{event_name} # * activerecord.state_machines.#{machine_name}.events.#{event_name} # * activerecord.state_machines.events.#{event_name} # # An example translation configuration might look like so: # # es: # activerecord: # state_machines: # states: # parked: 'estacionado' # events: # park: 'estacionarse' module ActiveRecord include Base include ActiveModel # The default options to use for state machines using this integration @defaults = {:action => :save, use_transactions: true} class << self # Classes that inherit from ActiveRecord::Base will automatically use # the ActiveRecord integration. def matching_ancestors [::ActiveRecord::Base] end end protected # Only runs validations on the action if using :save def runs_validations_on_action? action == :save end # Gets the db default for the machine's attribute def owner_class_attribute_default if owner_class.connected? && owner_class.table_exists? owner_class.column_defaults[attribute.to_s] end end def define_state_initializer define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def initialize(attributes = nil, *) super(attributes) do |*args| scoped_attributes = (attributes || {}).merge(self.class.scope_attributes) self.class.state_machines.initialize_states(self, {}, scoped_attributes) yield(*args) if block_given? end end end_eval end # Uses around callbacks to run state events if using the :save hook def define_action_hook if action_hook == :save define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def save(*, **) self.class.state_machine(#{name.inspect}).send(:around_save, self) { super } end def save!(*, **) result = self.class.state_machine(#{name.inspect}).send(:around_save, self) { super } result || raise(ActiveRecord::RecordInvalid.new(self)) end def changed_for_autosave? super || self.class.state_machines.any? {|name, machine| machine.action == :save && machine.read(self, :event)} end end_eval else super end end # Runs state events around the machine's :save action def around_save(object) object.class.state_machines.transitions(object, action).perform { yield } end # Creates a scope for finding records *with* a particular state or # states for the attribute def create_with_scope(name) create_scope(name, ->(values) { ["#{attribute_column} IN (?)", values] }) end # Creates a scope for finding records *without* a particular state or # states for the attribute def create_without_scope(name) create_scope(name, ->(values) { ["#{attribute_column} NOT IN (?)", values] }) end # Generates the fully-qualifed column name for this machine's attribute def attribute_column connection = owner_class.connection "#{connection.quote_table_name(owner_class.table_name)}.#{connection.quote_column_name(attribute)}" end # Runs a new database transaction, rolling back any changes by raising # an ActiveRecord::Rollback exception if the yielded block fails # (i.e. returns false). def transaction(object) result = nil object.class.transaction do raise ::ActiveRecord::Rollback unless result = yield end result end def locale_path "#{File.dirname(__FILE__)}/active_record/locale.rb" end private # Defines a new named scope with the given name def create_scope(name, scope) lambda { |model, values| model.where(scope.call(values)) } end # ActiveModel's use of method_missing / respond_to for attribute methods # breaks both ancestor lookups and defined?(super). Need to special-case # the existence of query attribute methods. def owner_class_ancestor_has_method?(scope, method) scope == :instance && method == "#{attribute}?" ? owner_class : super end end register(ActiveRecord) end end state_machines-activerecord-0.9.0/lib/state_machines/integrations/active_record/0000755000004100000410000000000014452121716030266 5ustar www-datawww-datastate_machines-activerecord-0.9.0/lib/state_machines/integrations/active_record/version.rb0000644000004100000410000000015514452121716032301 0ustar www-datawww-datamodule StateMachines module Integrations module ActiveRecord VERSION = '0.9.0' end end end state_machines-activerecord-0.9.0/lib/state_machines/integrations/active_record/locale.rb0000644000004100000410000000061414452121716032053 0ustar www-datawww-data{ en: { activerecord: { errors: { messages: { invalid: StateMachines::Machine.default_messages[:invalid], invalid_event: StateMachines::Machine.default_messages[:invalid_event] % ['%{state}'], invalid_transition: StateMachines::Machine.default_messages[:invalid_transition] % ['%{event}'] } } } } } state_machines-activerecord-0.9.0/lib/state_machines-activerecord.rb0000644000004100000410000000032714452121716025746 0ustar www-datawww-datarequire 'active_support' require 'state_machines/integrations/active_record' ActiveSupport.on_load(:i18n) do I18n.load_path << File.expand_path('state_machines/integrations/active_record/locale.rb', __dir__) end state_machines-activerecord-0.9.0/LICENSE.txt0000644000004100000410000000213514452121716021036 0ustar www-datawww-dataCopyright (c) 2006-2012 Aaron Pfeifer Copyright (c) 2014-2023 Abdelkader Boudih MIT License 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.