Skip to content

Instantly share code, notes, and snippets.

@dany1468
Created August 19, 2017 23:12
Show Gist options
  • Select an option

  • Save dany1468/e383681cd1e5cb44d9fa23a6c2c393b0 to your computer and use it in GitHub Desktop.

Select an option

Save dany1468/e383681cd1e5cb44d9fa23a6c2c393b0 to your computer and use it in GitHub Desktop.
Rails でのバックグラウンドジョブの実行と transaction 利用について ref: http://qiita.com/dany1468/items/f9278b43d4ecb63c2920
ActiveRecord::Base.transaction do
ActiveRecord::Base.after_transaction_commit { run_some_background_job }
# run_some_background_job has not run yet
end
# now, it has run
# this one runs immediately, since we are outside a transaction
ActiveRecord::Base.after_transaction_commit { some_other_task }
class UserMailWorker
include Sidekiq::Worker
def perform(user_id)
user = User.find(user_id)
# 送信処理
end
end
User.transaction do
user = User.create!(param)
UserMailWorker.perform_async(user.id)
# 他の transaction 内でするべき処理など
end
module ActiveRecord
module ConnectionAdapters # :nodoc:
module DatabaseStatements
def add_transaction_record(record)
current_transaction.add_record(record)
end
module ActiveRecord
module ConnectionAdapters
class Transaction #:nodoc:
attr_reader :connection, :state, :records, :savepoint_name
attr_writer :joinable
def initialize(connection, options, run_commit_callbacks: false)
@connection = connection
@state = TransactionState.new
@records = []
@joinable = options.fetch(:joinable, true)
@run_commit_callbacks = run_commit_callbacks
end
def add_record(record)
records << record
end
def commit_records
ite = records.uniq
while record = ite.shift
if @run_commit_callbacks
record.committed!
else
# if not running callbacks, only adds the record to the parent transaction
record.add_to_transaction
end
end
ensure
ite.each { |i| i.committed!(should_run_callbacks: false) }
end
module ActiveRecord
module ConnectionAdapters
class TransactionManager #:nodoc:
def initialize(connection)
@stack = []
@connection = connection
end
def begin_transaction(options = {})
# 略
end
def commit_transaction
@connection.lock.synchronize do
transaction = @stack.last
begin
transaction.before_commit_records
ensure
@stack.pop
end
transaction.commit
# 🏁 ここで commit_records
transaction.commit_records
end
end
def rollback_transaction(transaction = nil)
@connection.lock.synchronize do
transaction ||= @stack.pop
transaction.rollback
transaction.rollback_records
end
end
def within_new_transaction(options = {})
@connection.lock.synchronize do
begin
transaction = begin_transaction options
yield
rescue Exception => error
if transaction
rollback_transaction
after_failure_actions(transaction, error)
end
raise
ensure
unless error
if Thread.current.status == "aborting"
rollback_transaction if transaction
else
begin
# 🏁 ここで commit_transaction
commit_transaction
rescue Exception
rollback_transaction(transaction) unless transaction.state.completed?
raise
end
end
end
end
end
end
class AsyncRecord
def initialize(*args)
@args = args
end
def has_transactional_callbacks?
true
end
def committed!(*_, **__)
do_after_commit
end
def rolledback!(*_, **__)
# after_rollback 相当の処理
end
def do_after_commit
# after_commit の処理
end
end
def perform_after_transaction(*args)
async_record = AsyncRecord.new(*args)
if ActiveRecord::Base.connection.transaction_open?
ActiveRecord::Base.
connection.
current_transaction.
add_record(async_record)
else
async_record.do_after_commit
end
end
User.transaction do
user = User.create!(param)
# 他の transaction 内でするべき処理など
end
UserMailWorker.perform_async(user.id)
class Conversation < ActiveRecord::Base
enum status: [:inactive :active, :archived]
after_update_commit :notify_active
def notify_active
if previous_changes.key?(:status) && previous_changes[:status].first.to_s == 'active'
NotifyWorker.perform_async(id)
end
end
end
class Conversation < ActiveRecord::Base
enum status: [:inactive :active, :archived]
aasm column: :status, enum: true do
state :inactive, initial: true
state :active
state :archived
event :activate, after_commit: :notify_active do
transitions from: :inactive, to: :active
end
event :archive do
transitions from: %i(inactive active), to: :archive
end
end
def notify_active
# activate イベントでしか実行されないので、ここに条件は不要
NotifyWorker.perform_async(id)
end
end
module ActiveRecord
module Transactions
def destroy #:nodoc:
with_transaction_returning_status { super }
end
def save(*) #:nodoc:
rollback_active_record_state! do
with_transaction_returning_status { super }
end
end
def save!(*) #:nodoc:
with_transaction_returning_status { super }
end
def touch(*) #:nodoc:
with_transaction_returning_status { super }
end
def with_transaction_returning_status
status = nil
self.class.transaction do
add_to_transaction
begin
status = yield
rescue ActiveRecord::Rollback
clear_transaction_record_state
status = nil
end
raise ActiveRecord::Rollback unless status
end
status
ensure
if @transaction_state && @transaction_state.committed?
clear_transaction_record_state
end
end
def add_to_transaction
if has_transactional_callbacks?
self.class.connection.add_transaction_record(self)
else
sync_with_transaction_state
set_transaction_state(self.class.connection.transaction_state)
end
remember_transaction_record_state
end
private
def has_transactional_callbacks?
!_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_before_commit_callbacks.empty?
end
module AASM
module Persistence
module ORM
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)
begin
# 🏁 ここの super で lifecycle が実行される
# https://github.com/aasm/aasm#lifecycle
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
# ☠️ after_commit は単に lifecycle が終了した時に呼ばれるだけで、transaction とは関係が無い
if success
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
end
end
class User < ApplicationRecord
after_create_commit :send_mail
def send_mail
UserMailWorker.perform_async(id)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment