state_machines-activerecord-0.6.0/ 0000755 0000041 0000041 00000000000 13455361326 017214 5 ustar www-data www-data state_machines-activerecord-0.6.0/.travis.yml 0000644 0000041 0000041 00000003565 13455361326 021336 0 ustar www-data www-data language: ruby
sudo: false
cache: bundler
rvm:
- 2.1
- 2.2.9
- 2.3.8
- 2.4.6
- 2.5.5
- 2.6.2
- 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.9
- gemfile: gemfiles/active_record_5.0.gemfile
rvm: 2.3.8
- gemfile: gemfiles/active_record_5.0.gemfile
rvm: 2.4.6
- gemfile: gemfiles/active_record_5.0.gemfile
rvm: 2.5.5
- gemfile: gemfiles/active_record_5.0.gemfile
rvm: 2.6.2
- gemfile: gemfiles/active_record_5.1.gemfile
rvm: 2.2.9
- gemfile: gemfiles/active_record_5.1.gemfile
rvm: 2.3.8
- gemfile: gemfiles/active_record_5.1.gemfile
rvm: 2.4.6
- gemfile: gemfiles/active_record_5.1.gemfile
rvm: 2.5.5
- gemfile: gemfiles/active_record_5.1.gemfile
rvm: 2.6.2
- gemfile: gemfiles/active_record_5.2.gemfile
rvm: 2.2.9
- gemfile: gemfiles/active_record_5.2.gemfile
rvm: 2.3.8
- gemfile: gemfiles/active_record_5.2.gemfile
rvm: 2.4.6
- gemfile: gemfiles/active_record_5.2.gemfile
rvm: 2.5.5
- gemfile: gemfiles/active_record_5.2.gemfile
rvm: 2.6.2
- gemfile: gemfiles/active_record_6.0.gemfile
rvm: 2.5.5
- gemfile: gemfiles/active_record_6.0.gemfile
rvm: 2.6.2
- gemfile: gemfiles/active_record_edge.gemfile
rvm: 2.5.5
- gemfile: gemfiles/active_record_edge.gemfile
rvm: 2.6.2
exclude:
- gemfile: gemfiles/active_record_4.1.gemfile
rvm: 2.4.6
- gemfile: gemfiles/active_record_4.1.gemfile
rvm: 2.5.5
- gemfile: gemfiles/active_record_4.1.gemfile
rvm: 2.6.2
allow_failures:
- rvm: jruby
- rvm: rbx-2
- gemfile: gemfiles/active_record_edge.gemfile
rvm: 2.5.5
- gemfile: gemfiles/active_record_edge.gemfile
rvm: 2.6.2
state_machines-activerecord-0.6.0/test/ 0000755 0000041 0000041 00000000000 13455361326 020173 5 ustar www-data www-data state_machines-activerecord-0.6.0/test/machine_with_default_scope_test.rb 0000644 0000041 0000041 00000000664 13455361326 027121 0 ustar www-data www-data require_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.6.0/test/machine_with_validations_and_custom_attribute_test.rb 0000644 0000041 0000041 00000001015 13455361326 033107 0 ustar www-data www-data require_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.6.0/test/machine_with_scopes_and_joins_test.rb 0000644 0000041 0000041 00000002375 13455361326 027625 0 ustar www-data www-data require_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.6.0/test/machine_with_state_driven_validations_test.rb 0000644 0000041 0000041 00000001524 13455361326 031364 0 ustar www-data www-data require_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.6.0/test/machine_with_dirty_attribute_and_state_events_test.rb 0000644 0000041 0000041 00000000771 13455361326 033127 0 ustar www-data www-data require_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.6.0/test/machine_with_failed_action_test.rb 0000644 0000041 0000041 00000002217 13455361326 027061 0 ustar www-data www-data require_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.6.0/test/machine_with_conflicting_predicate_test.rb 0000644 0000041 0000041 00000000536 13455361326 030621 0 ustar www-data www-data require_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.6.0/test/machine_with_different_integer_column_default_test.rb 0000644 0000041 0000041 00000001476 13455361326 033052 0 ustar www-data www-data require_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.6.0/test/machine_with_same_column_default_test.rb 0000644 0000041 0000041 00000001213 13455361326 030301 0 ustar www-data www-data require_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.6.0/test/model_test.rb 0000644 0000041 0000041 00000000561 13455361326 022661 0 ustar www-data www-data require_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.6.0/test/machine_multiple_test.rb 0000644 0000041 0000041 00000000775 13455361326 025107 0 ustar www-data www-data require_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.6.0/test/machine_with_aliased_attribute_test.rb 0000644 0000041 0000041 00000001032 13455361326 027757 0 ustar www-data www-data require_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.6.0/test/machine_with_custom_attribute_test.rb 0000644 0000041 0000041 00000001007 13455361326 027671 0 ustar www-data www-data require_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.6.0/test/machine_with_event_attributes_on_validation_test.rb 0000644 0000041 0000041 00000006407 13455361326 032602 0 ustar www-data www-data require_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.6.0/test/machine_nested_action_test.rb 0000644 0000041 0000041 00000001604 13455361326 026063 0 ustar www-data www-data require_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.6.0/test/machine_with_loopback_test.rb 0000644 0000041 0000041 00000001123 13455361326 026065 0 ustar www-data www-data require_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.6.0/test/machine_with_dynamic_initial_state_test.rb 0000644 0000041 0000041 00000004546 13455361326 030644 0 ustar www-data www-data require_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.6.0/test/machine_with_dirty_attributes_test.rb 0000644 0000041 0000041 00000001757 13455361326 027711 0 ustar www-data www-data require_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.6.0/test/machine_with_conflicting_state_name_test.rb 0000644 0000041 0000041 00000001505 13455361326 030776 0 ustar www-data www-data require_relative 'test_helper'
require 'stringio'
class MachineWithConflictingStateNameTest < BaseTestCase
def setup
@original_stderr, $stderr = $stderr, StringIO.new
@model = new_model
end
def test_should_output_warning_with_same_machine_name
@machine = StateMachines::Machine.new(@model)
@machine.state :state
assert_match(/^Instance method "state\?" is already defined in Foo, use generic helper instead.*\n$/, $stderr.string)
end
def test_should_output_warning_with_same_machine_attribute
@machine = StateMachines::Machine.new(@model, :public_state, :attribute => :state)
@machine.state :state
assert_match(/^Instance method "state\?" is already defined in Foo, use generic helper instead.*\n$/, $stderr.string)
end
def teardown
$stderr = @original_stderr
super
end
end
state_machines-activerecord-0.6.0/test/machine_with_initialized_state_test.rb 0000644 0000041 0000041 00000002120 13455361326 027776 0 ustar www-data www-data require_relative 'test_helper'
class MachineWithInitializedStateTest < BaseTestCase
def setup
@model = new_model
@machine = StateMachines::Machine.new(@model, :initial => :parked)
@machine.state :idling
end
def test_should_allow_nil_initial_state_when_static
@machine.state nil
record = @model.new(:state => nil)
assert_nil record.state
end
def test_should_allow_nil_initial_state_when_dynamic
@machine.state nil
@machine.initial_state = lambda { :parked }
record = @model.new(:state => nil)
assert_nil record.state
end
def test_should_allow_different_initial_state_when_static
record = @model.new(:state => 'idling')
assert_equal 'idling', record.state
end
def test_should_allow_different_initial_state_when_using_create_with
record = @model.create_with(state: 'idling').new
assert_equal 'idling', record.state
end
def test_should_allow_different_initial_state_when_dynamic
@machine.initial_state = lambda { :parked }
record = @model.new(:state => 'idling')
assert_equal 'idling', record.state
end
end
state_machines-activerecord-0.6.0/test/machine_with_static_initial_state_test.rb 0000644 0000041 0000041 00000011226 13455361326 030500 0 ustar www-data www-data require_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.6.0/test/machine_with_event_attributes_on_save_bang_test.rb 0000644 0000041 0000041 00000003722 13455361326 032372 0 ustar www-data www-data require_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.6.0/test/machine_with_scopes_and_owner_subclass_test.rb 0000644 0000041 0000041 00000001635 13455361326 031532 0 ustar www-data www-data require_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.6.0/test/machine_with_column_state_attribute_test.rb 0000644 0000041 0000041 00000002163 13455361326 031060 0 ustar www-data www-data require_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.6.0/test/machine_with_different_column_default_test.rb 0000644 0000041 0000041 00000001433 13455361326 031326 0 ustar www-data www-data require_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.6.0/test/machine_with_scopes_test.rb 0000644 0000041 0000041 00000004104 13455361326 025571 0 ustar www-data www-data require_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.6.0/test/machine_with_same_integer_column_default_test.rb 0000644 0000041 0000041 00000001305 13455361326 032020 0 ustar www-data www-data require_relative 'test_helper'
require 'stringio'
class MachineWithSameIntegerColumnDefaultTest < BaseTestCase
def setup
@original_stderr, $stderr = $stderr, StringIO.new
@model = new_model do
connection.add_column table_name, :status, :integer, :default => 1
end
@machine = StateMachines::Machine.new(@model, :status, :initial => :parked) do
state :parked, :value => 1
end
@record = @model.new
end
def test_should_use_machine_default
assert_equal 1, @record.status
end
def test_should_not_generate_a_warning
assert_no_match(/have defined a different default/, $stderr.string)
end
def teardown
$stderr = @original_stderr
super
end
end
state_machines-activerecord-0.6.0/test/machine_with_failed_after_callbacks_test.rb 0000644 0000041 0000041 00000001771 13455361326 030710 0 ustar www-data www-data require_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
././@LongLink 0000644 0000000 0000000 00000000162 00000000000 011602 L ustar root root state_machines-activerecord-0.6.0/test/machine_with_dirty_attribute_and_custom_attributes_during_loopback_test.rb state_machines-activerecord-0.6.0/test/machine_with_dirty_attribute_and_custom_attributes_during_loo0000644 0000041 0000041 00000001262 13455361326 034757 0 ustar www-data www-data require_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.6.0/test/machine_with_validations_test.rb 0000644 0000041 0000041 00000002271 13455361326 026615 0 ustar www-data www-data require_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.6.0/test/machine_with_transactions_test.rb 0000644 0000041 0000041 00000001055 13455361326 027007 0 ustar www-data www-data require_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.6.0/test/machine_with_internationalization_test.rb 0000644 0000041 0000041 00000014635 13455361326 030554 0 ustar www-data www-data require_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.6.0/test/machine_with_states_test.rb 0000644 0000041 0000041 00000000466 13455361326 025607 0 ustar www-data www-data require_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.6.0/test/machine_with_event_attributes_on_save_test.rb 0000644 0000041 0000041 00000014277 13455361326 031412 0 ustar www-data www-data require_relative 'test_helper'
class MachineWithEventAttributesOnSaveTest < BaseTestCase
def setup
@model = new_model
@machine = StateMachines::Machine.new(@model)
@machine.event :ignite do
transition :parked => :idling
end
@record = @model.new
@record.state = 'parked'
@record.state_event = 'ignite'
end
def test_should_fail_if_event_is_invalid
@record.state_event = 'invalid'
assert_equal false, @record.save
end
def test_should_fail_if_event_has_no_transition
@record.state = 'idling'
assert_equal false, @record.save
end
def test_should_run_before_callbacks
ran_callback = false
@machine.before_transition { ran_callback = true }
@record.save
assert ran_callback
end
def test_should_run_before_callbacks_once
before_count = 0
@machine.before_transition { before_count += 1 }
@record.save
assert_equal 1, before_count
end
def test_should_run_around_callbacks_before_yield
ran_callback = false
@machine.around_transition { |block| ran_callback = true; block.call }
@record.save
assert ran_callback
end
def test_should_run_around_callbacks_before_yield_once
around_before_count = 0
@machine.around_transition { |block| around_before_count += 1; block.call }
@record.save
assert_equal 1, around_before_count
end
def test_should_persist_new_state
@record.save
assert_equal 'idling', @record.state
end
def test_should_run_after_callbacks
ran_callback = false
@machine.after_transition { ran_callback = true }
@record.save
assert ran_callback
end
def test_should_not_run_after_callbacks_with_failures_disabled_if_fails
@model.before_create { |record| abort_from_callback }
ran_callback = false
@machine.after_transition { ran_callback = true }
begin
; @record.save;
rescue;
end
refute ran_callback
end
def test_should_run_failure_callbacks__if_fails
@model.before_create { |record| abort_from_callback }
ran_callback = false
@machine.after_failure { ran_callback = true }
begin
; @record.save;
rescue;
end
assert ran_callback
end
def test_should_not_run_around_callbacks_if_fails
@model.before_create { |record| abort_from_callback }
ran_callback = false
@machine.around_transition { |block| block.call; ran_callback = true }
begin
; @record.save;
rescue;
end
refute ran_callback
end
def test_should_run_around_callbacks_after_yield
ran_callback = false
@machine.around_transition { |block| block.call; ran_callback = true }
@record.save
assert ran_callback
end
def test_should_run_before_transitions_within_transaction
@machine.before_transition { @model.create; raise ActiveRecord::Rollback }
begin
@record.save
rescue Exception
end
assert_equal 0, @model.count
end
def test_should_run_after_transitions_within_transaction
@machine.after_transition { @model.create; raise ActiveRecord::Rollback }
begin
@record.save
rescue Exception
end
assert_equal 0, @model.count
end
def test_should_run_around_transition_within_transaction
@machine.around_transition { @model.create; raise ActiveRecord::Rollback }
begin
@record.save
rescue Exception
end
assert_equal 0, @model.count
end
def test_should_allow_additional_transitions_to_new_state_in_after_transitions
@machine.event :park do
transition :idling => :parked
end
@machine.after_transition(:on => :ignite) { @record.park }
@record.save
assert_equal 'parked', @record.state
@record.reload
assert_equal 'parked', @record.state
end
def test_should_allow_additional_transitions_to_previous_state_in_after_transitions
@machine.event :shift_up do
transition :idling => :first_gear
end
@machine.after_transition(:on => :ignite) { @record.shift_up }
@record.save
assert_equal 'first_gear', @record.state
@record.reload
assert_equal 'first_gear', @record.state
end
def test_should_yield_one_model!
assert_equal true, @record.save!
assert_equal 1, @model.count
end
# explicit tests of #save and #save! to ensure expected behavior
def test_should_yield_two_models_with_before
@machine.before_transition { @model.create! }
assert_equal true, @record.save
assert_equal 2, @model.count
end
def test_should_yield_two_models_with_before!
@machine.before_transition { @model.create! }
assert_equal true, @record.save!
assert_equal 2, @model.count
end
def test_should_raise_on_around_transition_rollback!
@machine.before_transition { @model.create! }
@machine.around_transition { @model.create!; raise ActiveRecord::Rollback }
raised = false
begin
@record.save!
rescue Exception
raised = true
end
assert_equal true, raised
assert_equal 0, @model.count
end
def test_should_return_nil_on_around_transition_rollback
@machine.before_transition { @model.create! }
@machine.around_transition { @model.create!; raise ActiveRecord::Rollback }
assert_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.6.0/test/machine_errors_test.rb 0000644 0000041 0000041 00000001100 13455361326 024547 0 ustar www-data www-data require_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.6.0/test/machine_with_complex_pluralization_scopes_test.rb 0000644 0000041 0000041 00000000607 13455361326 032301 0 ustar www-data www-data require_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.6.0/test/machine_with_failed_before_callbacks_test.rb 0000644 0000041 0000041 00000002034 13455361326 031042 0 ustar www-data www-data require_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.6.0/test/machine_unmigrated_test.rb 0000644 0000041 0000041 00000000565 13455361326 025410 0 ustar www-data www-data require_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
end state_machines-activerecord-0.6.0/test/machine_with_dirty_attributes_during_loopback_test.rb 0000644 0000041 0000041 00000001122 13455361326 033115 0 ustar www-data www-data require_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.6.0/test/machine_with_events_test.rb 0000644 0000041 0000041 00000000460 13455361326 025602 0 ustar www-data www-data require_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.6.0/test/integration_test.rb 0000644 0000041 0000041 00000001611 13455361326 024101 0 ustar www-data www-data require_relative 'test_helper'
class IntegrationTest < BaseTestCase
def test_should_have_an_integration_name
assert_equal :active_record, StateMachines::Integrations::ActiveRecord.integration_name
end
def test_should_be_before_activemodel
integrations = StateMachines::Integrations.list.to_a
assert StateMachines::Integrations::ActiveRecord, integrations.first
assert StateMachines::Integrations::ActiveModel, integrations.last
end
def test_should_match_if_class_inherits_from_active_record
assert StateMachines::Integrations::ActiveRecord.matches?(new_model)
end
def test_should_not_match_if_class_does_not_inherit_from_active_record
refute StateMachines::Integrations::ActiveRecord.matches?(Class.new)
end
def test_should_have_defaults
assert_equal({action: :save, use_transactions: true}, StateMachines::Integrations::ActiveRecord.defaults)
end
end
state_machines-activerecord-0.6.0/test/machine_with_callbacks_test.rb 0000644 0000041 0000041 00000011145 13455361326 026217 0 ustar www-data www-data require_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.6.0/test/machine_with_event_attributes_on_autosave_test.rb 0000644 0000041 0000041 00000002733 13455361326 032275 0 ustar www-data www-data require_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.6.0/test/machine_by_default_test.rb 0000644 0000041 0000041 00000000530 13455361326 025357 0 ustar www-data www-data require_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.6.0/test/machine_with_non_column_state_attribute_defined_test.rb 0000644 0000041 0000041 00000001440 13455361326 033405 0 ustar www-data www-data require_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.6.0/test/machine_without_database_test.rb 0000644 0000041 0000041 00000000707 13455361326 026576 0 ustar www-data www-data require_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.6.0/test/files/ 0000755 0000041 0000041 00000000000 13455361326 021275 5 ustar www-data www-data state_machines-activerecord-0.6.0/test/files/models/ 0000755 0000041 0000041 00000000000 13455361326 022560 5 ustar www-data www-data state_machines-activerecord-0.6.0/test/files/models/post.rb 0000644 0000041 0000041 00000000324 13455361326 024071 0 ustar www-data www-data ActiveRecord::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
end state_machines-activerecord-0.6.0/test/files/en.yml 0000644 0000041 0000041 00000000137 13455361326 022423 0 ustar www-data www-data en:
activerecord:
errors:
messages:
invalid_transition: "cannot transition" state_machines-activerecord-0.6.0/test/machine_with_event_attributes_on_custom_action_test.rb 0000644 0000041 0000041 00000001632 13455361326 033312 0 ustar www-data www-data require_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.6.0/test/test_helper.rb 0000644 0000041 0000041 00000003023 13455361326 023034 0 ustar www-data www-data begin
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
if ActiveSupport.gem_version < Gem::Version.new('5.1.x')
ActiveRecord::Base.raise_in_transactional_callbacks = true
end
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
define_method(:abort_from_callback) do
if ActiveSupport.gem_version >= Gem::Version.new('5.0')
throw :abort
else
false
end
end
(
class << self;
self;
end).class_eval do
define_method(:name) { "#{name.to_s.capitalize}" }
end
end
model.class_eval(&block) if block_given?
model.reset_column_information if create_table
model
end
end
state_machines-activerecord-0.6.0/test/machine_without_transactions_test.rb 0000644 0000041 0000041 00000001065 13455361326 027540 0 ustar www-data www-data require_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.6.0/test/machine_with_dirty_attributes_and_custom_attribute_test.rb 0000644 0000041 0000041 00000001715 13455361326 034202 0 ustar www-data www-data require_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.6.0/state_machines-activerecord.gemspec 0000644 0000041 0000041 00000002374 13455361326 026226 0 ustar www-data www-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.5.0'
spec.add_dependency 'activerecord' , '>= 4.1'
spec.add_development_dependency 'rake', '~> 10.3'
spec.add_development_dependency 'sqlite3', '~> 1.3'
spec.add_development_dependency 'appraisal', '>= 1'
spec.add_development_dependency 'minitest' , '>= 5.4.0'
spec.add_development_dependency 'minitest-reporters'
end
state_machines-activerecord-0.6.0/README.md 0000644 0000041 0000041 00000005467 13455361326 020507 0 ustar www-data www-data [](https://travis-ci.org/state-machines/state_machines-activerecord)
[](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.
## Installation
Add this line to your application's Gemfile:
gem 'state_machines-activerecord'
And then execute:
$ bundle
Or install it yourself as:
$ gem install state_machines-activerecord
## Usage
For the complete usage guide, see http://www.rubydoc.info/github/state-machines/state_machines-activerecord/StateMachines/Integrations/ActiveRecord
### Example
```ruby
class Vehicle < 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
```
### State driven validations
As mentioned in `StateMachines::Machine#state`, you can define behaviors,
like validations, that only execute for certain states. One *important*
caveat here is that, due to a constraint in ActiveRecord's validation
framework, custom validators will not work as expected when defined to run
in multiple states. For example:
```ruby
class Vehicle < 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:
```ruby
class Vehicle < ActiveRecord::Base
state_machine do
state :first_gear, :second_gear do
validate {|vehicle| vehicle.speed_is_legal}
end
end
end
```
## Contributing
1. Fork it ( https://github.com/state-machines/state_machines-activerecord/fork )
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request
state_machines-activerecord-0.6.0/gemfiles/ 0000755 0000041 0000041 00000000000 13455361326 021007 5 ustar www-data www-data state_machines-activerecord-0.6.0/gemfiles/active_record_4.1.gemfile 0000644 0000041 0000041 00000000513 13455361326 025533 0 ustar www-data www-data # This file was generated by Appraisal
source "https://rubygems.org"
gem "sqlite3", "< 1.4.0",: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.6.0/gemfiles/active_record_5.2.gemfile 0000644 0000041 0000041 00000000561 13455361326 025540 0 ustar www-data www-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-2-stable"
gem "activemodel", github: "rails/rails", branch: "5-2-stable"
platforms :mri_20, :mri_21 do
gem "pry-byebug"
end
gemspec path: "../"
state_machines-activerecord-0.6.0/gemfiles/active_record_4.2.gemfile 0000644 0000041 0000041 00000000513 13455361326 025534 0 ustar www-data www-data # This file was generated by Appraisal
source "https://rubygems.org"
gem "sqlite3", "< 1.4.0",: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.6.0/gemfiles/active_record_edge.gemfile 0000644 0000041 0000041 00000000576 13455361326 026146 0 ustar www-data www-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.6.0/gemfiles/active_record_5.1.gemfile 0000644 0000041 0000041 00000000561 13455361326 025537 0 ustar www-data www-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-1-stable"
gem "activemodel", github: "rails/rails", branch: "5-1-stable"
platforms :mri_20, :mri_21 do
gem "pry-byebug"
end
gemspec path: "../"
state_machines-activerecord-0.6.0/gemfiles/active_record_5.0.gemfile 0000644 0000041 0000041 00000000620 13455361326 025532 0 ustar www-data www-data # This file was generated by Appraisal
source "https://rubygems.org"
gem "sqlite3", "< 1.4.0",: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.6.0/gemfiles/active_record_6.0.gemfile 0000644 0000041 0000041 00000000463 13455361326 025540 0 ustar www-data www-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"
gemspec path: "../"
state_machines-activerecord-0.6.0/Appraisals 0000644 0000041 0000041 00000002701 13455361326 021236 0 ustar www-data www-data appraise "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_5.1" do
gem "sqlite3", platforms: [:mri, :rbx]
gem "activerecord-jdbcsqlite3-adapter", platform: :jruby
gem "activerecord", github: 'rails/rails', branch: '5-1-stable'
gem "activemodel", github: 'rails/rails', branch: '5-1-stable'
end
appraise "active_record_5.2" do
gem "sqlite3", platforms: [:mri, :rbx]
gem "activerecord-jdbcsqlite3-adapter", platform: :jruby
gem "activerecord", github: 'rails/rails', branch: '5-2-stable'
gem "activemodel", github: 'rails/rails', branch: '5-2-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.6.0/.gitignore 0000644 0000041 0000041 00000000271 13455361326 021204 0 ustar www-data www-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/
*.lock state_machines-activerecord-0.6.0/log/ 0000755 0000041 0000041 00000000000 13455361326 017775 5 ustar www-data www-data state_machines-activerecord-0.6.0/log/.gitkeep 0000644 0000041 0000041 00000000000 13455361326 021414 0 ustar www-data www-data state_machines-activerecord-0.6.0/Rakefile 0000644 0000041 0000041 00000000251 13455361326 020657 0 ustar www-data www-data require '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.6.0/lib/ 0000755 0000041 0000041 00000000000 13455361326 017762 5 ustar www-data www-data state_machines-activerecord-0.6.0/lib/state_machines/ 0000755 0000041 0000041 00000000000 13455361326 022751 5 ustar www-data www-data state_machines-activerecord-0.6.0/lib/state_machines/integrations/ 0000755 0000041 0000041 00000000000 13455361326 025457 5 ustar www-data www-data state_machines-activerecord-0.6.0/lib/state_machines/integrations/active_record.rb 0000644 0000041 0000041 00000053703 13455361326 030625 0 ustar www-data www-data require '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
[::ActiveRecord::Base]
end
end
protected
# Only runs validations on the action if using :save
def runs_validations_on_action?
action == :save
end
# Gets the db default for the machine's attribute
if ::ActiveRecord.gem_version >= Gem::Version.new('4.2.0')
def owner_class_attribute_default
if owner_class.connected? && owner_class.table_exists?
owner_class.column_defaults[attribute.to_s]
end
end
else
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
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|
scoped_attributes = (attributes || {}).merge(self.class.scope_attributes)
self.class.state_machines.initialize_states(self, {}, scoped_attributes)
yield(*args) if block_given?
end
end
end_eval
elsif ::ActiveRecord.gem_version >= Gem::Version.new('4.2')
define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
def initialize(attributes = nil, options = {})
scoped_attributes = (attributes || {}).merge(self.class.scope_attributes)
super(attributes, options) do |*args|
self.class.state_machines.initialize_states(self, {}, scoped_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 = {})
scoped_attributes = (attributes || {}).merge(self.class.scope_attributes)
super(attributes, options) do |*args|
self.class.state_machines.initialize_states(self, {}, scoped_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.6.0/lib/state_machines/integrations/active_record/ 0000755 0000041 0000041 00000000000 13455361326 030270 5 ustar www-data www-data state_machines-activerecord-0.6.0/lib/state_machines/integrations/active_record/version.rb 0000644 0000041 0000041 00000000155 13455361326 032303 0 ustar www-data www-data module StateMachines
module Integrations
module ActiveRecord
VERSION = '0.6.0'
end
end
end
state_machines-activerecord-0.6.0/lib/state_machines/integrations/active_record/locale.rb 0000644 0000041 0000041 00000000614 13455361326 032055 0 ustar www-data www-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.6.0/lib/state_machines-activerecord.rb 0000644 0000041 0000041 00000000327 13455361326 025750 0 ustar www-data www-data require 'active_support'
require 'state_machines/integrations/active_record'
ActiveSupport.on_load(:i18n) do
I18n.load_path << File.expand_path('state_machines/integrations/active_record/locale.rb', __dir__)
end
state_machines-activerecord-0.6.0/Gemfile 0000644 0000041 0000041 00000000134 13455361326 020505 0 ustar www-data www-data source 'https://rubygems.org'
gemspec
platforms :mri_20, :mri_21 do
gem 'pry-byebug'
end
state_machines-activerecord-0.6.0/LICENSE.txt 0000644 0000041 0000041 00000002135 13455361326 021040 0 ustar www-data www-data Copyright (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.