Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Completely rewrite ORM action hooks to behave more consistently acros…

…s the board. Closes #201, closes #214, closes #219, closes #230

* Change transitions to be executed the same whether using ORM save actions or not
* Fix around_transition callbacks not being executed properly in ORM integrations
* Fix additional transitions not being able to be fired in transition callbacks
* Add documentation on the order in which transition callbacks run within ORM integrations
  • Loading branch information...
commit 5dfc92b74e7002355ae6a55bb0ec762a0407783f 1 parent e738ba9
@obrie obrie authored
View
5 CHANGELOG.md
@@ -1,5 +1,10 @@
# master
+* Completely rewrite ORM action hooks to behave more consistently across the board
+* Change transitions to be executed the same whether using ORM save actions or not
+* Fix around_transition callbacks not being executed properly in ORM integrations
+* Fix additional transitions not being able to be fired in transition callbacks
+* Add documentation on the order in which transition callbacks run within ORM integrations
* Update README to include a topic on explicit vs. implicit event transitions [Nathan Long]
* Fix around_transition pausing not being marked as unsupported for rubinius [Daniel Huckstep]
* Fix all / any / same matchers not being able to be used in :from / :to options in transitions and callbacks
View
53 lib/state_machine/integrations/active_record.rb
@@ -136,18 +136,6 @@ module Integrations #:nodoc:
# end
# end
#
- # If using the +save+ action for the machine, this option will be ignored as
- # the transaction will be created by ActiveRecord within +save+. To avoid
- # this, use a different action like so:
- #
- # class Vehicle < ActiveRecord::Base
- # state_machine :initial => :parked, :use_transactions => false, :action => :save_state do
- # ...
- # end
- #
- # alias_method :save_state, :save
- # end
- #
# == Validations
#
# As mentioned in StateMachine::Machine#state, you can define behaviors,
@@ -290,6 +278,27 @@ module Integrations #:nodoc:
# 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
@@ -469,7 +478,15 @@ def initialize(*)
# Uses around callbacks to run state events if using the :save hook
def define_action_hook
if action_hook == :save
- owner_class.set_callback(:save, :around, self, :prepend => true)
+ define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
+ def save(*)
+ self.class.state_machine(#{name.inspect}).send(:around_save, self) { super }
+ end
+
+ def save!(*)
+ self.class.state_machine(#{name.inspect}).send(:around_save, self) { super } || raise(ActiveRecord::RecordInvalid.new(self))
+ end
+ end_eval
else
super
end
@@ -477,7 +494,9 @@ def define_action_hook
# Runs state events around the machine's :save action
def around_save(object)
- object.class.state_machines.transitions(object, action).perform { yield }
+ transaction(object) do
@avit
avit added a note

Why was this block added? Shouldn't it depend on the :use_transactions setting?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ object.class.state_machines.transitions(object, action).perform { yield }
+ end
end
# Creates a scope for finding records *with* a particular state or
@@ -502,7 +521,11 @@ def attribute_column
# an ActiveRecord::Rollback exception if the yielded block fails
# (i.e. returns false).
def transaction(object)
- object.class.transaction {raise ::ActiveRecord::Rollback unless yield}
+ result = nil
+ object.class.transaction do
+ raise ::ActiveRecord::Rollback unless result = yield
+ end
+ result
end
# Defines a new named scope with the given name
View
16 lib/state_machine/integrations/active_record/versions.rb
@@ -81,10 +81,6 @@ def i18n_scope(klass)
:activerecord
end
- def action_hook
- action == :save ? :create_or_update : super
- end
-
def load_observer_extensions
super
::ActiveRecord::Observer.class_eval do
@@ -122,18 +118,6 @@ def ancestors_for(klass)
klass.self_and_descendants_from_active_record
end
end
-
- version '3.0.x' do
- def self.active?
- ::ActiveRecord::VERSION::MAJOR == 3 && ::ActiveRecord::VERSION::MINOR == 0
- end
-
- def define_action_hook
- # +around+ callbacks don't have direct access to results until AS 3.1
- owner_class.set_callback(:save, :after, 'value', :prepend => true) if action_hook == :save
- super
- end
- end
end
end
end
View
71 lib/state_machine/integrations/data_mapper.rb
@@ -182,21 +182,6 @@ module Integrations #:nodoc:
# end
# end
#
- # If using the +save+ action for the machine, this option will be ignored as
- # the transaction behavior will depend on the +save+ implementation within
- # DataMapper. To avoid this, use a different action like so:
- #
- # class Vehicle
- # include DataMapper::Resource
- # ...
- #
- # state_machine :initial => :parked, :use_transactions => false, :action => :save_state do
- # ...
- # end
- #
- # alias_method :save_state, :save
- # end
- #
# == Validation errors
#
# If an event fails to successfully fire because there are no matching
@@ -338,6 +323,26 @@ module Integrations #:nodoc:
# The failure callback creates +TransitionLog+ records using a second
# connection to the database, allowing them to be saved without being
# affected by rollbacks in the +Vehicle+ resource'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*
+ # * (2) before :valid?
+ # * (-) valid?
+ # * (3) after :valid?
+ # * (4) before :save
+ # * (-) save
+ # * (5) before :create
+ # * (-) create
+ # * (6) after :create
+ # * (7) after :save
+ # * (8) *after_transition*
+ # * (-) end transaction (if enabled)
module DataMapper
include Base
@@ -440,20 +445,40 @@ def define_state_accessor
# Adds hooks into validation for automatically firing events
def define_action_helpers
- super
-
- if action == :save && supports_validations?
+ if action_hook == :save
define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
- def valid?(*)
- self.class.state_machines.transitions(self, :save, :after => false).perform { super }
+ def save(*)
+ result = self.class.state_machines.transitions(self, :save).perform { super }
+ assert_save_successful(:save, result)
+ result
+ end
+
+ def save!(*)
+ result = self.class.state_machines.transitions(self, :save).perform { super }
+ assert_save_successful(:save!, result)
+ result
+ end
+
+ def save_self(*)
+ self.class.state_machines.transitions(self, :save).perform { super }
end
end_eval
+
+ define_validation_hook
+ else
+ super
end
end
- # Uses internal save hooks if using the :save action
- def action_hook
- action == :save ? :save_self : super
+ # Adds hooks into validation for automatically firing events
+ def define_validation_hook
+ if supports_validations?
+ define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
+ def valid?(*)
+ self.class.state_machines.transitions(self, :save, :after => false).perform { super }
+ end
+ end_eval
+ end
end
# Creates a scope for finding records *with* a particular state or
View
54 lib/state_machine/integrations/data_mapper/versions.rb
@@ -1,23 +1,33 @@
module StateMachine
module Integrations #:nodoc:
module DataMapper
- version '0.9.x' do
+ version '0.9.x - 0.10.x' do
def self.active?
- ::DataMapper::VERSION =~ /^0\.9\./
+ ::DataMapper::VERSION =~ /^0\.\d\./ || ::DataMapper::VERSION =~ /^0\.10\./
end
- def action_hook
- action
+ def pluralize(word)
+ ::Extlib::Inflection.pluralize(word.to_s)
end
end
- version '0.9.x - 0.10.x' do
+ version '0.9.x' do
def self.active?
- ::DataMapper::VERSION =~ /^0\.\d\./ || ::DataMapper::VERSION =~ /^0\.10\./
+ ::DataMapper::VERSION =~ /^0\.9\./
end
- def pluralize(word)
- ::Extlib::Inflection.pluralize(word.to_s)
+ def define_action_helpers
+ if action_hook == :save
+ define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
+ def save(*)
+ self.class.state_machines.transitions(self, :save).perform { super }
+ end
+ end_eval
+
+ define_validation_hook
+ else
+ super
+ end
end
end
@@ -33,6 +43,34 @@ def define_action_helpers?
end
end
+ version '0.10.x' do
+ def self.active?
+ ::DataMapper::VERSION =~ /^0\.10\./
+ end
+
+ def define_action_helpers
+ if action_hook == :save
+ define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
+ def save(*)
+ self.class.state_machines.transitions(self, :save).perform { super }
+ end
+
+ def save!(*)
+ self.class.state_machines.transitions(self, :save).perform { super }
+ end
+
+ def save_self(*)
+ self.class.state_machines.transitions(self, :save).perform { super }
+ end
+ end_eval
+
+ define_validation_hook
+ else
+ super
+ end
+ end
+ end
+
version '1.0.0' do
def self.active?
::DataMapper::VERSION == '1.0.0'
View
26 lib/state_machine/integrations/mongo_mapper.rb
@@ -233,6 +233,22 @@ module Integrations #:nodoc:
# Note, also, that the transition can be accessed by simply defining
# additional arguments in the callback block.
#
+ # === Callback Order
+ #
+ # Callbacks occur in the following order. Callbacks specific to state_machine
+ # are bolded. The remaining callbacks are part of MongoMapper.
+ #
+ # * (-) save
+ # * (1) *before_transition*
+ # * (-) valid
+ # * (2) before_validation
+ # * (3) after_validation
+ # * (4) before_save
+ # * (5) before_create
+ # * (6) after_create
+ # * (7) after_save
+ # * (8) *after_transition*
+ #
# == Internationalization
#
# Any error message that is generated from performing invalid transitions
@@ -333,7 +349,15 @@ def define_state_accessor
# Uses around callbacks to run state events if using the :save hook
def define_action_hook
if action_hook == :save
- owner_class.set_callback(:save, :around, self, :prepend => true)
+ define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
+ def save(*)
+ self.class.state_machine(#{name.inspect}).send(:around_save, self) { super }
+ end
+
+ def save!(*)
+ self.class.state_machine(#{name.inspect}).send(:around_save, self) { super } || raise(::MongoMapper::DocumentNotValid.new(self))
+ end
+ end_eval
else
super
end
View
17 lib/state_machine/integrations/mongo_mapper/versions.rb
@@ -49,10 +49,6 @@ def define_state_accessor
})
end
- def action_hook
- action == :save ? :create_or_update : super
- end
-
def load_locale
end
@@ -88,19 +84,6 @@ def initialize(*args)
end_eval
end
end
-
- # Assumes MongoMapper 0.10+ uses ActiveModel 3.1+
- version '0.9.x' do
- def self.active?
- defined?(::MongoMapper::Version) && ::MongoMapper::Version =~ /^0\.9\./
- end
-
- def define_action_hook
- # +around+ callbacks don't have direct access to results until AS 3.1
- owner_class.set_callback(:save, :after, 'value', :prepend => true) if action_hook == :save
- super
- end
- end
end
end
end
View
31 lib/state_machine/integrations/mongoid.rb
@@ -283,6 +283,22 @@ module Integrations #:nodoc:
# end
# end
#
+ # === Callback Order
+ #
+ # Callbacks occur in the following order. Callbacks specific to state_machine
+ # are bolded. The remaining callbacks are part of Mongoid.
+ #
+ # * (-) save
+ # * (1) *before_transition*
+ # * (-) valid
+ # * (2) before_validation
+ # * (3) after_validation
+ # * (4) before_save
+ # * (5) before_create
+ # * (6) after_create
+ # * (7) after_save
+ # * (8) *after_transition*
+ #
# == Internationalization
#
# Any error message that is generated from performing invalid transitions
@@ -396,7 +412,20 @@ def define_state_accessor
# Uses around callbacks to run state events if using the :save hook
def define_action_hook
if action_hook == :save
- owner_class.set_callback(:save, :around, self, :prepend => true)
+ define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
+ def insert(*)
+ self.class.state_machine(#{name.inspect}).send(:around_save, self) { super.persisted? }
+ self
+ end
+
+ def update(*)
+ self.class.state_machine(#{name.inspect}).send(:around_save, self) { super }
+ end
+
+ def upsert(*)
+ self.class.state_machine(#{name.inspect}).send(:around_save, self) { super }
+ end
+ end_eval
else
super
end
View
42 lib/state_machine/integrations/mongoid/versions.rb
@@ -23,6 +23,23 @@ def initialize(*)
def owner_class_attribute_default
attribute_field && attribute_field.default
end
+
+ def define_action_hook
+ if action_hook == :save
+ define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
+ def insert(*)
+ self.class.state_machine(#{name.inspect}).send(:around_save, self) { super.persisted? }
+ self
+ end
+
+ def update(*)
+ self.class.state_machine(#{name.inspect}).send(:around_save, self) { super }
+ end
+ end_eval
+ else
+ super
+ end
+ end
end
version '2.0.x - 2.3.x' do
@@ -58,31 +75,6 @@ def apply_default_attributes(*)
end
end_eval
end
-
- def define_action_hook
- # +around+ callbacks don't have direct access to results until AS 3.1
- owner_class.set_callback(:save, :after, 'value', :prepend => true) if action_hook == :save
- super
- end
- end
-
- version '2.0.x' do
- def self.active?
- ::Mongoid::VERSION =~ /^2\.0\./
- end
-
- # Forces the change in state to be recognized regardless of whether the
- # state value actually changed
- def write(object, attribute, value, *args)
- result = super
-
- if (attribute == :state || attribute == :event && value) && !object.send("#{self.attribute}_changed?")
- current = read(object, :state)
- object.changes[self.attribute.to_s] = [attribute == :event ? current : value, current]
- end
-
- result
- end
end
end
end
View
80 lib/state_machine/integrations/sequel.rb
@@ -134,18 +134,6 @@ module Integrations #:nodoc:
# end
# end
#
- # If using the +save+ action for the machine, this option will be ignored as
- # the transaction will be created by Sequel within +save+. To avoid
- # this, use a different action like so:
- #
- # class Vehicle < Sequel::Model
- # state_machine :initial => :parked, :use_transactions => false, :action => :save_state do
- # ...
- # end
- #
- # alias_method :save_state, :save
- # end
- #
# == Validation errors
#
# If an event fails to successfully fire because there are no matching
@@ -265,6 +253,26 @@ module Integrations #:nodoc:
# The +TransitionLog+ model uses 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 Sequel.
+ #
+ # * (-) save
+ # * (-) begin transaction (if enabled)
+ # * (1) *before_transition*
+ # * (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
module Sequel
include Base
@@ -359,6 +367,43 @@ def define_action_helpers
define_validation_hook if action == :save
end
+ # Uses around callbacks to run state events if using the :save hook
+ def define_action_hook
+ if action == :save
+ define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
+ def #{action_hook}(*args)
+ opts = args.last.is_a?(Hash) ? args.last : {}
+ yielded = false
+ result = self.class.state_machine(#{name.inspect}).send(:around_save, self) do
+ yielded = true
+ super
+ end
+
+ if yielded || result
+ result
+ else
+ #{handle_save_failure}
+ end
+ end
+ end_eval
+ else
+ super
+ end
+ end
+
+ # Handles how save failures (due to invalid transitions) are raised
+ def handle_save_failure
+ 'raise_hook_failure(:before_transition) if raise_on_failure?(opts)'
+ end
+
+ # Runs state events around the machine's :save action
+ def around_save(object)
+ result = transaction(object) do
+ object.class.state_machines.transitions(object, action).perform { yield }
+ end
+ result
+ end
+
# Adds hooks into validation for automatically firing events
def define_validation_hook
define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
@@ -368,11 +413,6 @@ def around_validation(*)
end_eval
end
- # Uses internal save hooks if using the :save action
- def action_hook
- action == :save ? :around_save : super
- end
-
# Gets the db default for the machine's attribute
def owner_class_attribute_default
if owner_class.db.table_exists?(owner_class.table_name) && column = owner_class.db_schema[attribute.to_sym]
@@ -426,7 +466,11 @@ def attribute_column
# Runs a new database transaction, rolling back any changes if the
# yielded block fails (i.e. returns false).
def transaction(object)
- object.db.transaction {raise ::Sequel::Error::Rollback unless yield}
+ result = nil
+ object.db.transaction do
+ raise ::Sequel::Error::Rollback unless result = yield
+ end
+ result
end
# Creates a new callback in the callback chain, always ensuring that
View
57 lib/state_machine/integrations/sequel/versions.rb
@@ -26,6 +26,7 @@ def set(*)
def define_validation_hook
define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
def valid?(*args)
+ opts = args.first.is_a?(Hash) ? args.first : {}
yielded = false
result = self.class.state_machines.transitions(self, :save, :after => false).perform do
yielded = true
@@ -40,31 +41,19 @@ def valid?(*args)
end
end_eval
end
+ end
+
+ version '2.8.x - 3.13.x' do
+ def self.active?
+ !defined?(::Sequel::MAJOR) || ::Sequel::MAJOR == 2 || ::Sequel::MAJOR == 3 && ::Sequel::MINOR <= 13
+ end
- def define_action_hook
- if action == :save
- define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
- def #{action_hook}(*)
- yielded = false
- result = self.class.state_machines.transitions(self, :save).perform do
- yielded = true
- super
- end
-
- if yielded || result
- result
- else
- #{handle_save_failure}
- end
- end
- end_eval
- else
- super
- end
+ def handle_validation_failure
+ 'raise_on_save_failure ? save_failure(:validation) : result'
end
- def action_hook
- action == :save ? :_save : super
+ def handle_save_failure
+ 'save_failure(:save) if raise_on_save_failure'
end
end
@@ -79,10 +68,6 @@ def load_plugins
def load_inflector
end
- def action_hook
- action == :save ? :save : super
- end
-
def model_from_dataset(dataset)
dataset.model_classes[nil]
end
@@ -96,31 +81,13 @@ def define_state_accessor
end
end
- version '2.8.x - 3.13.x' do
- def self.active?
- !defined?(::Sequel::MAJOR) || ::Sequel::MAJOR == 2 || ::Sequel::MAJOR == 3 && ::Sequel::MINOR <= 13
- end
-
- def handle_validation_failure
- 'raise_on_save_failure ? save_failure(:validation) : result'
- end
-
- def handle_save_failure
- 'save_failure(:save)'
- end
- end
-
version '3.14.x - 3.23.x' do
def self.active?
defined?(::Sequel::MAJOR) && ::Sequel::MAJOR == 3 && ::Sequel::MINOR >= 14 && ::Sequel::MINOR <= 23
end
def handle_validation_failure
- 'raise_on_failure?(args.first || {}) ? raise_hook_failure(:validation) : result'
- end
-
- def handle_save_failure
- 'raise_hook_failure(:save)'
+ 'raise_on_failure?(opts) ? raise_hook_failure(:validation) : result'
end
end
end
View
16 test/unit/integrations/active_model_test.rb
@@ -1,7 +1,7 @@
require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
require 'active_model'
-if defined?(::ActiveModel::VERSION) && ::ActiveModel::VERSION::MAJOR >= 4
+if defined?(ActiveModel::VERSION) && ActiveModel::VERSION::MAJOR >= 4
require 'rails/observers/active_model/active_model'
require 'active_model/mass_assignment_security'
else
@@ -517,11 +517,21 @@ def test_should_run_after_callbacks_outside_the_context_of_the_record
def test_should_run_around_callbacks
before_called = false
after_called = false
- @machine.around_transition {|block| before_called = true; block.call; after_called = true}
+ 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
@@ -803,7 +813,7 @@ def test_should_call_all_transition_callback_permutations
end
def test_should_call_no_transition_callbacks_when_observers_disabled
- return unless ::ActiveModel::VERSION::MAJOR >= 3 && ::ActiveModel::VERSION::MINOR >= 1
+ return unless ActiveModel::VERSION::MAJOR >= 3 && ActiveModel::VERSION::MINOR >= 1
callbacks = [
:before_ignite,
View
121 test/unit/integrations/active_record_test.rb
@@ -19,7 +19,7 @@ class ActiveRecord::TestCase < ActiveSupport::TestCase
end
require 'active_record/version'
-if ::ActiveRecord::VERSION::MAJOR >= 4
+if ActiveRecord::VERSION::MAJOR >= 4
require 'rails/observers/activerecord/active_record'
require 'active_record/mass_assignment_security'
end
@@ -211,7 +211,7 @@ def test_should_set_attributes_prior_to_initialize_block
def test_should_set_attributes_prior_to_after_initialize_hook
state = nil
- @model.class_eval {define_method(:after_initialize) {}} if ::ActiveRecord::VERSION::MAJOR <= 2
+ @model.class_eval {define_method(:after_initialize) {}} if ActiveRecord::VERSION::MAJOR <= 2
@model.after_initialize do |record|
state = record.state
end
@@ -249,7 +249,7 @@ def test_should_persist_initial_state
assert_equal 'parked', record.state
end
- unless ::ActiveRecord::VERSION::MAJOR == 3 && ::ActiveRecord::VERSION::MINOR == 0
+ unless ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0
def test_should_persist_initial_state_on_dup
record = @model.create.dup
record.save
@@ -312,7 +312,7 @@ def test_should_set_attributes_prior_to_initialize_block
def test_should_set_attributes_prior_to_after_initialize_hook
state = nil
- @model.class_eval {define_method(:after_initialize) {}} if ::ActiveRecord::VERSION::MAJOR <= 2
+ @model.class_eval {define_method(:after_initialize) {}} if ActiveRecord::VERSION::MAJOR <= 2
@model.after_initialize do |record|
state = record.state
end
@@ -350,7 +350,7 @@ def test_should_persist_initial_state
assert_equal 'parked', record.state
end
- unless ::ActiveRecord::VERSION::MAJOR == 3 && ::ActiveRecord::VERSION::MINOR == 0
+ unless ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0
def test_should_persist_initial_state_on_dup
record = @model.create.dup
record.save
@@ -1019,11 +1019,21 @@ def test_should_run_after_callbacks_if_model_callback_added_prior_to_state_machi
def test_should_run_around_callbacks
before_called = false
after_called = false
- @machine.around_transition {|block| before_called = true; block.call; after_called = true}
+ 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
@@ -1056,6 +1066,33 @@ class << @record
assert_equal [1, 2, 3], @record.callback_result
end
+
+ def test_should_run_in_expected_order
+ expected = [
+ :before_transition, :before_validation, :after_validation,
+ :before_save, :before_create, :after_create, :after_save,
+ :after_transition
+ ]
+
+ callbacks = []
+ @model.before_validation { callbacks << :before_validation }
+ @model.after_validation { callbacks << :after_validation }
+ @model.before_save { callbacks << :before_save }
+ @model.before_create { callbacks << :before_create }
+ @model.after_create { callbacks << :after_create }
+ @model.after_save { callbacks << :after_save }
+ if @model.respond_to?(:after_commit)
+ @model.after_commit { callbacks << :after_commit }
+ expected << :after_commit
+ end
+
+ @machine.before_transition { callbacks << :before_transition }
+ @machine.after_transition { callbacks << :after_transition }
+
+ @transition.perform
+
+ assert_equal expected, callbacks
+ end
end
class MachineWithFailedBeforeCallbacksTest < BaseTestCase
@@ -1093,6 +1130,42 @@ def test_should_not_run_further_callbacks
end
end
+ class MachineNestedActionTest < BaseTestCase
+ def setup
+ @callbacks = []
+
+ @model = new_model
+ @machine = StateMachine::Machine.new(@model)
+ @machine.event :ignite do
+ transition :parked => :idling
+ end
+
+ @record = @model.new(:state => 'parked')
+ end
+
+ def test_should_allow_transition_prior_to_creation_if_skipping_action
+ record = @record
+ @model.before_create { record.ignite(false) }
+ result = @record.save
+
+ assert_equal true, result
+ assert_equal "idling", @record.state
+ @record.reload
+ assert_equal "idling", @record.state
+ end
+
+ def test_should_allow_transition_after_creation
+ record = @record
+ @model.after_create { record.ignite }
+ result = @record.save
+
+ assert_equal true, result
+ assert_equal "idling", @record.state
+ @record.reload
+ assert_equal "idling", @record.state
+ end
+ end
+
class MachineWithFailedActionTest < BaseTestCase
def setup
@model = new_model do
@@ -1540,6 +1613,40 @@ def test_should_run_around_transition_within_transaction
assert_equal 0, @model.count
end
+
+ def test_should_allow_additional_transitions_to_new_state_in_after_transitions
+ @machine.event :park do
+ transition :idling => :parked
+ end
+
+ @machine.after_transition(:on => :ignite) { @record.park }
+
+ @record.save
+ assert_equal 'parked', @record.state
+
+ @record.reload
+ assert_equal 'parked', @record.state
+ end
+
+ def test_should_allow_additional_transitions_to_previous_state_in_after_transitions
+ @machine.event :shift_up do
+ transition :idling => :first_gear
+ end
+
+ @machine.after_transition(:on => :ignite) { @record.shift_up }
+
+ @record.save
+ assert_equal 'first_gear', @record.state
+
+ @record.reload
+ assert_equal 'first_gear', @record.state
+ end
+
+ def test_should_return_nil_on_manual_rollback
+ @machine.before_transition { raise ActiveRecord::Rollback }
+
+ assert_equal nil, @record.save
+ end
end
class MachineWithEventAttributesOnSaveBangTest < BaseTestCase
@@ -1700,7 +1807,7 @@ def test_should_call_all_transition_callback_permutations
end
def test_should_call_no_transition_callbacks_when_observers_disabled
- return unless ::ActiveRecord::VERSION::MAJOR >= 3 && ::ActiveRecord::VERSION::MINOR >= 1
+ return unless ActiveRecord::VERSION::MAJOR >= 3 && ActiveRecord::VERSION::MINOR >= 1
callbacks = [
:before_ignite,
View
140 test/unit/integrations/data_mapper_test.rb
@@ -1,16 +1,16 @@
require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
require 'dm-core'
-require 'dm-core/version' unless defined?(::DataMapper::VERSION)
+require 'dm-core/version' unless defined?(DataMapper::VERSION)
require 'dm-observer'
-if Gem::Version.new(::DataMapper::VERSION) >= Gem::Version.new('0.10.3')
+if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.3')
require 'dm-migrations'
end
# Establish database connection
DataMapper.setup(:default, 'sqlite3::memory:')
-DataObjects::Sqlite3.logger = DataObjects::Logger.new("#{File.dirname(__FILE__)}/../../data_mapper.log", :info)
+DataObjects::Sqlite3.logger = DataObjects::Logger.new("#{File.dirname(__FILE__)}/../../data_mapper.log", :debug)
module DataMapperTest
class BaseTestCase < Test::Unit::TestCase
@@ -119,7 +119,7 @@ def test_should_define_field_with_string_type
property = @resource.properties.detect {|p| p.name == :status}
assert_not_nil property
- if Gem::Version.new(::DataMapper::VERSION) >= Gem::Version.new('1.0.0')
+ if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('1.0.0')
assert_instance_of DataMapper::Property::String, property
else
assert_equal String, property.type
@@ -139,7 +139,7 @@ def test_should_not_redefine_field
property = @resource.properties.detect {|p| p.name == :status}
assert_not_nil property
- if Gem::Version.new(::DataMapper::VERSION) >= Gem::Version.new('1.0.0')
+ if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('1.0.0')
assert_instance_of DataMapper::Property::Integer, property
else
assert_equal Integer, property.type
@@ -617,7 +617,7 @@ def test_should_allow_different_initial_state_when_dynamic
assert_equal 'idling', record.state
end
- if Gem::Version.new(::DataMapper::VERSION) >= Gem::Version.new('0.9.8')
+ if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.9.8')
def test_should_raise_exception_if_protected
resource = new_resource do
protected :state=
@@ -692,7 +692,7 @@ def test_should_include_state_in_changed_attributes
end
def test_should_track_attribute_change
- if Gem::Version.new(::DataMapper::VERSION) >= Gem::Version.new('0.10.0')
+ if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.0')
assert_equal({@resource.properties[:state] => 'parked'}, @record.original_attributes)
else
assert_equal({:state => 'parked'}, @record.original_values)
@@ -703,7 +703,7 @@ def test_should_not_reset_changes_on_multiple_transitions
transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling)
transition.perform(false)
- if Gem::Version.new(::DataMapper::VERSION) >= Gem::Version.new('0.10.0')
+ if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.0')
assert_equal({@resource.properties[:state] => 'parked'}, @record.original_attributes)
else
assert_equal({:state => 'parked'}, @record.original_values)
@@ -753,7 +753,7 @@ def test_should_include_state_in_changed_attributes
end
def test_should_track_attribute_change
- if Gem::Version.new(::DataMapper::VERSION) >= Gem::Version.new('0.10.0')
+ if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.0')
assert_equal({@resource.properties[:status] => 'parked'}, @record.original_attributes)
else
assert_equal({:status => 'parked'}, @record.original_values)
@@ -764,7 +764,7 @@ def test_should_not_reset_changes_on_multiple_transitions
transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling)
transition.perform(false)
- if Gem::Version.new(::DataMapper::VERSION) >= Gem::Version.new('0.10.0')
+ if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.0')
assert_equal({@resource.properties[:status] => 'parked'}, @record.original_attributes)
else
assert_equal({:status => 'parked'}, @record.original_values)
@@ -806,7 +806,7 @@ def test_should_not_include_state_in_changed_attributes
end
def test_should_not_track_attribute_change
- if Gem::Version.new(::DataMapper::VERSION) >= Gem::Version.new('0.10.0')
+ if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.0')
assert_equal({}, @record.original_attributes)
else
assert_equal({}, @record.original_values)
@@ -840,7 +840,7 @@ def test_should_not_rollback_transaction_if_true
end
begin
- if Gem::Version.new(::DataMapper::VERSION) >= Gem::Version.new('0.10.3')
+ if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.3')
require 'dm-transactions'
end
@@ -941,12 +941,22 @@ def test_should_run_after_callbacks_with_the_context_of_the_record
def test_should_run_around_callbacks
before_called = false
- after_called = [false]
- @machine.around_transition {|block| before_called = true; block.call; after_called[0] = true}
+ 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[0]
+ assert after_called
+ assert_equal ensure_called, 1
end
def test_should_run_around_callbacks_with_the_context_of_the_record
@@ -981,6 +991,36 @@ class << @record
assert_equal [1, 2, 3], @record.callback_result
end
+
+ def test_should_run_in_expected_order
+ # Avoid Ruby 2.0.0 stack too deep issues
+ @resource.class_eval do
+ def valid?(*)
+ super
+ end
+ end
+
+ expected = [
+ :before_transition, :before_validation, :after_validation,
+ :before_save, :before_create, :after_create, :after_save,
+ :after_transition
+ ]
+
+ callbacks = []
+ @resource.before(:valid?) { callbacks << :before_validation }
+ @resource.after(:valid?) { callbacks << :after_validation }
+ @resource.before(:save) { callbacks << :before_save }
+ @resource.before(:create) { callbacks << :before_create }
+ @resource.after(:create) { callbacks << :after_create }
+ @resource.after(:save) { callbacks << :after_save }
+
+ @machine.before_transition { callbacks << :before_transition }
+ @machine.after_transition { callbacks << :after_transition }
+
+ @transition.perform
+
+ assert_equal expected, callbacks
+ end
end
class MachineWithFailedBeforeCallbacksTest < BaseTestCase
@@ -1020,6 +1060,42 @@ def test_should_not_run_further_callbacks
end
end
+ if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('1.0.0')
+ class MachineNestedActionTest < BaseTestCase
+ def setup
+ @callbacks = []
+
+ @resource = new_resource
+ @machine = StateMachine::Machine.new(@resource)
+ @machine.event :ignite do
+ transition :parked => :idling
+ end
+
+ @record = @resource.new(:state => 'parked')
+ end
+
+ def test_should_allow_transition_prior_to_creation_if_skipping_action
+ record = @record
+ @resource.before(:create) { record.ignite }
+ result = @record.save
+
+ assert_equal true, result
+ assert_equal "idling", @record.state
+ @record.reload
+ assert_equal "idling", @record.state
+ end
+
+ def test_should_not_allow_transition_after_creation
+ record = @record
+ @resource.after(:create) { record.ignite(false) }
+
+ result = @record.save
+
+ assert_equal false, result
+ end
+ end
+ end
+
class MachineWithFailedActionTest < BaseTestCase
def setup
@resource = new_resource do
@@ -1215,7 +1291,7 @@ def test_should_be_valid_if_validation_succeeds_within_state_scope
end
# See README caveats
- if Gem::Version.new(::DataMapper::VERSION) > Gem::Version.new('0.9.6')
+ if Gem::Version.new(DataMapper::VERSION) > Gem::Version.new('0.9.6')
class MachineWithEventAttributesOnValidationTest < BaseTestCase
def setup
@resource = new_resource
@@ -1483,10 +1559,38 @@ def test_should_not_run_around_transition_within_transaction
assert_equal false, @record.save
assert_equal 1, @resource.all.size
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) { 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) { shift_up }
+
+ @record.save
+ assert_equal 'first_gear', @record.state
+
+ @record.reload
+ assert_equal 'first_gear', @record.state
+ end
end
end
- if Gem::Version.new(::DataMapper::VERSION) >= Gem::Version.new('0.10.0')
+ if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.0')
class MachineWithEventAttributesOnSaveBangTest < BaseTestCase
def setup
@resource = new_resource
@@ -1569,7 +1673,7 @@ def test_should_run_around_callbacks_after_yield
end
end
- if Gem::Version.new(::DataMapper::VERSION) > Gem::Version.new('0.9.6')
+ if Gem::Version.new(DataMapper::VERSION) > Gem::Version.new('0.9.6')
class MachineWithEventAttributesOnCustomActionTest < BaseTestCase
def setup
@superclass = new_resource do
View
99 test/unit/integrations/mongo_mapper_test.rb
@@ -888,11 +888,21 @@ def test_should_run_after_callbacks_if_model_callback_added_prior_to_state_machi
def test_should_run_around_callbacks
before_called = false
after_called = false
- @machine.around_transition {|block| before_called = true; block.call; after_called = true}
+ 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
@@ -925,6 +935,29 @@ class << @record
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 }
+
+ @machine.before_transition { callbacks << :before_transition }
+ @machine.after_transition { callbacks << :after_transition }
+
+ @transition.perform
+
+ assert_equal expected, callbacks
+ end
end
class MachineWithFailedBeforeCallbacksTest < BaseTestCase
@@ -980,6 +1013,42 @@ def test_should_not_run_further_callbacks
end
end
+ class MachineNestedActionTest < BaseTestCase
+ def setup
+ @callbacks = []
+
+ @model = new_model
+ @machine = StateMachine::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 = @model.find(@record.id)
+ 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 = @model.find(@record.id)
+ assert_equal "idling", @record.state
+ end
+ end
+
class MachineWithFailedActionTest < BaseTestCase
def setup
@model = new_model do
@@ -1402,6 +1471,34 @@ def test_should_run_around_callbacks_after_yield
@record.save
assert ran_callback
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 = @model.find(@record.id)
+ 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 = @model.find(@record.id)
+ assert_equal 'first_gear', @record.state
+ end
end
class MachineWithEventAttributesOnSaveBangTest < BaseTestCase
View
181 test/unit/integrations/mongoid_test.rb
@@ -40,7 +40,7 @@ def new_model(name = :foo, &block)
model.class_eval do
include Mongoid::Document
- if ::Mongoid::VERSION =~ /^2\./
+ if Mongoid::VERSION =~ /^2\./
self.collection_name = table_name
else
self.default_collection_name = table_name
@@ -796,22 +796,12 @@ def setup
@transition.perform(false)
end
- if ::Mongoid::VERSION =~ /^2\.0\./
- def test_should_include_state_in_changed_attributes
- assert_equal %w(state), @record.changed
- end
-
- def test_should_not_track_attribute_changes
- assert_equal %w(parked parked), @record.send(:attribute_change, 'state')
- end
- else
- 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.send(:attribute_change, 'state')
- 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.send(:attribute_change, 'state')
end
end
@@ -860,22 +850,12 @@ def setup
@transition.perform(false)
end
- if ::Mongoid::VERSION =~ /^2\.0\./
- def test_should_include_state_in_changed_attributes
- assert_equal %w(status), @record.changed
- end
-
- def test_should_track_attribute_changes
- assert_equal %w(parked parked), @record.send(:attribute_change, 'status')
- end
- else
- def test_should_include_state_in_changed_attributes
- assert_equal [], @record.changed
- end
-
- def test_should_track_attribute_changes
- assert_equal nil, @record.send(:attribute_change, 'status')
- end
+ def test_should_include_state_in_changed_attributes
+ assert_equal [], @record.changed
+ end
+
+ def test_should_track_attribute_changes
+ assert_equal nil, @record.send(:attribute_change, 'status')
end
end
@@ -889,34 +869,12 @@ def setup
@record.state_event = 'ignite'
end
- if ::Mongoid::VERSION =~ /^2\.0\./
- 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 parked), @record.send(:attribute_change, 'state')
- end
-
- def test_should_not_reset_changes_on_multiple_changes
- @record.state_event = 'ignite'
- assert_equal %w(parked parked), @record.send(:attribute_change, 'state')
- end
-
- def test_should_not_include_state_in_changed_attributes_if_nil
- @record = @model.create
- @record.state_event = nil
-
- assert_equal [], @record.changed
- end
- else
- 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.send(:attribute_change, 'state')
- 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.send(:attribute_change, 'state')
end
end
@@ -1014,11 +972,21 @@ def test_should_run_after_callbacks_if_model_callback_added_prior_to_state_machi
def test_should_run_around_callbacks
before_called = false
after_called = false
- @machine.around_transition {|block| before_called = true; block.call; after_called = true}
+ 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
@@ -1051,6 +1019,29 @@ class << @record
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 }
+
+ @machine.before_transition { callbacks << :before_transition }
+ @machine.after_transition { callbacks << :after_transition }
+
+ @transition.perform
+
+ assert_equal expected, callbacks
+ end
end
class MachineWithFailedBeforeCallbacksTest < BaseTestCase
@@ -1088,6 +1079,44 @@ def test_should_not_run_further_callbacks
end
end
+ class MachineNestedActionTest < BaseTestCase
+ def setup
+ @callbacks = []
+
+ @model = new_model
+ @machine = StateMachine::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
+
+ if Mongoid::VERSION !~ /^2\.1\./
+ 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
+ end
+
class MachineWithFailedActionTest < BaseTestCase
def setup
@model = new_model do
@@ -1504,6 +1533,34 @@ def test_should_run_around_callbacks_after_yield
@record.save
assert ran_callback
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
end
class MachineWithEventAttributesOnSaveBangTest < BaseTestCase
View
197 test/unit/integrations/sequel_test.rb
@@ -17,9 +17,9 @@ def default_test
def new_model(create_table = :foo, &block)
name = create_table || :foo
table_name = "#{name}_#{rand(1000000)}"
- table_identifier = ::Sequel::SQL::Identifier.new(table_name)
+ table_identifier = Sequel::SQL::Identifier.new(table_name)
- if !defined?(Sequel::VERSION) || Gem::Version.new(::Sequel::VERSION) <= Gem::Version.new('3.26.0')
+ if !defined?(Sequel::VERSION) || Gem::Version.new(Sequel::VERSION) <= Gem::Version.new('3.26.0')
class << table_identifier
alias_method :original_to_s, :to_s
def to_s(*args); args.empty? ? inspect : original_to_s(*args); end
@@ -891,12 +891,22 @@ def test_should_run_after_callbacks_with_the_context_of_the_record
def test_should_run_around_callbacks
before_called = false
- after_called = [false]
- @machine.around_transition {|block| before_called = true; block.call; after_called[0] = true}
+ 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[0]
+ assert after_called
+ assert_equal ensure_called, 1
end
def test_should_run_around_callbacks_with_the_context_of_the_record
@@ -931,6 +941,33 @@ class << @record
assert_equal [1, 2, 3], @record.callback_result
end
+
+ def test_should_run_in_expected_order
+ expected = [
+ :before_transition, :before_validation, :after_validation,
+ :before_save, :before_create, :after_create, :after_save,
+ :after_transition
+ ]
+
+ callbacks = []
+ @model.before_validation { callbacks << :before_validation }
+ @model.after_validation { callbacks << :after_validation }
+ @model.before_save { callbacks << :before_save }
+ @model.before_create { callbacks << :before_create }
+ @model.after_create { callbacks << :after_create }
+ @model.after_save { callbacks << :after_save }
+ if @model.respond_to?(:after_commit)
+ @model.after_commit { callbacks << :after_commit }
+ expected << :after_commit
+ end
+
+ @machine.before_transition { callbacks << :before_transition }
+ @machine.after_transition { callbacks << :after_transition }
+
+ @transition.perform
+
+ assert_equal expected, callbacks
+ end
end
class MachineWithFailedBeforeCallbacksTest < BaseTestCase
@@ -970,6 +1007,44 @@ def test_should_not_run_further_callbacks
end
end
+ class MachineNestedActionTest < BaseTestCase
+ def setup
+ @callbacks = []
+
+ @model = new_model
+ @machine = StateMachine::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 result
+ assert_equal "idling", @record.state
+ @record.reload
+ assert_equal "idling", @record.state
+ end
+
+ if !defined?(Sequel::VERSION) || !%w(2.10.0 2.11.0).include?(Sequel::VERSION)
+ def test_should_allow_transition_after_creation
+ record = @record
+ @model.after_create { record.ignite }
+ result = @record.save
+
+ assert result
+ assert_equal "idling", @record.state
+ @record.reload
+ assert_equal "idling", @record.state
+ end
+ end
+ end
+
class MachineWithFailedActionTest < BaseTestCase
def setup
@model = new_model do
@@ -1407,29 +1482,17 @@ def test_should_not_run_after_callbacks_with_failures_disabled_if_fails
assert !ran_callback
end
- if defined?(Sequel::MAJOR) && Sequel::MAJOR >= 3 && Sequel::MINOR >= 7
- def test_should_not_run_failure_callbacks_if_fails
- @model.before_create {|record| false}
-
- ran_callback = false
- @machine.after_failure { ran_callback = true }
-
- @record.save
- assert !ran_callback
- end
- else
- def test_should_run_failure_callbacks_if_fails
- @model.before_create {|record| false}
-
- ran_callback = false
- @machine.after_failure { ran_callback = true }
-
- @record.save
- assert ran_callback
- end
+ def test_should_run_failure_callbacks_if_fails
+ @model.before_create {|record| false}
+
+ ran_callback = false
+ @machine.after_failure { ran_callback = true }
+
+ @record.save
+ assert ran_callback
end
- def test_should_not_run_before_transitions_within_transaction
+ def test_should_run_before_transitions_within_transaction
@machine.before_transition { self.class.create; raise Sequel::Error::Rollback }
begin
@@ -1437,7 +1500,7 @@ def test_should_not_run_before_transitions_within_transaction
rescue Sequel::Error::Rollback
end
- assert_equal 1, @model.count
+ assert_equal 0, @model.count
end
def test_should_not_run_around_callbacks_with_failures_disabled_if_fails
@@ -1458,44 +1521,54 @@ def test_should_run_around_callbacks_after_yield
assert ran_callback[0]
end
- if defined?(Sequel::MAJOR) && (Sequel::MAJOR >= 3 || Sequel::MAJOR == 2 && Sequel::MINOR == 12)
- def test_should_run_after_transitions_within_transaction
- @machine.after_transition { self.class.create; raise Sequel::Error::Rollback }
-
- @record.save
-
- assert_equal 0, @model.count
- end
-
- def test_should_run_around_transition_within_transaction
- @machine.around_transition {|block| block.call; self.class.create; raise Sequel::Error::Rollback }
-
- @record.save
-
- assert_equal 0, @model.count
- end
- else
- def test_should_not_run_after_transitions_within_transaction
- @machine.after_transition { self.class.create; raise Sequel::Error::Rollback }
-
- begin
- @record.save
- rescue Sequel::Error::Rollback
- end
-
- assert_equal 2, @model.count
+ def test_should_run_after_transitions_within_transaction
+ @machine.after_transition { self.class.create; raise Sequel::Error::Rollback }
+
+ @record.save
+
+ assert_equal 0, @model.count
+ end
+
+ def test_should_run_around_transition_within_transaction
+ @machine.around_transition {|block| block.call; self.class.create; raise Sequel::Error::Rollback }
+
+ @record.save
+
+ 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
- def test_should_not_run_around_transition_within_transaction
- @machine.around_transition {|block| block.call; self.class.create; raise Sequel::Error::Rollback }
-
- begin
- @record.save
- rescue Sequel::Error::Rollback
- end
-
- assert_equal 2, @model.count
+ @machine.after_transition(:on => :ignite) { 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) { shift_up }
+
+ @record.save
+ assert_equal 'first_gear', @record.state
+
+ @record.reload
+ assert_equal 'first_gear', @record.state
+ end
+
+ def test_should_return_nil_on_manual_rollback
+ @machine.before_transition { raise Sequel::Error::Rollback }
+
+ assert_equal nil, @record.save
end
end
Please sign in to comment.
Something went wrong with that request. Please try again.