1
0
Fork 0
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:
Anil Maurya 2020-05-12 23:11:04 +05:30
commit 1cf15ed16e
9 changed files with 83 additions and 21 deletions

View file

@ -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'

View file

@ -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)

View file

@ -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: "../"

View file

@ -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: "../"

View file

@ -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: "../"

View file

@ -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)

View file

@ -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

View file

@ -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