mirror of
https://github.com/aasm/aasm
synced 2023-03-27 23:22:41 -04:00
Merge branch 'stokarenko-fix-after-commit-within-nested-transaction-vol2'
This commit is contained in:
commit
1cf15ed16e
9 changed files with 83 additions and 21 deletions
|
@ -29,6 +29,7 @@ appraise 'rails_5.0' do
|
|||
gem 'dynamoid', '~> 1.3', platforms: :ruby
|
||||
gem 'aws-sdk', '~> 2', platforms: :ruby
|
||||
gem 'redis-objects'
|
||||
gem "after_commit_everywhere", "~> 0.1", ">= 0.1.5"
|
||||
end
|
||||
|
||||
appraise 'rails_5.0_nobrainer' do
|
||||
|
@ -43,6 +44,7 @@ appraise 'rails_5.1' do
|
|||
gem 'dynamoid', '~> 1.3', platforms: :ruby
|
||||
gem 'aws-sdk', '~>2', platforms: :ruby
|
||||
gem 'redis-objects'
|
||||
gem "after_commit_everywhere", "~> 0.1", ">= 0.1.5"
|
||||
end
|
||||
|
||||
appraise 'rails_5.2' do
|
||||
|
@ -52,9 +54,11 @@ appraise 'rails_5.2' do
|
|||
gem 'dynamoid', '~>2.2', platforms: :ruby
|
||||
gem 'aws-sdk', '~>2', platforms: :ruby
|
||||
gem 'redis-objects'
|
||||
gem "after_commit_everywhere", "~> 0.1", ">= 0.1.5"
|
||||
end
|
||||
|
||||
appraise 'norails' do
|
||||
gem 'sqlite3', '~> 1.3', '>= 1.3.5', platforms: :ruby
|
||||
gem 'rails', install_if: false
|
||||
gem 'sequel'
|
||||
gem 'redis-objects'
|
||||
|
|
|
@ -981,6 +981,12 @@ job.run
|
|||
job.save! #notify_about_running_job is not run
|
||||
```
|
||||
|
||||
Please note that `:after_commit` AASM callbacks behaves around custom implementation
|
||||
of transaction pattern rather than a real-life DB transaction. This fact still causes
|
||||
the race conditions and redundant callback calls within nested transaction. In order
|
||||
to fix that it's highly recommended to add `gem 'after_commit_everywhere', '~> 0.1', '>= 0.1.5'`
|
||||
to your `Gemfile`.
|
||||
|
||||
If you want to encapsulate state changes within an own transaction, the behavior
|
||||
of this nested transaction might be confusing. Take a look at
|
||||
[ActiveRecord Nested Transactions](http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html)
|
||||
|
|
|
@ -9,5 +9,6 @@ gem "sequel"
|
|||
gem "dynamoid", "~> 1.3", platforms: :ruby
|
||||
gem "aws-sdk", "~> 2", platforms: :ruby
|
||||
gem "redis-objects"
|
||||
gem "after_commit_everywhere", "~> 0.1", ">= 0.1.5"
|
||||
|
||||
gemspec path: "../"
|
||||
|
|
|
@ -9,5 +9,6 @@ gem "sequel"
|
|||
gem "dynamoid", "~> 1.3", platforms: :ruby
|
||||
gem "aws-sdk", "~>2", platforms: :ruby
|
||||
gem "redis-objects"
|
||||
gem "after_commit_everywhere", "~> 0.1", ">= 0.1.5"
|
||||
|
||||
gemspec path: "../"
|
||||
|
|
|
@ -9,5 +9,6 @@ gem "sequel"
|
|||
gem "dynamoid", "~>2.2", platforms: :ruby
|
||||
gem "aws-sdk", "~>2", platforms: :ruby
|
||||
gem "redis-objects"
|
||||
gem "after_commit_everywhere", "~> 0.1", ">= 0.1.5"
|
||||
|
||||
gemspec path: "../"
|
||||
|
|
|
@ -28,6 +28,19 @@ module AASM
|
|||
# end
|
||||
#
|
||||
def self.included(base)
|
||||
begin
|
||||
require 'after_commit_everywhere'
|
||||
raise LoadError unless Gem::Version.new(::AfterCommitEverywhere::VERSION) >= Gem::Version.new('0.1.5')
|
||||
|
||||
base.send(:include, ::AfterCommitEverywhere) unless base.include?(::AfterCommitEverywhere)
|
||||
base.send(:alias_method, :aasm_execute_after_commit, :after_commit)
|
||||
rescue LoadError
|
||||
warn <<-MSG
|
||||
[DEPRECATION] :after_commit AASM callback is not safe in terms of race conditions and redundant calls.
|
||||
Please add `gem 'after_commit_everywhere', '~> 0.1', '>= 0.1.5'` to your Gemfile in order to fix that.
|
||||
MSG
|
||||
end
|
||||
|
||||
base.send(:include, AASM::Persistence::Base)
|
||||
base.send(:include, AASM::Persistence::ORM)
|
||||
base.send(:include, AASM::Persistence::ActiveRecordPersistence::InstanceMethods)
|
||||
|
|
|
@ -81,6 +81,10 @@ module AASM
|
|||
true
|
||||
end
|
||||
|
||||
def aasm_execute_after_commit
|
||||
yield
|
||||
end
|
||||
|
||||
def aasm_write_state_attribute(state, name=:default)
|
||||
aasm_write_attribute(self.class.aasm(name).attribute_name, aasm_raw_attribute_value(state, name))
|
||||
end
|
||||
|
@ -116,32 +120,32 @@ module AASM
|
|||
|
||||
# Returns true if event was fired successfully and transaction completed.
|
||||
def aasm_fire_event(state_machine_name, name, options, *args, &block)
|
||||
if aasm_supports_transactions? && options[:persist]
|
||||
event = self.class.aasm(state_machine_name).state_machine.events[name]
|
||||
event.fire_callbacks(:before_transaction, self, *args)
|
||||
event.fire_global_callbacks(:before_all_transactions, self, *args)
|
||||
return super unless aasm_supports_transactions? && options[:persist]
|
||||
|
||||
begin
|
||||
success = if options[:persist] && use_transactions?(state_machine_name)
|
||||
aasm_transaction(requires_new?(state_machine_name), requires_lock?(state_machine_name)) do
|
||||
super
|
||||
end
|
||||
else
|
||||
event = self.class.aasm(state_machine_name).state_machine.events[name]
|
||||
event.fire_callbacks(:before_transaction, self, *args)
|
||||
event.fire_global_callbacks(:before_all_transactions, self, *args)
|
||||
|
||||
begin
|
||||
success = if options[:persist] && use_transactions?(state_machine_name)
|
||||
aasm_transaction(requires_new?(state_machine_name), requires_lock?(state_machine_name)) do
|
||||
super
|
||||
end
|
||||
else
|
||||
super
|
||||
end
|
||||
|
||||
if success
|
||||
if success
|
||||
aasm_execute_after_commit do
|
||||
event.fire_callbacks(:after_commit, self, *args)
|
||||
event.fire_global_callbacks(:after_all_commits, self, *args)
|
||||
end
|
||||
|
||||
success
|
||||
ensure
|
||||
event.fire_callbacks(:after_transaction, self, *args)
|
||||
event.fire_global_callbacks(:after_all_transactions, self, *args)
|
||||
end
|
||||
else
|
||||
super
|
||||
|
||||
success
|
||||
ensure
|
||||
event.fire_callbacks(:after_transaction, self, *args)
|
||||
event.fire_global_callbacks(:after_all_transactions, self, *args)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -613,6 +613,38 @@ if defined?(ActiveRecord)
|
|||
expect(validator).to be_running
|
||||
expect(validator.name).to eq("name")
|
||||
end
|
||||
|
||||
context "nested transaction" do
|
||||
it "should fire :after_commit if root transaction was successful" do
|
||||
validator = Validator.create(:name => 'name')
|
||||
expect(validator).to be_sleeping
|
||||
|
||||
validator.transaction do
|
||||
validator.run!
|
||||
expect(validator.name).to eq("name")
|
||||
expect(validator).to be_running
|
||||
end
|
||||
|
||||
expect(validator.name).to eq("name changed")
|
||||
expect(validator.reload).to be_running
|
||||
end
|
||||
|
||||
it "should not fire :after_commit if root transaction failed" do
|
||||
validator = Validator.create(:name => 'name')
|
||||
expect(validator).to be_sleeping
|
||||
|
||||
validator.transaction do
|
||||
validator.run!
|
||||
expect(validator.name).to eq("name")
|
||||
expect(validator).to be_running
|
||||
|
||||
raise ActiveRecord::Rollback, "failed on purpose"
|
||||
end
|
||||
|
||||
expect(validator.name).to eq("name")
|
||||
expect(validator.reload).to be_sleeping
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'before and after transaction callbacks' do
|
||||
|
|
Loading…
Reference in a new issue