state_machines-activerecord-0.4.0/0000755000004100000410000000000012717476705017222 5ustar www-datawww-datastate_machines-activerecord-0.4.0/Rakefile0000644000004100000410000000025112717476705020665 0ustar www-datawww-datarequire '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.4.0/Gemfile0000644000004100000410000000013412717476705020513 0ustar www-datawww-datasource 'https://rubygems.org' gemspec platforms :mri_20, :mri_21 do gem 'pry-byebug' end state_machines-activerecord-0.4.0/log/0000755000004100000410000000000012717476705020003 5ustar www-datawww-datastate_machines-activerecord-0.4.0/log/.gitkeep0000644000004100000410000000000012717476705021422 0ustar www-datawww-datastate_machines-activerecord-0.4.0/LICENSE.txt0000644000004100000410000000213512717476705021046 0ustar www-datawww-dataCopyright (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.4.0/.travis.yml0000644000004100000410000000122612717476705021334 0ustar www-datawww-datalanguage: ruby sudo: false cache: bundler rvm: - 2.1 - 2.2.5 - 2.3.1 - jruby - rbx-2 gemfile: - gemfiles/active_record_4.1.gemfile - gemfiles/active_record_4.2.gemfile matrix: include: - gemfile: gemfiles/active_record_5.0.gemfile rvm: 2.2.5 - gemfile: gemfiles/active_record_5.0.gemfile rvm: 2.3.1 - gemfile: gemfiles/active_record_edge.gemfile rvm: 2.2.5 - gemfile: gemfiles/active_record_edge.gemfile rvm: 2.3.1 allow_failures: - rvm: jruby - rvm: rbx-2 - gemfile: gemfiles/active_record_edge.gemfile rvm: 2.2.5 - gemfile: gemfiles/active_record_edge.gemfile rvm: 2.3.1 state_machines-activerecord-0.4.0/lib/0000755000004100000410000000000012717476705017770 5ustar www-datawww-datastate_machines-activerecord-0.4.0/lib/state_machines/0000755000004100000410000000000012717476705022757 5ustar www-datawww-datastate_machines-activerecord-0.4.0/lib/state_machines/integrations/0000755000004100000410000000000012717476705025465 5ustar www-datawww-datastate_machines-activerecord-0.4.0/lib/state_machines/integrations/active_record.rb0000644000004100000410000005276212717476705030637 0ustar www-datawww-datarequire 'state_machines-activemodel' require 'active_record' require 'state_machines/integrations/active_record/version' module StateMachines module Integrations #:nodoc: # Adds support for integrating state machines with ActiveRecord models. # # == Examples # # Below is an example of a simple state machine defined within an # ActiveRecord model: # # class Vehicle < 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('5.0.0.alpha') define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def initialize(attributes = nil) super(attributes) do |*args| self.class.state_machines.initialize_states(self, {}, attributes || {}) yield(*args) if block_given? end end end_eval elsif ::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.4.0/lib/state_machines/integrations/active_record/0000755000004100000410000000000012717476705030276 5ustar www-datawww-datastate_machines-activerecord-0.4.0/lib/state_machines/integrations/active_record/version.rb0000644000004100000410000000015512717476705032311 0ustar www-datawww-datamodule StateMachines module Integrations module ActiveRecord VERSION = '0.4.0' end end end state_machines-activerecord-0.4.0/lib/state_machines/integrations/active_record/locale.rb0000644000004100000410000000061412717476705032063 0ustar www-datawww-data{ en: { activerecord: { errors: { messages: { invalid: StateMachines::Machine.default_messages[:invalid], invalid_event: StateMachines::Machine.default_messages[:invalid_event] % ['%{state}'], invalid_transition: StateMachines::Machine.default_messages[:invalid_transition] % ['%{event}'] } } } } } state_machines-activerecord-0.4.0/lib/state_machines-activerecord.rb0000644000004100000410000000006412717476705025754 0ustar www-datawww-datarequire 'state_machines/integrations/active_record' state_machines-activerecord-0.4.0/gemfiles/0000755000004100000410000000000012717476705021015 5ustar www-datawww-datastate_machines-activerecord-0.4.0/gemfiles/active_record_4.2.gemfile0000644000004100000410000000050112717476705025537 0ustar www-datawww-data# 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.4.0/gemfiles/active_record_edge.gemfile0000644000004100000410000000057612717476705026154 0ustar www-datawww-data# 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 => "master" gem "activemodel", :github => "rails/rails", :branch => "master" platforms :mri_20, :mri_21 do gem "pry-byebug" end gemspec :path => "../" state_machines-activerecord-0.4.0/gemfiles/active_record_5.0.gemfile0000644000004100000410000000060612717476705025544 0ustar www-datawww-data# 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 => "5-0-stable" gem "activemodel", :github => "rails/rails", :branch => "5-0-stable" platforms :mri_20, :mri_21 do gem "pry-byebug" end gemspec :path => "../" state_machines-activerecord-0.4.0/gemfiles/active_record_4.1.gemfile0000644000004100000410000000050112717476705025536 0ustar www-datawww-data# 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.4.0/test/0000755000004100000410000000000012717476705020201 5ustar www-datawww-datastate_machines-activerecord-0.4.0/test/machine_errors_test.rb0000644000004100000410000000110012717476705024555 0ustar www-datawww-datarequire_relative 'test_helper' class MachineErrorsTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model) @record = @model.new end def test_should_be_able_to_describe_current_errors @record.errors.add(:id, 'cannot be blank') @record.errors.add(:state, 'is invalid') assert_equal ['Id cannot be blank', 'State is invalid'], @machine.errors_for(@record).split(', ').sort end def test_should_describe_as_halted_with_no_errors assert_equal 'Transition halted', @machine.errors_for(@record) end end state_machines-activerecord-0.4.0/test/machine_unmigrated_test.rb0000644000004100000410000000056512717476705025416 0ustar www-datawww-datarequire_relative 'test_helper' class MachineUnmigratedTest < BaseTestCase def setup @model = new_model(false) # Drop the table so that it definitely doesn't exist @model.connection.drop_table(@model.table_name) if @model.table_exists? end def test_should_allow_machine_creation assert_nothing_raised { StateMachines::Machine.new(@model) } end endstate_machines-activerecord-0.4.0/test/machine_with_event_attributes_on_validation_test.rb0000644000004100000410000000640712717476705032610 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithEventAttributesOnValidationTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model) @machine.event :ignite do transition :parked => :idling end @record = @model.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_fail_if_event_is_invalid @record.state_event = 'invalid' refute @record.valid? assert_equal ['State event is invalid'], @record.errors.full_messages end def test_should_fail_if_event_has_no_transition @record.state = 'idling' refute @record.valid? assert_equal ['State event cannot transition when idling'], @record.errors.full_messages end def test_should_be_successful_if_event_has_transition assert @record.valid? end def test_should_run_before_callbacks ran_callback = false @machine.before_transition { ran_callback = true } @record.valid? assert ran_callback end def test_should_run_around_callbacks_before_yield ran_callback = false @machine.around_transition { |block| ran_callback = true; block.call } begin @record.valid? rescue ArgumentError raise if StateMachines::Transition.pause_supported? end assert ran_callback end def test_should_persist_new_state @record.valid? assert_equal 'idling', @record.state end def test_should_not_run_after_callbacks ran_callback = false @machine.after_transition { ran_callback = true } @record.valid? refute ran_callback end def test_should_not_run_after_callbacks_with_failures_disabled_if_validation_fails @model.class_eval do attr_accessor :seatbelt validates_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.4.0/test/machine_with_different_column_default_test.rb0000644000004100000410000000143312717476705031334 0ustar www-datawww-datarequire_relative 'test_helper' require 'stringio' class MachineWithDifferentColumnDefaultTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @model = new_model do connection.add_column table_name, :status, :string, :default => 'idling' end @machine = StateMachines::Machine.new(@model, :status, :initial => :parked) @record = @model.new end def test_should_use_machine_default assert_equal 'parked', @record.status end def test_should_generate_a_warning assert_match(/Both Foo and its :status machine have defined a different default for "status". Use only one or the other for defining defaults to avoid unexpected behaviors\./, $stderr.string) end def teardown $stderr = @original_stderr super end end state_machines-activerecord-0.4.0/test/machine_nested_action_test.rb0000644000004100000410000000160412717476705026071 0ustar www-datawww-datarequire_relative 'test_helper' class MachineNestedActionTest < BaseTestCase def setup @callbacks = [] @model = new_model @machine = StateMachines::Machine.new(@model) @machine.event :ignite do transition :parked => :idling end @record = @model.new(:state => 'parked') end def test_should_allow_transition_prior_to_creation_if_skipping_action record = @record @model.before_create { record.ignite(false) } result = @record.save assert_equal true, result assert_equal "idling", @record.state @record.reload assert_equal "idling", @record.state end def test_should_allow_transition_after_creation record = @record @model.after_create { record.ignite } result = @record.save assert_equal true, result assert_equal "idling", @record.state @record.reload assert_equal "idling", @record.state end end state_machines-activerecord-0.4.0/test/machine_with_non_column_state_attribute_defined_test.rb0000644000004100000410000000144012717476705033413 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithNonColumnStateAttributeDefinedTest < BaseTestCase def setup @model = new_model do attr_accessor :status end @machine = StateMachines::Machine.new(@model, :status, :initial => :parked) @machine.other_states(:idling) @record = @model.new end def test_should_return_false_for_predicate_if_does_not_match_current_value refute @record.status?(:idling) end def test_should_return_true_for_predicate_if_matches_current_value assert @record.status?(:parked) end def test_should_raise_exception_for_predicate_if_invalid_state_specified assert_raise(IndexError) { @record.status?(:invalid) } end def test_should_set_initial_state_on_created_object assert_equal 'parked', @record.status end end state_machines-activerecord-0.4.0/test/machine_with_validations_and_custom_attribute_test.rb0000644000004100000410000000101512717476705033115 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithValidationsAndCustomAttributeTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model, :status, :attribute => :state) @machine.state :parked @record = @model.new end def test_should_add_validation_errors_to_custom_attribute @record.state = 'invalid' refute @record.valid? assert_equal ['State is invalid'], @record.errors.full_messages @record.state = 'parked' assert @record.valid? end end state_machines-activerecord-0.4.0/test/machine_with_default_scope_test.rb0000644000004100000410000000066412717476705027127 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithDefaultScopeTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model, :initial => :parked) @machine.state :idling @model.class_eval do default_scope { with_state(:parked, :idling) } end end def test_should_set_initial_state_on_created_object object = @model.new assert_equal 'parked', object.state end end state_machines-activerecord-0.4.0/test/machine_with_aliased_attribute_test.rb0000644000004100000410000000103212717476705027765 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithAliasedAttributeTest < BaseTestCase def setup @model = new_model do alias_attribute :vehicle_status, :state end @machine = StateMachines::Machine.new(@model, :status, :attribute => :vehicle_status) @machine.state :parked @record = @model.new end def test_should_check_custom_attribute_for_predicate @record.vehicle_status = nil refute @record.status?(:parked) @record.vehicle_status = 'parked' assert @record.status?(:parked) end end state_machines-activerecord-0.4.0/test/machine_with_transactions_test.rb0000644000004100000410000000105512717476705027015 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithTransactionsTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model, :use_transactions => true) end def test_should_rollback_transaction_if_false @machine.within_transaction(@model.new) do @model.create false end assert_equal 0, @model.count end def test_should_not_rollback_transaction_if_true @machine.within_transaction(@model.new) do @model.create true end assert_equal 1, @model.count end end state_machines-activerecord-0.4.0/test/machine_without_database_test.rb0000644000004100000410000000070712717476705026604 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithoutDatabaseTest < BaseTestCase def setup @model = new_model(false) do # Simulate the database not being available entirely def self.connection raise ActiveRecord::ConnectionNotEstablished end def self.connected? false end end end def test_should_allow_machine_creation assert_nothing_raised { StateMachines::Machine.new(@model) } end end state_machines-activerecord-0.4.0/test/machine_with_event_attributes_on_autosave_test.rb0000644000004100000410000000273312717476705032303 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithEventAttributesOnAutosaveTest < BaseTestCase def setup @vehicle_model = new_model(:vehicle) do connection.add_column table_name, :owner_id, :integer end MachineWithEventAttributesOnAutosaveTest.const_set('Vehicle', @vehicle_model) @owner_model = new_model(:owner) MachineWithEventAttributesOnAutosaveTest.const_set('Owner', @owner_model) machine = StateMachines::Machine.new(@vehicle_model) machine.event :ignite do transition :parked => :idling end @owner = @owner_model.create @vehicle = @vehicle_model.create(:state => 'parked', :owner_id => @owner.id) end def test_should_persist_has_one_autosave @owner_model.has_one :vehicle, :class_name => 'MachineWithEventAttributesOnAutosaveTest::Vehicle', :autosave => true @owner.vehicle.state_event = 'ignite' @owner.save @vehicle.reload assert_equal 'idling', @vehicle.state end def test_should_persist_has_many_autosave @owner_model.has_many :vehicles, :class_name => 'MachineWithEventAttributesOnAutosaveTest::Vehicle', :autosave => true @owner.vehicles[0].state_event = 'ignite' @owner.save @vehicle.reload assert_equal 'idling', @vehicle.state end def teardown MachineWithEventAttributesOnAutosaveTest.class_eval do remove_const('Vehicle') remove_const('Owner') end ActiveSupport::Dependencies.clear if defined?(ActiveSupport::Dependencies) super end end state_machines-activerecord-0.4.0/test/files/0000755000004100000410000000000012717476705021303 5ustar www-datawww-datastate_machines-activerecord-0.4.0/test/files/en.yml0000644000004100000410000000013712717476705022431 0ustar www-datawww-dataen: activerecord: errors: messages: invalid_transition: "cannot transition"state_machines-activerecord-0.4.0/test/files/models/0000755000004100000410000000000012717476705022566 5ustar www-datawww-datastate_machines-activerecord-0.4.0/test/files/models/post.rb0000644000004100000410000000032412717476705024077 0ustar www-datawww-dataActiveRecord::Base.connection.create_table(:posts, :force => true) do |t| t.string :title t.string :content t.string :state end class Post < ActiveRecord::Base state_machine initial: :draft do end endstate_machines-activerecord-0.4.0/test/machine_with_static_initial_state_test.rb0000644000004100000410000001122612717476705030506 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithStaticInitialStateTest < BaseTestCase def setup @model = new_model(:vehicle) do attr_accessor :value end @machine = StateMachines::Machine.new(@model, :initial => :parked) end def test_should_set_initial_state_on_created_object record = @model.new assert_equal 'parked', record.state end def test_should_set_initial_state_with_nil_attributes record = @model.new(nil) assert_equal 'parked', record.state end def test_should_still_set_attributes record = @model.new(:value => 1) assert_equal 1, record.value end def test_should_still_allow_initialize_blocks block_args = nil record = @model.new do |*args| block_args = args end assert_equal [record], block_args end def test_should_set_attributes_prior_to_initialize_block state = nil @model.new do |record| state = record.state end assert_equal 'parked', state end def test_should_set_attributes_prior_to_after_initialize_hook state = nil @model.after_initialize do |record| state = record.state end @model.new assert_equal 'parked', state end def test_should_set_initial_state_before_setting_attributes @model.class_eval do attr_accessor :state_during_setter remove_method :value= define_method(:value=) do |value| self.state_during_setter = state end end record = @model.new record.value = 1 assert_equal 'parked', record.state_during_setter end def test_should_not_set_initial_state_after_already_initialized record = @model.new(:value => 1) assert_equal 'parked', record.state record.state = 'idling' record.attributes = {} assert_equal 'idling', record.state end def test_should_persist_initial_state record = @model.new record.save record.reload assert_equal 'parked', record.state end def test_should_persist_initial_state_on_dup record = @model.create.dup record.save record.reload assert_equal 'parked', record.state end def test_should_use_stored_values_when_loading_from_database @machine.state :idling record = @model.find(@model.create(:state => 'idling').id) assert_equal 'idling', record.state end def test_should_use_stored_values_when_loading_from_database_with_nil_state @machine.state nil record = @model.find(@model.create(:state => nil).id) assert_nil record.state end def test_should_use_stored_values_when_loading_for_many_association @machine.state :idling @model.connection.add_column @model.table_name, :owner_id, :integer @model.reset_column_information MachineWithStaticInitialStateTest.const_set('Vehicle', @model) owner_model = new_model(:owner) do has_many :vehicles, :class_name => 'MachineWithStaticInitialStateTest::Vehicle' end MachineWithStaticInitialStateTest.const_set('Owner', owner_model) owner = owner_model.create 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.4.0/test/machine_with_complex_pluralization_scopes_test.rb0000644000004100000410000000060712717476705032307 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithComplexPluralizationScopesTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model, :status) end def test_should_create_singular_with_scope assert @model.respond_to?(:with_status) end def test_should_create_plural_with_scope assert @model.respond_to?(:with_statuses) end end state_machines-activerecord-0.4.0/test/machine_with_same_column_default_test.rb0000644000004100000410000000121312717476705030307 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithSameColumnDefaultTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @model = new_model do connection.add_column table_name, :status, :string, :default => 'parked' end @machine = StateMachines::Machine.new(@model, :status, :initial => :parked) @record = @model.new end def test_should_use_machine_default assert_equal 'parked', @record.status end def test_should_not_generate_a_warning assert_no_match(/have defined a different default/, $stderr.string) end def teardown $stderr = @original_stderr super end end state_machines-activerecord-0.4.0/test/machine_with_loopback_test.rb0000644000004100000410000000112312717476705026073 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithLoopbackTest < BaseTestCase def setup @model = new_model do connection.add_column table_name, :updated_at, :datetime end @machine = StateMachines::Machine.new(@model, :initial => :parked) @machine.event :park @record = @model.create(:updated_at => Time.now - 1) @transition = StateMachines::Transition.new(@record, @machine, :park, :parked, :parked) @timestamp = @record.updated_at @transition.perform end def test_should_not_update_record assert_equal @timestamp, @record.updated_at end end state_machines-activerecord-0.4.0/test/machine_multiple_test.rb0000644000004100000410000000077512717476705025115 0ustar www-datawww-datarequire_relative 'test_helper' class MachineMultipleTest < BaseTestCase def setup @model = new_model do connection.add_column table_name, :status, :string end @state_machine = StateMachines::Machine.new(@model, :initial => :parked) @status_machine = StateMachines::Machine.new(@model, :status, :initial => :idling) end def test_should_should_initialize_each_state record = @model.new assert_equal 'parked', record.state assert_equal 'idling', record.status end end state_machines-activerecord-0.4.0/test/machine_with_failed_action_test.rb0000644000004100000410000000221712717476705027067 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithFailedActionTest < BaseTestCase def setup @model = new_model do validates_inclusion_of :state, :in => %w(first_gear) end @machine = StateMachines::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @callbacks = [] @machine.before_transition { @callbacks << :before } @machine.after_transition { @callbacks << :after } @machine.after_failure { @callbacks << :after_failure } @machine.around_transition { |block| @callbacks << :around_before; block.call; @callbacks << :around_after } @record = @model.new(:state => 'parked') @transition = StateMachines::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform end def test_should_not_be_successful refute @result end def test_should_not_change_current_state assert_equal 'parked', @record.state end def test_should_not_save_record assert @record.new_record? end def test_should_run_before_callbacks_and_after_callbacks_with_failures assert_equal [:before, :around_before, :after_failure], @callbacks end end state_machines-activerecord-0.4.0/test/machine_with_events_test.rb0000644000004100000410000000046012717476705025610 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithEventsTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model) @machine.event :shift_up end def test_should_humanize_name assert_equal 'shift up', @machine.event(:shift_up).human_name end end state_machines-activerecord-0.4.0/test/machine_with_dirty_attribute_and_state_events_test.rb0000644000004100000410000000077112717476705033135 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithDirtyAttributeAndStateEventsTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model, :initial => :parked) @machine.event :ignite @record = @model.create @record.state_event = 'ignite' end def test_should_not_include_state_in_changed_attributes assert_equal [], @record.changed end def test_should_not_track_attribute_change assert_equal nil, @record.changes['state'] end end state_machines-activerecord-0.4.0/test/machine_with_dirty_attributes_test.rb0000644000004100000410000000175712717476705027717 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithDirtyAttributesTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model, :initial => :parked) @machine.event :ignite @machine.state :idling @record = @model.create @transition = StateMachines::Transition.new(@record, @machine, :ignite, :parked, :idling) @transition.perform(false) end def test_should_include_state_in_changed_attributes assert_equal %w(state), @record.changed end def test_should_track_attribute_change assert_equal %w(parked idling), @record.changes['state'] end def test_should_not_reset_changes_on_multiple_transitions transition = StateMachines::Transition.new(@record, @machine, :ignite, :idling, :idling) transition.perform(false) assert_equal %w(parked idling), @record.changes['state'] end def test_should_not_have_changes_when_loaded_from_database record = @model.find(@record.id) refute record.changed? end end state_machines-activerecord-0.4.0/test/machine_with_conflicting_state_name_test.rb0000644000004100000410000000150512717476705031004 0ustar www-datawww-datarequire_relative 'test_helper' require 'stringio' class MachineWithConflictingStateNameTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @model = new_model end def test_should_output_warning_with_same_machine_name @machine = StateMachines::Machine.new(@model) @machine.state :state assert_match(/^Instance method "state\?" is already defined in Foo, use generic helper instead.*\n$/, $stderr.string) end def test_should_output_warning_with_same_machine_attribute @machine = StateMachines::Machine.new(@model, :public_state, :attribute => :state) @machine.state :state assert_match(/^Instance method "state\?" is already defined in Foo, use generic helper instead.*\n$/, $stderr.string) end def teardown $stderr = @original_stderr super end end ././@LongLink0000000000000000000000000000016200000000000011564 Lustar rootrootstate_machines-activerecord-0.4.0/test/machine_with_dirty_attribute_and_custom_attributes_during_loopback_test.rbstate_machines-activerecord-0.4.0/test/machine_with_dirty_attribute_and_custom_attributes_during_loo0000644000004100000410000000126212717476705034765 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithDirtyAttributeAndCustomAttributesDuringLoopbackTest < BaseTestCase def setup @model = new_model do connection.add_column table_name, :status, :string end @machine = StateMachines::Machine.new(@model, :status, :initial => :parked) @machine.event :park @record = @model.create @transition = StateMachines::Transition.new(@record, @machine, :park, :parked, :parked) @transition.perform(false) end def test_should_not_include_state_in_changed_attributes assert_equal [], @record.changed end def test_should_not_track_attribute_changes assert_equal nil, @record.changes['status'] end end state_machines-activerecord-0.4.0/test/test_helper.rb0000644000004100000410000000243012717476705023043 0ustar www-datawww-databegin require 'pry-byebug' rescue LoadError end require 'minitest/reporters' Minitest::Reporters.use!(Minitest::Reporters::SpecReporter.new) require 'state_machines-activerecord' require 'minitest/autorun' require 'securerandom' # Establish database connection ActiveRecord::Base.establish_connection('adapter' => 'sqlite3', 'database' => ':memory:') ActiveRecord::Base.logger = Logger.new("#{File.dirname(__FILE__)}/../log/active_record.log") 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.4.0/test/machine_with_states_test.rb0000644000004100000410000000046612717476705025615 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithStatesTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model) @machine.state :first_gear end def test_should_humanize_name assert_equal 'first gear', @machine.state(:first_gear).human_name end end state_machines-activerecord-0.4.0/test/integration_test.rb0000644000004100000410000000177612717476705024123 0ustar www-datawww-datarequire_relative 'test_helper' class IntegrationTest < BaseTestCase def test_should_have_an_integration_name assert_equal :active_record, StateMachines::Integrations::ActiveRecord.integration_name end def test_should_be_before_activemodel integrations = StateMachines::Integrations.list.to_a assert StateMachines::Integrations::ActiveRecord, integrations.first assert StateMachines::Integrations::ActiveModel, integrations.last end def test_should_match_if_class_inherits_from_active_record assert StateMachines::Integrations::ActiveRecord.matches?(new_model) end def test_should_not_match_if_class_does_not_inherit_from_active_record refute StateMachines::Integrations::ActiveRecord.matches?(Class.new) end def test_should_have_defaults assert_equal({action: :save, use_transactions: true}, StateMachines::Integrations::ActiveRecord.defaults) end def test_should_have_a_locale_path assert_not_nil StateMachines::Integrations::ActiveRecord.locale_path end end state_machines-activerecord-0.4.0/test/machine_with_initialized_state_test.rb0000644000004100000410000000164412717476705030016 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithInitializedStateTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model, :initial => :parked) @machine.state :idling end def test_should_allow_nil_initial_state_when_static @machine.state nil record = @model.new(:state => nil) assert_nil record.state end def test_should_allow_nil_initial_state_when_dynamic @machine.state nil @machine.initial_state = lambda { :parked } record = @model.new(:state => nil) assert_nil record.state end def test_should_allow_different_initial_state_when_static record = @model.new(:state => 'idling') assert_equal 'idling', record.state end def test_should_allow_different_initial_state_when_dynamic @machine.initial_state = lambda { :parked } record = @model.new(:state => 'idling') assert_equal 'idling', record.state end end state_machines-activerecord-0.4.0/test/machine_with_scopes_test.rb0000644000004100000410000000410412717476705025577 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithScopesTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model) @machine.state :parked, :first_gear @machine.state :idling, :value => -> { 'idling' } end def test_should_create_singular_with_scope assert @model.respond_to?(:with_state) end def test_should_only_include_records_with_state_in_singular_with_scope parked = @model.create :state => 'parked' @model.create :state => 'idling' assert_equal [parked], @model.with_state(:parked).all end def test_should_create_plural_with_scope assert @model.respond_to?(:with_states) end def test_should_only_include_records_with_states_in_plural_with_scope parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [parked, idling], @model.with_states(:parked, :idling).all end def test_should_allow_lookup_by_string_name parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [parked, idling], @model.with_states('parked', 'idling').all end def test_should_create_singular_without_scope assert @model.respond_to?(:without_state) end def test_should_only_include_records_without_state_in_singular_without_scope parked = @model.create :state => 'parked' 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.4.0/test/machine_by_default_test.rb0000644000004100000410000000053012717476705025365 0ustar www-datawww-datarequire_relative 'test_helper' class MachineByDefaultTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model) end def test_should_use_save_as_action assert_equal :save, @machine.action end def test_should_use_transactions assert_equal true, @machine.use_transactions end end state_machines-activerecord-0.4.0/test/machine_without_transactions_test.rb0000644000004100000410000000106512717476705027546 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithoutTransactionsTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model, :use_transactions => false) end def test_should_not_rollback_transaction_if_false @machine.within_transaction(@model.new) do @model.create false end assert_equal 1, @model.count end def test_should_not_rollback_transaction_if_true @machine.within_transaction(@model.new) do @model.create true end assert_equal 1, @model.count end end state_machines-activerecord-0.4.0/test/machine_with_internationalization_test.rb0000644000004100000410000001463512717476705030562 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithInternationalizationTest < BaseTestCase def setup I18n.backend = I18n::Backend::Simple.new # Initialize the backend StateMachines::Machine.new(new_model) @model = new_model end def test_should_use_defaults I18n.backend.store_translations(:en, { :activerecord => {:errors => {:messages => {:invalid_transition => "cannot #{interpolation_key('event')}"}}} }) machine = StateMachines::Machine.new(@model) machine.state :parked, :idling machine.event :ignite record = @model.new(:state => 'idling') machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot transition via "ignite"'], record.errors.full_messages end def test_should_allow_customized_error_key I18n.backend.store_translations(:en, { :activerecord => {:errors => {:messages => {:bad_transition => "cannot #{interpolation_key('event')}"}}} }) machine = StateMachines::Machine.new(@model, :messages => {:invalid_transition => :bad_transition}) machine.state :parked, :idling record = @model.new(:state => 'idling') machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot ignite'], record.errors.full_messages end def test_should_allow_customized_error_string machine = StateMachines::Machine.new(@model, :messages => {:invalid_transition => "cannot #{interpolation_key('event')}"}) machine.state :parked, :idling record = @model.new(:state => 'idling') machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot ignite'], record.errors.full_messages end def test_should_allow_customized_state_key_scoped_to_class_and_machine I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:'foo' => {:state => {:states => {:parked => 'shutdown'}}}}} }) machine = StateMachines::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_allow_customized_state_key_scoped_to_class I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:'foo' => {:states => {:parked => 'shutdown'}}}} }) machine = StateMachines::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_allow_customized_state_key_scoped_to_machine I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:state => {:states => {:parked => 'shutdown'}}}} }) machine = StateMachines::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_allow_customized_state_key_unscoped I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:states => {:parked => 'shutdown'}}} }) machine = StateMachines::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_support_nil_state_key I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:states => {:nil => 'empty'}}} }) machine = StateMachines::Machine.new(@model) assert_equal 'empty', machine.state(nil).human_name end def test_should_allow_customized_event_key_scoped_to_class_and_machine I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:'foo' => {:state => {:events => {:park => 'stop'}}}}} }) machine = StateMachines::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_allow_customized_event_key_scoped_to_class I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:'foo' => {:events => {:park => 'stop'}}}} }) machine = StateMachines::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_allow_customized_event_key_scoped_to_machine I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:state => {:events => {:park => 'stop'}}}} }) machine = StateMachines::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_allow_customized_event_key_unscoped I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:events => {:park => 'stop'}}} }) machine = StateMachines::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_only_add_locale_once_in_load_path assert_equal 1, I18n.load_path.select { |path| path =~ %r{active_record/locale\.rb$} }.length # Create another ActiveRecord model that will trigger the i18n feature new_model assert_equal 1, I18n.load_path.select { |path| path =~ %r{active_record/locale\.rb$} }.length end def test_should_prefer_other_locales_first @original_load_path = I18n.load_path I18n.backend = I18n::Backend::Simple.new I18n.load_path = [File.dirname(__FILE__) + '/files/en.yml'] machine = StateMachines::Machine.new(@model) machine.state :parked, :idling machine.event :ignite record = @model.new(:state => 'idling') machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot transition'], record.errors.full_messages ensure I18n.load_path = @original_load_path end private def interpolation_key(key) "%{#{key}}" end end state_machines-activerecord-0.4.0/test/machine_with_failed_before_callbacks_test.rb0000644000004100000410000000203412717476705031050 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithFailedBeforeCallbacksTest < BaseTestCase def setup @callbacks = [] @model = new_model @machine = StateMachines::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @machine.before_transition { @callbacks << :before_1; false } @machine.before_transition { @callbacks << :before_2 } @machine.after_transition { @callbacks << :after } @machine.around_transition { |block| @callbacks << :around_before; block.call; @callbacks << :around_after } @record = @model.new(:state => 'parked') @transition = StateMachines::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform end def test_should_not_be_successful refute @result end def test_should_not_change_current_state assert_equal 'parked', @record.state end def test_should_not_run_action assert @record.new_record? end def test_should_not_run_further_callbacks assert_equal [:before_1], @callbacks end end state_machines-activerecord-0.4.0/test/machine_with_event_attributes_on_save_test.rb0000644000004100000410000001422512717476705031411 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithEventAttributesOnSaveTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model) @machine.event :ignite do transition :parked => :idling end @record = @model.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_fail_if_event_is_invalid @record.state_event = 'invalid' assert_equal false, @record.save end def test_should_fail_if_event_has_no_transition @record.state = 'idling' assert_equal false, @record.save end def test_should_run_before_callbacks ran_callback = false @machine.before_transition { ran_callback = true } @record.save assert ran_callback end def test_should_run_before_callbacks_once before_count = 0 @machine.before_transition { before_count += 1 } @record.save assert_equal 1, before_count end def test_should_run_around_callbacks_before_yield ran_callback = false @machine.around_transition { |block| ran_callback = true; block.call } @record.save assert ran_callback end def test_should_run_around_callbacks_before_yield_once around_before_count = 0 @machine.around_transition { |block| around_before_count += 1; block.call } @record.save assert_equal 1, around_before_count end def test_should_persist_new_state @record.save assert_equal 'idling', @record.state end def test_should_run_after_callbacks ran_callback = false @machine.after_transition { ran_callback = true } @record.save assert ran_callback end def test_should_not_run_after_callbacks_with_failures_disabled_if_fails @model.before_create { |record| 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.4.0/test/machine_with_state_driven_validations_test.rb0000644000004100000410000000152412717476705031372 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithStateDrivenValidationsTest < BaseTestCase def setup @model = new_model do attr_accessor :seatbelt end @machine = StateMachines::Machine.new(@model) @machine.state :first_gear, :second_gear do validates_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.4.0/test/machine_with_different_integer_column_default_test.rb0000644000004100000410000000147612717476705033060 0ustar www-datawww-datarequire_relative 'test_helper' require 'stringio' class MachineWithDifferentIntegerColumnDefaultTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @model = new_model do connection.add_column table_name, :status, :integer, :default => 0 end @machine = StateMachines::Machine.new(@model, :status, :initial => :parked) @machine.state :parked, :value => 1 @record = @model.new end def test_should_use_machine_default assert_equal 1, @record.status end def test_should_generate_a_warning assert_match(/Both Foo and its :status machine have defined a different default for "status". Use only one or the other for defining defaults to avoid unexpected behaviors\./, $stderr.string) end def teardown $stderr = @original_stderr super end end state_machines-activerecord-0.4.0/test/machine_with_dirty_attributes_and_custom_attribute_test.rb0000644000004100000410000000171512717476705034210 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithDirtyAttributesAndCustomAttributeTest < BaseTestCase def setup @model = new_model do connection.add_column table_name, :status, :string end @machine = StateMachines::Machine.new(@model, :status, :initial => :parked) @machine.event :ignite @machine.state :idling @record = @model.create @transition = StateMachines::Transition.new(@record, @machine, :ignite, :parked, :idling) @transition.perform(false) end def test_should_include_state_in_changed_attributes assert_equal %w(status), @record.changed end def test_should_track_attribute_change assert_equal %w(parked idling), @record.changes['status'] end def test_should_not_reset_changes_on_multiple_transitions transition = StateMachines::Transition.new(@record, @machine, :ignite, :idling, :idling) transition.perform(false) assert_equal %w(parked idling), @record.changes['status'] end end state_machines-activerecord-0.4.0/test/machine_with_dynamic_initial_state_test.rb0000644000004100000410000000454612717476705030652 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithDynamicInitialStateTest < BaseTestCase def setup @model = new_model do attr_accessor :value end @machine = StateMachines::Machine.new(@model, :initial => lambda { |object| :parked }) @machine.state :parked end def test_should_set_initial_state_on_created_object record = @model.new assert_equal 'parked', record.state end def test_should_still_set_attributes record = @model.new(:value => 1) assert_equal 1, record.value end def test_should_still_allow_initialize_blocks block_args = nil record = @model.new do |*args| block_args = args end assert_equal [record], block_args end def test_should_set_attributes_prior_to_initialize_block state = nil @model.new do |record| state = record.state end assert_equal 'parked', state end def test_should_set_attributes_prior_to_after_initialize_hook state = nil @model.after_initialize do |record| state = record.state end @model.new assert_equal 'parked', state end def test_should_set_initial_state_after_setting_attributes @model.class_eval do attr_accessor :state_during_setter remove_method :value= define_method(:value=) do |value| self.state_during_setter = state || 'nil' end end record = @model.new(:value => 1) assert_equal 'nil', record.state_during_setter end def test_should_not_set_initial_state_after_already_initialized record = @model.new(:value => 1) assert_equal 'parked', record.state record.state = 'idling' record.attributes = {} assert_equal 'idling', record.state end def test_should_persist_initial_state record = @model.new record.save record.reload assert_equal 'parked', record.state end def test_should_persist_initial_state_on_dup record = @model.create.dup record.save record.reload assert_equal 'parked', record.state end def test_should_use_stored_values_when_loading_from_database @machine.state :idling record = @model.find(@model.create(:state => 'idling').id) assert_equal 'idling', record.state end def test_should_use_stored_values_when_loading_from_database_with_nil_state @machine.state nil record = @model.find(@model.create(:state => nil).id) assert_nil record.state end end state_machines-activerecord-0.4.0/test/machine_with_callbacks_test.rb0000644000004100000410000001114512717476705026225 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithCallbacksTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model, :initial => :parked) @machine.other_states :idling @machine.event :ignite @record = @model.new(:state => 'parked') @transition = StateMachines::Transition.new(@record, @machine, :ignite, :parked, :idling) end def test_should_run_before_callbacks called = false @machine.before_transition { called = true } @transition.perform assert called end def test_should_pass_record_to_before_callbacks_with_one_argument record = nil @machine.before_transition { |arg| record = arg } @transition.perform assert_equal @record, record end def test_should_pass_record_and_transition_to_before_callbacks_with_multiple_arguments callback_args = nil @machine.before_transition { |*args| callback_args = args } @transition.perform assert_equal [@record, @transition], callback_args end def test_should_run_before_callbacks_outside_the_context_of_the_record context = nil @machine.before_transition { context = self } @transition.perform assert_equal self, context end def test_should_run_after_callbacks called = false @machine.after_transition { called = true } @transition.perform assert called end def test_should_pass_record_to_after_callbacks_with_one_argument record = nil @machine.after_transition { |arg| record = arg } @transition.perform assert_equal @record, record end def test_should_pass_record_and_transition_to_after_callbacks_with_multiple_arguments callback_args = nil @machine.after_transition { |*args| callback_args = args } @transition.perform assert_equal [@record, @transition], callback_args end def test_should_run_after_callbacks_outside_the_context_of_the_record context = nil @machine.after_transition { context = self } @transition.perform assert_equal self, context end def test_should_run_after_callbacks_if_model_callback_added_prior_to_state_machine_definition model = new_model do after_save { nil } end machine = StateMachines::Machine.new(model, :initial => :parked) machine.other_states :idling machine.event :ignite after_called = false machine.after_transition { after_called = true } record = model.new(:state => 'parked') transition = StateMachines::Transition.new(record, machine, :ignite, :parked, :idling) transition.perform assert_equal true, after_called end def test_should_run_around_callbacks before_called = false after_called = false ensure_called = 0 @machine.around_transition do |block| before_called = true begin block.call ensure ensure_called += 1 end after_called = true end @transition.perform assert before_called assert after_called assert_equal ensure_called, 1 end def test_should_include_transition_states_in_known_states @machine.before_transition :to => :first_gear, :do => lambda {} assert_equal [:parked, :idling, :first_gear], @machine.states.map { |state| state.name } end def test_should_allow_symbolic_callbacks callback_args = nil klass = class << @record; self; end klass.send(:define_method, :after_ignite) do |*args| callback_args = args end @machine.before_transition(:after_ignite) @transition.perform assert_equal [@transition], callback_args end def test_should_allow_string_callbacks class << @record attr_reader :callback_result end @machine.before_transition('@callback_result = [1, 2, 3]') @transition.perform assert_equal [1, 2, 3], @record.callback_result end def test_should_run_in_expected_order expected = [ :before_transition, :before_validation, :after_validation, :before_save, :before_create, :after_create, :after_save, :after_transition ] callbacks = [] @model.before_validation { callbacks << :before_validation } @model.after_validation { callbacks << :after_validation } @model.before_save { callbacks << :before_save } @model.before_create { callbacks << :before_create } @model.after_create { callbacks << :after_create } @model.after_save { callbacks << :after_save } @model.after_commit { callbacks << :after_commit } expected << :after_commit @machine.before_transition { callbacks << :before_transition } @machine.after_transition { callbacks << :after_transition } @transition.perform assert_equal expected, callbacks end end state_machines-activerecord-0.4.0/test/machine_with_column_state_attribute_test.rb0000644000004100000410000000216312717476705031066 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithColumnStateAttributeTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model, :initial => :parked) @machine.other_states(:idling) @record = @model.new end def test_should_not_override_the_column_reader @record[:state] = 'parked' assert_equal 'parked', @record.state end def test_should_not_override_the_column_writer @record.state = 'parked' assert_equal 'parked', @record[:state] end def test_should_have_an_attribute_predicate assert @record.respond_to?(:state?) end def test_should_test_for_existence_on_predicate_without_parameters assert @record.state? @record.state = nil refute @record.state? end def test_should_return_false_for_predicate_if_does_not_match_current_value refute @record.state?(:idling) end def test_should_return_true_for_predicate_if_matches_current_value assert @record.state?(:parked) end def test_should_raise_exception_for_predicate_if_invalid_state_specified assert_raises(IndexError) { @record.state?(:invalid) } end end state_machines-activerecord-0.4.0/test/machine_with_validations_test.rb0000644000004100000410000000227112717476705026623 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithValidationsTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model) @machine.state :parked @record = @model.new end def test_should_invalidate_using_errors I18n.backend = I18n::Backend::Simple.new @record.state = 'parked' @machine.invalidate(@record, :state, :invalid_transition, [[:event, 'park']]) assert_equal ['State cannot transition via "park"'], @record.errors.full_messages end def test_should_auto_prefix_custom_attributes_on_invalidation @machine.invalidate(@record, :event, :invalid) assert_equal ['State event is invalid'], @record.errors.full_messages end def test_should_clear_errors_on_reset @record.state = 'parked' @record.errors.add(:state, 'is invalid') @machine.reset(@record) assert_equal [], @record.errors.full_messages end def test_should_be_valid_if_state_is_known @record.state = 'parked' assert @record.valid? end def test_should_not_be_valid_if_state_is_unknown @record.state = 'invalid' refute @record.valid? assert_equal ['State is invalid'], @record.errors.full_messages end end state_machines-activerecord-0.4.0/test/machine_with_event_attributes_on_save_bang_test.rb0000644000004100000410000000372212717476705032400 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithEventAttributesOnSaveBangTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model) @machine.event :ignite do transition :parked => :idling end @record = @model.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_fail_if_event_is_invalid @record.state_event = 'invalid' assert_raise(ActiveRecord::RecordInvalid) { @record.save! } end def test_should_fail_if_event_has_no_transition @record.state = 'idling' assert_raise(ActiveRecord::RecordInvalid) { @record.save! } end def test_should_be_successful_if_event_has_transition assert_equal true, @record.save! end def test_should_run_before_callbacks ran_callback = false @machine.before_transition { ran_callback = true } @record.save! assert ran_callback end def test_should_run_before_callbacks_once before_count = 0 @machine.before_transition { before_count += 1 } @record.save! assert_equal 1, before_count end def test_should_run_around_callbacks_before_yield ran_callback = false @machine.around_transition { |block| ran_callback = true; block.call } @record.save! assert ran_callback end def test_should_run_around_callbacks_before_yield_once around_before_count = 0 @machine.around_transition { |block| around_before_count += 1; block.call } @record.save! assert_equal 1, around_before_count end def test_should_persist_new_state @record.save! assert_equal 'idling', @record.state end def test_should_run_after_callbacks ran_callback = false @machine.after_transition { ran_callback = true } @record.save! assert ran_callback end def test_should_run_around_callbacks_after_yield ran_callback = false @machine.around_transition { |block| block.call; ran_callback = true } @record.save! assert ran_callback end end state_machines-activerecord-0.4.0/test/machine_with_dirty_attributes_during_loopback_test.rb0000644000004100000410000000112212717476705033123 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithDirtyAttributesDuringLoopbackTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model, :initial => :parked) @machine.event :park @record = @model.create @transition = StateMachines::Transition.new(@record, @machine, :park, :parked, :parked) @transition.perform(false) end def test_should_not_include_state_in_changed_attributes assert_equal [], @record.changed end def test_should_not_track_attribute_changes assert_equal nil, @record.changes['state'] end end state_machines-activerecord-0.4.0/test/model_test.rb0000644000004100000410000000056112717476705022667 0ustar www-datawww-datarequire_relative 'test_helper' require_relative 'files/models/post' class ModelTest < ActiveSupport::TestCase def test_should_have_draft_state_in_defaut_machine assert_equal 'draft', Post.new.state end def test_should_have_the_correct_integration assert_equal StateMachines::Integrations::ActiveRecord, StateMachines::Integrations.match(Post) end end state_machines-activerecord-0.4.0/test/machine_with_conflicting_predicate_test.rb0000644000004100000410000000053612717476705030627 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithConflictingPredicateTest < BaseTestCase def setup @model = new_model do def state?(*args) true end end @machine = StateMachines::Machine.new(@model) @record = @model.new end def test_should_not_define_attribute_predicate assert @record.state? end end state_machines-activerecord-0.4.0/test/machine_with_event_attributes_on_custom_action_test.rb0000644000004100000410000000163212717476705033320 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithEventAttributesOnCustomActionTest < BaseTestCase def setup @superclass = new_model do def persist create_or_update end end @model = Class.new(@superclass) @machine = StateMachines::Machine.new(@model, :action => :persist) @machine.event :ignite do transition :parked => :idling end @record = @model.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_not_transition_on_valid? @record.valid? assert_equal 'parked', @record.state end def test_should_not_transition_on_save @record.save assert_equal 'parked', @record.state end def test_should_not_transition_on_save! @record.save! assert_equal 'parked', @record.state end def test_should_transition_on_custom_action @record.persist assert_equal 'idling', @record.state end end state_machines-activerecord-0.4.0/test/machine_with_custom_attribute_test.rb0000644000004100000410000000100712717476705027677 0ustar www-datawww-datarequire_relative 'test_helper' require 'stringio' class MachineWithCustomAttributeTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @model = new_model @machine = StateMachines::Machine.new(@model, :public_state, :attribute => :state) @record = @model.new end def test_should_not_delegate_attribute_predicate_with_different_attribute assert_raise(ArgumentError) { @record.public_state? } end def teardown $stderr = @original_stderr super end end state_machines-activerecord-0.4.0/test/machine_with_failed_after_callbacks_test.rb0000644000004100000410000000177112717476705030716 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithFailedAfterCallbacksTest < BaseTestCase def setup @callbacks = [] @model = new_model @machine = StateMachines::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @machine.after_transition { @callbacks << :after_1; false } @machine.after_transition { @callbacks << :after_2 } @machine.around_transition { |block| @callbacks << :around_before; block.call; @callbacks << :around_after } @record = @model.new(:state => 'parked') @transition = StateMachines::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform end def test_should_be_successful assert @result end def test_should_change_current_state assert_equal 'idling', @record.state end def test_should_save_record refute @record.new_record? end def test_should_not_run_further_after_callbacks assert_equal [:around_before, :around_after, :after_1], @callbacks end end state_machines-activerecord-0.4.0/test/machine_with_scopes_and_joins_test.rb0000644000004100000410000000237512717476705027633 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithScopesAndJoinsTest < BaseTestCase def setup @company = new_model(:company) MachineWithScopesAndJoinsTest.const_set('Company', @company) @vehicle = new_model(:vehicle) do connection.add_column table_name, :company_id, :integer belongs_to :company, :class_name => 'MachineWithScopesAndJoinsTest::Company' end MachineWithScopesAndJoinsTest.const_set('Vehicle', @vehicle) @company_machine = StateMachines::Machine.new(@company, :initial => :active) @vehicle_machine = StateMachines::Machine.new(@vehicle, :initial => :parked) @vehicle_machine.state :idling @ford = @company.create @mustang = @vehicle.create(:company => @ford) end def test_should_find_records_in_with_scope assert_equal [@mustang], @vehicle.with_states(:parked).joins(:company).where("#{@company.table_name}.state = \"active\"") end def test_should_find_records_in_without_scope assert_equal [@mustang], @vehicle.without_states(:idling).joins(:company).where("#{@company.table_name}.state = \"active\"") end def teardown MachineWithScopesAndJoinsTest.class_eval do remove_const('Vehicle') remove_const('Company') end ActiveSupport::Dependencies.clear end end state_machines-activerecord-0.4.0/test/machine_with_scopes_and_owner_subclass_test.rb0000644000004100000410000000163512717476705031540 0ustar www-datawww-datarequire_relative 'test_helper' class MachineWithScopesAndOwnerSubclassTest < BaseTestCase def setup @model = new_model @machine = StateMachines::Machine.new(@model, :state) @subclass = Class.new(@model) @subclass_machine = @subclass.state_machine(:state) {} @subclass_machine.state :parked, :idling, :first_gear end def test_should_only_include_records_with_subclass_states_in_with_scope parked = @subclass.create :state => 'parked' idling = @subclass.create :state => 'idling' assert_equal [parked, idling], @subclass.with_states(:parked, :idling).all end def test_should_only_include_records_without_subclass_states_in_without_scope parked = @subclass.create :state => 'parked' idling = @subclass.create :state => 'idling' first_gear = @subclass.create :state => 'first_gear' assert_equal [parked, idling], @subclass.without_states(:first_gear).all end end state_machines-activerecord-0.4.0/.gitignore0000644000004100000410000000027112717476705021212 0ustar www-datawww-data*.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/ *.lockstate_machines-activerecord-0.4.0/state_machines-activerecord.gemspec0000644000004100000410000000240512717476705026227 0ustar www-datawww-data# 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/state-machines/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', '< 5.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.4.0/Appraisals0000644000004100000410000000164712717476705021254 0ustar www-datawww-dataappraise "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 appraise "active_record_5.0" do gem "sqlite3", platforms: [:mri, :rbx] gem "activerecord-jdbcsqlite3-adapter", platform: :jruby gem "activerecord", github: 'rails/rails', branch: '5-0-stable' gem "activemodel", github: 'rails/rails', branch: '5-0-stable' end appraise "active_record_edge" do gem "sqlite3", platforms: [:mri, :rbx] gem "activerecord-jdbcsqlite3-adapter", platform: :jruby gem "activerecord", github: 'rails/rails', branch: 'master' gem "activemodel", github: 'rails/rails', branch: 'master' end state_machines-activerecord-0.4.0/README.md0000644000004100000410000000354112717476705020504 0ustar www-datawww-data[![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.svg)](https://codeclimate.com/github/state-machines/state_machines-activerecord) # StateMachines Active Record Integration The Active Record 4.1+ integration adds support for database transactions, automatically saving the record, named scopes, validation errors. ## Dependencies Active Record 4.1+ ## 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 ``` ## 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