Skip to content

Commit 031d229

Browse files
MONGOID-5708 Add :on to transaction callbacks (#5767)
1 parent 5237212 commit 031d229

File tree

4 files changed

+298
-55
lines changed

4 files changed

+298
-55
lines changed

lib/mongoid/clients/sessions.rb

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ def self.included(base)
1616

1717
module ClassMethods
1818

19+
# Actions that can be used to trigger transactional callbacks.
20+
# @api private
21+
CALLBACK_ACTIONS = [:create, :destroy, :update]
22+
1923
# Execute a block within the context of a session.
2024
#
2125
# @example Execute some operations in the context of a session.
@@ -101,6 +105,49 @@ def transaction(options = {}, session_options: {})
101105
end
102106
end
103107

108+
# Sets up a callback is called after a commit of a transaction.
109+
# The callback is called only if the document is created, updated, or destroyed
110+
# in the transaction.
111+
#
112+
# See +ActiveSupport::Callbacks::ClassMethods::set_callback+ for more
113+
# information about method parameters and possible options.
114+
def after_commit(*args, &block)
115+
set_options_for_callbacks!(args)
116+
set_callback(:commit, :after, *args, &block)
117+
end
118+
119+
# Shortcut for +after_commit :hook, on: [ :create, :update ]+
120+
def after_save_commit(*args, &block)
121+
set_options_for_callbacks!(args, on: [ :create, :update ])
122+
set_callback(:commit, :after, *args, &block)
123+
end
124+
125+
# Shortcut for +after_commit :hook, on: :create+.
126+
def after_create_commit(*args, &block)
127+
set_options_for_callbacks!(args, on: :create)
128+
set_callback(:commit, :after, *args, &block)
129+
end
130+
131+
# Shortcut for +after_commit :hook, on: :update+.
132+
def after_update_commit(*args, &block)
133+
set_options_for_callbacks!(args, on: :update)
134+
set_callback(:commit, :after, *args, &block)
135+
end
136+
137+
# Shortcut for +after_commit :hook, on: :destroy+.
138+
def after_destroy_commit(*args, &block)
139+
set_options_for_callbacks!(args, on: :destroy)
140+
set_callback(:commit, :after, *args, &block)
141+
end
142+
143+
# This callback is called after a create, update, or destroy are rolled back.
144+
#
145+
# Please check the documentation of +after_commit+ for options.
146+
def after_rollback(*args, &block)
147+
set_options_for_callbacks!(args)
148+
set_callback(:rollback, :after, *args, &block)
149+
end
150+
104151
private
105152

106153
# @return [ Mongo::Session ] Session for the current client.
@@ -145,6 +192,46 @@ def abort_transaction(session)
145192
doc.run_after_callbacks(:rollback)
146193
end
147194
end
195+
196+
# Transforms custom options for after_commit and after_rollback callbacks
197+
# into options for +set_callback+.
198+
def set_options_for_callbacks!(args)
199+
options = args.extract_options!
200+
args << options
201+
202+
if options[:on]
203+
fire_on = Array(options[:on])
204+
assert_valid_transaction_action(fire_on)
205+
options[:if] = [
206+
-> { transaction_include_any_action?(fire_on) },
207+
*options[:if]
208+
]
209+
end
210+
end
211+
212+
# Asserts that the given actions are valid for after_commit
213+
# and after_rollback callbacks.
214+
#
215+
# @param [ Array<Symbol> ] actions Actions to be checked.
216+
# @raise [ ArgumentError ] If any of the actions is not valid.
217+
def assert_valid_transaction_action(actions)
218+
if (actions - CALLBACK_ACTIONS).any?
219+
raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{CALLBACK_ACTIONS}"
220+
end
221+
end
222+
223+
def transaction_include_any_action?(actions)
224+
actions.any? do |action|
225+
case action
226+
when :create
227+
persisted? && previously_new_record?
228+
when :update
229+
!(previously_new_record? || destroyed?)
230+
when :destroy
231+
destroyed?
232+
end
233+
end
234+
end
148235
end
149236
end
150237
end

lib/mongoid/interceptable.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ module Interceptable
4444
# @api private
4545
define_model_callbacks :persist_parent
4646

47-
define_model_callbacks :commit, :rollback, only: :after
47+
define_callbacks :commit, :rollback,
48+
only: :after,
49+
scope: [:kind, :name]
4850

4951
attr_accessor :before_callback_halted
5052
end

spec/mongoid/clients/transactions_spec.rb

Lines changed: 164 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -786,111 +786,221 @@ def capture_exception
786786
before do
787787
Mongoid::Clients.with_name(:default).database.collections.each(&:drop)
788788
TransactionsSpecPerson.collection.create
789+
TransactionsSpecPersonWithOnCreate.collection.create
790+
TransactionsSpecPersonWithOnUpdate.collection.create
791+
TransactionsSpecPersonWithOnDestroy.collection.create
789792
TransactionSpecRaisesBeforeSave.collection.create
790793
TransactionSpecRaisesAfterSave.collection.create
791794
end
792795

793796
context 'when commit the transaction' do
794797
context 'create' do
795-
let!(:subject) do
796-
person = nil
797-
TransactionsSpecPerson.transaction do
798-
person = TransactionsSpecPerson.create!(name: 'James Bond')
798+
context 'without :on option' do
799+
let!(:subject) do
800+
person = nil
801+
TransactionsSpecPerson.transaction do
802+
person = TransactionsSpecPerson.create!(name: 'James Bond')
803+
end
804+
person
799805
end
800-
person
806+
807+
it_behaves_like 'commit callbacks are called'
801808
end
802809

803-
it_behaves_like 'commit callbacks are called'
810+
context 'when callback has :on option' do
811+
let!(:subject) do
812+
person = nil
813+
TransactionsSpecPersonWithOnCreate.transaction do
814+
person = TransactionsSpecPersonWithOnCreate.create!(name: 'James Bond')
815+
end
816+
person
817+
end
818+
819+
it_behaves_like 'commit callbacks are called'
820+
end
804821
end
805822

806823
context 'save' do
807-
let(:subject) do
808-
TransactionsSpecPerson.create!(name: 'James Bond').tap do |subject|
809-
subject.after_commit_counter.reset
810-
subject.after_rollback_counter.reset
824+
context 'without :on option' do
825+
let(:subject) do
826+
TransactionsSpecPerson.create!(name: 'James Bond').tap do |subject|
827+
subject.after_commit_counter.reset
828+
subject.after_rollback_counter.reset
829+
end
830+
end
831+
832+
context 'when modified once' do
833+
before do
834+
subject.transaction do
835+
subject.name = 'Austin Powers'
836+
subject.save!
837+
end
838+
end
839+
840+
it_behaves_like 'commit callbacks are called'
841+
end
842+
843+
context 'when modified multiple times' do
844+
before do
845+
subject.transaction do
846+
subject.name = 'Austin Powers'
847+
subject.save!
848+
subject.name = 'Jason Bourne'
849+
subject.save!
850+
end
851+
end
852+
853+
it_behaves_like 'commit callbacks are called'
811854
end
812855
end
813856

814-
context 'when modified once' do
857+
context 'with :on option' do
858+
let(:subject) do
859+
TransactionsSpecPersonWithOnUpdate.create!(name: 'James Bond').tap do |subject|
860+
subject.after_commit_counter.reset
861+
subject.after_rollback_counter.reset
862+
end
863+
end
864+
865+
context 'when modified once' do
866+
before do
867+
subject.transaction do
868+
subject.name = 'Austin Powers'
869+
subject.save!
870+
end
871+
end
872+
873+
it_behaves_like 'commit callbacks are called'
874+
end
875+
876+
context 'when modified multiple times' do
877+
before do
878+
subject.transaction do
879+
subject.name = 'Austin Powers'
880+
subject.save!
881+
subject.name = 'Jason Bourne'
882+
subject.save!
883+
end
884+
end
885+
886+
it_behaves_like 'commit callbacks are called'
887+
end
888+
end
889+
end
890+
891+
context 'update_attributes' do
892+
context 'without :on option' do
893+
let(:subject) do
894+
TransactionsSpecPerson.create!(name: 'James Bond').tap do |subject|
895+
subject.after_commit_counter.reset
896+
subject.after_rollback_counter.reset
897+
end
898+
end
899+
815900
before do
816901
subject.transaction do
817-
subject.name = 'Austin Powers'
818-
subject.save!
902+
subject.update_attributes!(name: 'Austin Powers')
819903
end
820904
end
821905

822906
it_behaves_like 'commit callbacks are called'
823907
end
824908

825-
context 'when modified multiple times' do
909+
context 'when callback has on option' do
910+
let(:subject) do
911+
TransactionsSpecPersonWithOnUpdate.create!(name: 'Jason Bourne')
912+
end
913+
826914
before do
827-
subject.transaction do
828-
subject.name = 'Austin Powers'
829-
subject.save!
830-
subject.name = 'Jason Bourne'
831-
subject.save!
915+
TransactionsSpecPersonWithOnUpdate.transaction do
916+
subject.update_attributes!(name: 'Foma Kiniaev')
832917
end
833918
end
834919

835920
it_behaves_like 'commit callbacks are called'
836921
end
837922
end
838923

839-
context 'update_attributes' do
840-
let(:subject) do
841-
TransactionsSpecPerson.create!(name: 'James Bond').tap do |subject|
842-
subject.after_commit_counter.reset
843-
subject.after_rollback_counter.reset
924+
context 'destroy' do
925+
context 'without :on option' do
926+
let(:after_commit_counter) do
927+
TransactionsSpecCounter.new
844928
end
845-
end
846929

847-
before do
848-
subject.transaction do
849-
subject.update_attributes!(name: 'Austin Powers')
930+
let(:after_rollback_counter) do
931+
TransactionsSpecCounter.new
850932
end
851-
end
852933

853-
it_behaves_like 'commit callbacks are called'
854-
end
934+
let(:subject) do
935+
TransactionsSpecPerson.create!(name: 'James Bond').tap do |p|
936+
p.after_commit_counter = after_commit_counter
937+
p.after_rollback_counter = after_rollback_counter
938+
end
939+
end
855940

856-
context 'destroy' do
857-
let(:after_commit_counter) do
858-
TransactionsSpecCounter.new
859-
end
941+
before do
942+
subject.transaction do
943+
subject.destroy
944+
end
945+
end
860946

861-
let(:after_rollback_counter) do
862-
TransactionsSpecCounter.new
947+
it_behaves_like 'commit callbacks are called'
863948
end
864949

865-
let(:subject) do
866-
TransactionsSpecPerson.create!(name: 'James Bond').tap do |p|
867-
p.after_commit_counter = after_commit_counter
868-
p.after_rollback_counter = after_rollback_counter
950+
context 'with :on option' do
951+
let(:after_commit_counter) do
952+
TransactionsSpecCounter.new
869953
end
870-
end
871954

872-
before do
873-
subject.transaction do
874-
subject.destroy
955+
let(:after_rollback_counter) do
956+
TransactionsSpecCounter.new
875957
end
876-
end
877958

878-
it_behaves_like 'commit callbacks are called'
959+
let(:subject) do
960+
TransactionsSpecPersonWithOnDestroy.create!(name: 'James Bond').tap do |p|
961+
p.after_commit_counter = after_commit_counter
962+
p.after_rollback_counter = after_rollback_counter
963+
end
964+
end
965+
966+
before do
967+
subject.transaction do
968+
subject.destroy
969+
end
970+
end
971+
972+
it_behaves_like 'commit callbacks are called'
973+
end
879974
end
880975
end
881976

882977
context 'when rollback the transaction' do
883978
context 'create' do
884-
let!(:subject) do
885-
person = nil
886-
TransactionsSpecPerson.transaction do
887-
person = TransactionsSpecPerson.create!(name: 'James Bond')
888-
raise Mongoid::Errors::Rollback
979+
context 'without :on option' do
980+
let!(:subject) do
981+
person = nil
982+
TransactionsSpecPerson.transaction do
983+
person = TransactionsSpecPerson.create!(name: 'James Bond')
984+
raise Mongoid::Errors::Rollback
985+
end
986+
person
889987
end
890-
person
988+
989+
it_behaves_like 'rollback callbacks are called'
891990
end
892991

893-
it_behaves_like 'rollback callbacks are called'
992+
context 'with :on option' do
993+
let!(:subject) do
994+
person = nil
995+
TransactionsSpecPersonWithOnCreate.transaction do
996+
person = TransactionsSpecPersonWithOnCreate.create!(name: 'James Bond')
997+
raise Mongoid::Errors::Rollback
998+
end
999+
person
1000+
end
1001+
1002+
it_behaves_like 'rollback callbacks are called'
1003+
end
8941004
end
8951005

8961006
context 'save' do

0 commit comments

Comments
 (0)