state-machines-activerecord-0.3.0/0000755000175600017570000000000012617672045016142 5ustar pravipravistate-machines-activerecord-0.3.0/state_machines-activerecord.gemspec0000644000175600017570000000236312617672045025152 0ustar pravipravi# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'state_machines/integrations/active_record/version' Gem::Specification.new do |spec| spec.name = 'state_machines-activerecord' spec.version = StateMachines::Integrations::ActiveRecord::VERSION spec.authors = ['Abdelkader Boudih', 'Aaron Pfeifer'] spec.email = %w(terminale@gmail.com aaron@pluginaweek.org) spec.summary = %q(State machines Active Record Integration) spec.description = %q(Adds support for creating state machines for attributes on ActiveRecord) spec.homepage = 'https://github.com/seuros/state_machines-activerecord' spec.license = 'MIT' spec.files = `git ls-files -z`.split("\x0") spec.test_files = spec.files.grep(/^test\//) spec.require_paths = ['lib'] spec.add_dependency 'state_machines-activemodel', '>= 0.3.0' spec.add_dependency 'activerecord' , '~> 4.1' spec.add_development_dependency 'rake', '~> 10.3' spec.add_development_dependency 'sqlite3', '~> 1.3' spec.add_development_dependency 'appraisal', '>= 1' spec.add_development_dependency 'minitest' , '>= 5.4.0' spec.add_development_dependency 'minitest-reporters' end state-machines-activerecord-0.3.0/LICENSE.txt0000644000175600017570000000213512617672045017766 0ustar pravipraviCopyright (c) 2006-2012 Aaron Pfeifer Copyright (c) 2014-2015 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. state-machines-activerecord-0.3.0/.travis.yml0000644000175600017570000000035112617672045020252 0ustar pravipravilanguage: ruby sudo: false cache: bundler rvm: - 2.1 - 2.2 - jruby - rbx-2 gemfile: - gemfiles/active_record_4.1.gemfile - gemfiles/active_record_4.2.gemfile matrix: allow_failures: - rvm: jruby - rvm: rbx-2 state-machines-activerecord-0.3.0/log/0000755000175600017570000000000012617672045016723 5ustar pravipravistate-machines-activerecord-0.3.0/log/.gitkeep0000644000175600017570000000000012617672045020342 0ustar pravipravistate-machines-activerecord-0.3.0/Appraisals0000644000175600017570000000062412617672045020166 0ustar pravipraviappraise "active_record_4.1" do gem "sqlite3", platforms: [:mri, :rbx] gem "activerecord-jdbcsqlite3-adapter", platform: :jruby gem "activerecord", github: 'rails/rails', branch: '4-1-stable' end appraise "active_record_4.2" do gem "sqlite3", platforms: [:mri, :rbx] gem "activerecord-jdbcsqlite3-adapter", platform: :jruby gem "activerecord", github: 'rails/rails', branch: '4-2-stable' end state-machines-activerecord-0.3.0/README.md0000644000175600017570000000353212617672045017424 0ustar pravipravi[![Build Status](https://travis-ci.org/state-machines/state_machines-activerecord.svg?branch=master)](https://travis-ci.org/state-machines/state_machines-activerecord) [![Code Climate](https://codeclimate.com/github/state-machines/state_machines-activerecord.png)](https://codeclimate.com/github/state-machines/state_machines-activerecord) # StateMachines Active Record Integration The Active Record 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 ```ruby class Vehicle < ActiveRecord::Base 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_presence_of :seatbelt_on 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 ``` Dependencies Active Record 4.1+ ## 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.3.0/test/0000755000175600017570000000000012617672045017121 5ustar pravipravistate-machines-activerecord-0.3.0/test/machine_with_non_column_state_attribute_defined_test.rb0000644000175600017570000000144012617672045032333 0ustar pravipravirequire_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.3.0/test/machine_unmigrated_test.rb0000644000175600017570000000056512617672045024336 0ustar pravipravirequire_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.3.0/test/machine_with_event_attributes_on_save_test.rb0000644000175600017570000001422512617672045030331 0ustar pravipravirequire_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| false } 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| false } ran_callback = false @machine.after_failure { ran_callback = true } begin ; @record.save; rescue; end assert ran_callback end def test_should_not_run_around_callbacks_if_fails @model.before_create { |record| false } ran_callback = false @machine.around_transition { |block| block.call; ran_callback = true } begin ; @record.save; rescue; end 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_equal 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_equal 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.3.0/test/model_test.rb0000644000175600017570000000056112617672045021607 0ustar pravipravirequire_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.3.0/test/machine_with_dynamic_initial_state_test.rb0000644000175600017570000000454612617672045027572 0ustar pravipravirequire_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.3.0/test/machine_with_conflicting_predicate_test.rb0000644000175600017570000000053612617672045027547 0ustar pravipravirequire_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.3.0/test/machine_with_callbacks_test.rb0000644000175600017570000001114512617672045025145 0ustar pravipravirequire_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.3.0/test/machine_with_events_test.rb0000644000175600017570000000046012617672045024530 0ustar pravipravirequire_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.3.0/test/test_helper.rb0000644000175600017570000000243012617672045021763 0ustar pravipravibegin 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") if ActiveSupport.gem_version >= Gem::Version.new('4.2.0') ActiveSupport.test_order = :random ActiveRecord::Base.raise_in_transactional_callbacks = true end 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 ( 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 end state-machines-activerecord-0.3.0/test/machine_with_complex_pluralization_scopes_test.rb0000644000175600017570000000060712617672045031227 0ustar pravipravirequire_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.3.0/test/machine_with_validations_test.rb0000644000175600017570000000227112617672045025543 0ustar pravipravirequire_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.3.0/test/machine_with_custom_attribute_test.rb0000644000175600017570000000100712617672045026617 0ustar pravipravirequire_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.3.0/test/machine_without_transactions_test.rb0000644000175600017570000000106512617672045026466 0ustar pravipravirequire_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.3.0/test/machine_by_default_test.rb0000644000175600017570000000053012617672045024305 0ustar pravipravirequire_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.3.0/test/machine_multiple_test.rb0000644000175600017570000000077512617672045024035 0ustar pravipravirequire_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.3.0/test/machine_with_state_driven_validations_test.rb0000644000175600017570000000152412617672045030312 0ustar pravipravirequire_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_presence_of :seatbelt end @machine.other_states :parked end def test_should_be_valid_if_validation_fails_outside_state_scope record = @model.new(:state => 'parked', :seatbelt => nil) assert record.valid? end def test_should_be_invalid_if_validation_fails_within_state_scope record = @model.new(:state => 'first_gear', :seatbelt => nil) 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.3.0/test/machine_with_same_column_default_test.rb0000644000175600017570000000121312617672045027227 0ustar pravipravirequire_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.3.0/test/machine_nested_action_test.rb0000644000175600017570000000160412617672045025011 0ustar pravipravirequire_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.3.0/test/machine_without_database_test.rb0000644000175600017570000000070712617672045025524 0ustar pravipravirequire_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.3.0/test/machine_with_failed_action_test.rb0000644000175600017570000000221712617672045026007 0ustar pravipravirequire_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.3.0/test/machine_with_event_attributes_on_autosave_test.rb0000644000175600017570000000273312617672045031223 0ustar pravipravirequire_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 ActiveSupport::Dependencies.clear if defined?(ActiveSupport::Dependencies) super end end state-machines-activerecord-0.3.0/test/machine_with_column_state_attribute_test.rb0000644000175600017570000000216312617672045030006 0ustar pravipravirequire_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.3.0/test/machine_with_validations_and_custom_attribute_test.rb0000644000175600017570000000101512617672045032035 0ustar pravipravirequire_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.3.0/test/machine_with_different_column_default_test.rb0000644000175600017570000000143312617672045030254 0ustar pravipravirequire_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.3.0/test/machine_with_scopes_test.rb0000644000175600017570000000410412617672045024517 0ustar pravipravirequire_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' idling = @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' first_gear = @model.create :state => 'first_gear' assert_equal [parked, idling], @model.without_states(:first_gear).all end def test_should_allow_chaining_scopes parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [idling], @model.without_state(:parked).with_state(:idling).all end end state-machines-activerecord-0.3.0/test/machine_with_dirty_attributes_test.rb0000644000175600017570000000175712617672045026637 0ustar pravipravirequire_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.3.0/test/machine_with_event_attributes_on_custom_action_test.rb0000644000175600017570000000163212617672045032240 0ustar pravipravirequire_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.3.0/test/machine_errors_test.rb0000644000175600017570000000110012617672045023475 0ustar pravipravirequire_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.3.0/test/machine_with_conflicting_state_name_test.rb0000644000175600017570000000150512617672045027724 0ustar pravipravirequire_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.3.0/test/machine_with_event_attributes_on_save_bang_test.rb0000644000175600017570000000372212617672045031320 0ustar pravipravirequire_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.3.0/test/machine_with_failed_before_callbacks_test.rb0000644000175600017570000000203412617672045027770 0ustar pravipravirequire_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.3.0/test/machine_with_internationalization_test.rb0000644000175600017570000001463512617672045027502 0ustar pravipravirequire_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.3.0/test/machine_with_transactions_test.rb0000644000175600017570000000105512617672045025735 0ustar pravipravirequire_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 ././@LongLink0000644000000000000000000000016200000000000011602 Lustar rootrootstate-machines-activerecord-0.3.0/test/machine_with_dirty_attribute_and_custom_attributes_during_loopback_test.rbstate-machines-activerecord-0.3.0/test/machine_with_dirty_attribute_and_custom_attributes_during_loo0000644000175600017570000000126212617672045033705 0ustar pravipravirequire_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_equal nil, @record.changes['status'] end end state-machines-activerecord-0.3.0/test/machine_with_scopes_and_owner_subclass_test.rb0000644000175600017570000000163512617672045030460 0ustar pravipravirequire_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' first_gear = @subclass.create :state => 'first_gear' assert_equal [parked, idling], @subclass.without_states(:first_gear).all end end state-machines-activerecord-0.3.0/test/machine_with_initialized_state_test.rb0000644000175600017570000000164412617672045026736 0ustar pravipravirequire_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_dynamic @machine.initial_state = lambda { :parked } record = @model.new(:state => 'idling') assert_equal 'idling', record.state end end state-machines-activerecord-0.3.0/test/machine_with_aliased_attribute_test.rb0000644000175600017570000000103212617672045026705 0ustar pravipravirequire_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.3.0/test/machine_with_dirty_attributes_during_loopback_test.rb0000644000175600017570000000112212617672045032043 0ustar pravipravirequire_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_equal nil, @record.changes['state'] end end state-machines-activerecord-0.3.0/test/machine_with_static_initial_state_test.rb0000644000175600017570000001122612617672045027426 0ustar pravipravirequire_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 record = @model.create(:state => 'idling', :owner_id => owner.id) assert_equal 'idling', owner.vehicles[0].state end def test_should_use_stored_values_when_loading_for_one_association @machine.state :idling @model.connection.add_column @model.table_name, :owner_id, :integer @model.reset_column_information 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 record = @model.create(:state => 'idling', :owner_id => owner.id) assert_equal 'idling', owner.vehicle.state end def test_should_use_stored_values_when_loading_for_belongs_to_association @machine.state :idling 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 ActiveSupport::Dependencies.clear super end end state-machines-activerecord-0.3.0/test/files/0000755000175600017570000000000012617672045020223 5ustar pravipravistate-machines-activerecord-0.3.0/test/files/models/0000755000175600017570000000000012617672045021506 5ustar pravipravistate-machines-activerecord-0.3.0/test/files/models/post.rb0000644000175600017570000000032412617672045023017 0ustar pravipraviActiveRecord::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.3.0/test/files/en.yml0000644000175600017570000000013712617672045021351 0ustar pravipravien: activerecord: errors: messages: invalid_transition: "cannot transition"state-machines-activerecord-0.3.0/test/machine_with_states_test.rb0000644000175600017570000000046612617672045024535 0ustar pravipravirequire_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.3.0/test/machine_with_dirty_attributes_and_custom_attribute_test.rb0000644000175600017570000000171512617672045033130 0ustar pravipravirequire_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.3.0/test/machine_with_scopes_and_joins_test.rb0000644000175600017570000000237512617672045026553 0ustar pravipravirequire_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 ActiveSupport::Dependencies.clear end end state-machines-activerecord-0.3.0/test/machine_with_event_attributes_on_validation_test.rb0000644000175600017570000000640712617672045031530 0ustar pravipravirequire_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_presence_of :seatbelt 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_presence_of :seatbelt end ran_callback = false @machine.after_failure { ran_callback = true } @record.valid? assert ran_callback end def test_should_not_run_around_callbacks_after_yield ran_callback = false @machine.around_transition { |block| block.call; ran_callback = true } begin @record.valid? rescue ArgumentError raise if 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_presence_of :seatbelt 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.3.0/test/machine_with_failed_after_callbacks_test.rb0000644000175600017570000000177112617672045027636 0ustar pravipravirequire_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 state-machines-activerecord-0.3.0/test/machine_with_loopback_test.rb0000644000175600017570000000112312617672045025013 0ustar pravipravirequire_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.3.0/test/machine_with_different_integer_column_default_test.rb0000644000175600017570000000147612617672045032000 0ustar pravipravirequire_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.3.0/test/integration_test.rb0000644000175600017570000000177612617672045023043 0ustar pravipravirequire_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 def test_should_have_a_locale_path assert_not_nil StateMachines::Integrations::ActiveRecord.locale_path end end state-machines-activerecord-0.3.0/test/machine_with_dirty_attribute_and_state_events_test.rb0000644000175600017570000000077112617672045032055 0ustar pravipravirequire_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_equal nil, @record.changes['state'] end end state-machines-activerecord-0.3.0/test/machine_with_default_scope_test.rb0000644000175600017570000000066412617672045026047 0ustar pravipravirequire_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.3.0/Gemfile0000644000175600017570000000013412617672045017433 0ustar pravipravisource 'https://rubygems.org' gemspec platforms :mri_20, :mri_21 do gem 'pry-byebug' end state-machines-activerecord-0.3.0/metadata.yml0000644000175600017570000002105112617672045020444 0ustar pravipravi--- !ruby/object:Gem::Specification name: state_machines-activerecord version: !ruby/object:Gem::Version version: 0.3.0 platform: ruby authors: - Abdelkader Boudih - Aaron Pfeifer autorequire: bindir: bin cert_chain: [] date: 2015-07-13 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: state_machines-activemodel requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 0.3.0 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 0.3.0 - !ruby/object:Gem::Dependency name: activerecord requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '4.1' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '4.1' - !ruby/object:Gem::Dependency name: rake requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '10.3' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '10.3' - !ruby/object:Gem::Dependency name: sqlite3 requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.3' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.3' - !ruby/object:Gem::Dependency name: appraisal requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '1' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '1' - !ruby/object:Gem::Dependency name: minitest requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 5.4.0 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 5.4.0 - !ruby/object:Gem::Dependency name: minitest-reporters requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' description: Adds support for creating state machines for attributes on ActiveRecord email: - terminale@gmail.com - aaron@pluginaweek.org executables: [] extensions: [] extra_rdoc_files: [] files: - ".gitignore" - ".travis.yml" - Appraisals - Gemfile - LICENSE.txt - README.md - Rakefile - gemfiles/active_record_4.1.gemfile - gemfiles/active_record_4.2.gemfile - lib/state_machines-activerecord.rb - lib/state_machines/integrations/active_record.rb - lib/state_machines/integrations/active_record/locale.rb - lib/state_machines/integrations/active_record/version.rb - log/.gitkeep - state_machines-activerecord.gemspec - test/files/en.yml - test/files/models/post.rb - test/integration_test.rb - test/machine_by_default_test.rb - test/machine_errors_test.rb - test/machine_multiple_test.rb - test/machine_nested_action_test.rb - test/machine_unmigrated_test.rb - test/machine_with_aliased_attribute_test.rb - test/machine_with_callbacks_test.rb - test/machine_with_column_state_attribute_test.rb - test/machine_with_complex_pluralization_scopes_test.rb - test/machine_with_conflicting_predicate_test.rb - test/machine_with_conflicting_state_name_test.rb - test/machine_with_custom_attribute_test.rb - test/machine_with_default_scope_test.rb - test/machine_with_different_column_default_test.rb - test/machine_with_different_integer_column_default_test.rb - test/machine_with_dirty_attribute_and_custom_attributes_during_loopback_test.rb - test/machine_with_dirty_attribute_and_state_events_test.rb - test/machine_with_dirty_attributes_and_custom_attribute_test.rb - test/machine_with_dirty_attributes_during_loopback_test.rb - test/machine_with_dirty_attributes_test.rb - test/machine_with_dynamic_initial_state_test.rb - test/machine_with_event_attributes_on_autosave_test.rb - test/machine_with_event_attributes_on_custom_action_test.rb - test/machine_with_event_attributes_on_save_bang_test.rb - test/machine_with_event_attributes_on_save_test.rb - test/machine_with_event_attributes_on_validation_test.rb - test/machine_with_events_test.rb - test/machine_with_failed_action_test.rb - test/machine_with_failed_after_callbacks_test.rb - test/machine_with_failed_before_callbacks_test.rb - test/machine_with_initialized_state_test.rb - test/machine_with_internationalization_test.rb - test/machine_with_loopback_test.rb - test/machine_with_non_column_state_attribute_defined_test.rb - test/machine_with_same_column_default_test.rb - test/machine_with_scopes_and_joins_test.rb - test/machine_with_scopes_and_owner_subclass_test.rb - test/machine_with_scopes_test.rb - test/machine_with_state_driven_validations_test.rb - test/machine_with_states_test.rb - test/machine_with_static_initial_state_test.rb - test/machine_with_transactions_test.rb - test/machine_with_validations_and_custom_attribute_test.rb - test/machine_with_validations_test.rb - test/machine_without_database_test.rb - test/machine_without_transactions_test.rb - test/model_test.rb - test/test_helper.rb homepage: https://github.com/seuros/state_machines-activerecord licenses: - MIT metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.4.7 signing_key: specification_version: 4 summary: State machines Active Record Integration test_files: - test/files/en.yml - test/files/models/post.rb - test/integration_test.rb - test/machine_by_default_test.rb - test/machine_errors_test.rb - test/machine_multiple_test.rb - test/machine_nested_action_test.rb - test/machine_unmigrated_test.rb - test/machine_with_aliased_attribute_test.rb - test/machine_with_callbacks_test.rb - test/machine_with_column_state_attribute_test.rb - test/machine_with_complex_pluralization_scopes_test.rb - test/machine_with_conflicting_predicate_test.rb - test/machine_with_conflicting_state_name_test.rb - test/machine_with_custom_attribute_test.rb - test/machine_with_default_scope_test.rb - test/machine_with_different_column_default_test.rb - test/machine_with_different_integer_column_default_test.rb - test/machine_with_dirty_attribute_and_custom_attributes_during_loopback_test.rb - test/machine_with_dirty_attribute_and_state_events_test.rb - test/machine_with_dirty_attributes_and_custom_attribute_test.rb - test/machine_with_dirty_attributes_during_loopback_test.rb - test/machine_with_dirty_attributes_test.rb - test/machine_with_dynamic_initial_state_test.rb - test/machine_with_event_attributes_on_autosave_test.rb - test/machine_with_event_attributes_on_custom_action_test.rb - test/machine_with_event_attributes_on_save_bang_test.rb - test/machine_with_event_attributes_on_save_test.rb - test/machine_with_event_attributes_on_validation_test.rb - test/machine_with_events_test.rb - test/machine_with_failed_action_test.rb - test/machine_with_failed_after_callbacks_test.rb - test/machine_with_failed_before_callbacks_test.rb - test/machine_with_initialized_state_test.rb - test/machine_with_internationalization_test.rb - test/machine_with_loopback_test.rb - test/machine_with_non_column_state_attribute_defined_test.rb - test/machine_with_same_column_default_test.rb - test/machine_with_scopes_and_joins_test.rb - test/machine_with_scopes_and_owner_subclass_test.rb - test/machine_with_scopes_test.rb - test/machine_with_state_driven_validations_test.rb - test/machine_with_states_test.rb - test/machine_with_static_initial_state_test.rb - test/machine_with_transactions_test.rb - test/machine_with_validations_and_custom_attribute_test.rb - test/machine_with_validations_test.rb - test/machine_without_database_test.rb - test/machine_without_transactions_test.rb - test/model_test.rb - test/test_helper.rb state-machines-activerecord-0.3.0/lib/0000755000175600017570000000000012617672045016710 5ustar pravipravistate-machines-activerecord-0.3.0/lib/state_machines/0000755000175600017570000000000012617672045021677 5ustar pravipravistate-machines-activerecord-0.3.0/lib/state_machines/integrations/0000755000175600017570000000000012617672045024405 5ustar pravipravistate-machines-activerecord-0.3.0/lib/state_machines/integrations/active_record/0000755000175600017570000000000012617672045027216 5ustar pravipravistate-machines-activerecord-0.3.0/lib/state_machines/integrations/active_record/version.rb0000644000175600017570000000015512617672045031231 0ustar pravipravimodule StateMachines module Integrations module ActiveRecord VERSION = '0.3.0' end end end state-machines-activerecord-0.3.0/lib/state_machines/integrations/active_record/locale.rb0000644000175600017570000000061412617672045031003 0ustar pravipravi{ 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.3.0/lib/state_machines/integrations/active_record.rb0000644000175600017570000005240712617672045027553 0ustar pravipravirequire '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 < ActiveRecord::Base # 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 < ActiveRecord::Base # 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 < ActiveRecord::Base # 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 < ActiveRecord::Base # 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 < ActiveRecord::Base # 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 < ActiveRecord::Base # 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 < ActiveRecord::Base # 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 < ActiveRecord::Base # named_scope :with_states, lambda {|*states| {:conditions => {:state => states}}} # # 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 < ActiveRecord::Base # 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 < ActiveRecord::Base # establish_connection Rails.env.to_sym # end # # class Vehicle < ActiveRecord::Base # 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 %w(ActiveRecord::Base) end def locale_path "#{File.dirname(__FILE__)}/active_record/locale.rb" 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? if column = owner_class.columns_hash[attribute.to_s] column.default end end end def define_state_initializer if ::ActiveRecord.gem_version >= Gem::Version.new('4.2') define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def initialize(attributes = nil, options = {}) super(attributes, options) do |*args| self.class.state_machines.initialize_states(self, {}, attributes || {}) yield(*args) if block_given? end end end_eval else # Initializes static states # # This is the only available hook where the default set of attributes # can be overridden for a new object *prior* to the processing of the # attributes passed into #initialize define_helper :class, <<-end_eval, __FILE__, __LINE__ + 1 def column_defaults(*) #:nodoc: result = super # No need to pass in an object, since the overrides will be forced self.state_machines.initialize_states(nil, :static => :force, :dynamic => false, :to => result) result end end_eval # Initializes dynamic states define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def initialize(attributes = nil, options = {}) super(attributes, options) do |*args| self.class.state_machines.initialize_states(self, {}, attributes || {}) yield(*args) if block_given? end end end_eval end 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.3.0/lib/state_machines-activerecord.rb0000644000175600017570000000006412617672045024674 0ustar pravipravirequire 'state_machines/integrations/active_record' state-machines-activerecord-0.3.0/gemfiles/0000755000175600017570000000000012617672045017735 5ustar pravipravistate-machines-activerecord-0.3.0/gemfiles/active_record_4.1.gemfile0000644000175600017570000000050112617672045024456 0ustar pravipravi# This file was generated by Appraisal source "https://rubygems.org" gem "sqlite3", :platforms => [:mri, :rbx] gem "activerecord-jdbcsqlite3-adapter", :platform => :jruby gem "activerecord", :github => "rails/rails", :branch => "4-1-stable" platforms :mri_20, :mri_21 do gem "pry-byebug" end gemspec :path => "../" state-machines-activerecord-0.3.0/gemfiles/active_record_4.2.gemfile0000644000175600017570000000050112617672045024457 0ustar pravipravi# This file was generated by Appraisal source "https://rubygems.org" gem "sqlite3", :platforms => [:mri, :rbx] gem "activerecord-jdbcsqlite3-adapter", :platform => :jruby gem "activerecord", :github => "rails/rails", :branch => "4-2-stable" platforms :mri_20, :mri_21 do gem "pry-byebug" end gemspec :path => "../" state-machines-activerecord-0.3.0/Rakefile0000644000175600017570000000025112617672045017605 0ustar pravipravirequire 'bundler/gem_tasks' require 'rake/testtask' Rake::TestTask.new do |t| t.pattern = 'test/*_test.rb' end desc 'Default: run all tests.' task :default => :test state-machines-activerecord-0.3.0/.gitignore0000644000175600017570000000027112617672045020132 0ustar pravipravi*.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports tmp *.bundle *.so *.o *.a log/active_record.log .idea/ *.lock