有时控制器操作中的一系列事件中的一个失败。例如,处理信用卡但随后ActiveRecord查询超时。有没有办法让这些电话变得可逆?
E.g。使用此控制器操作:
def process_order
cart = Cart.new(params[:cart])
load_order
response = credit_card.charge
if response
submit_order
order.receipt = Pdf.new(render_to_string(:partial => 'receipt')
order.receipt.pdf.generate
order.receipt.save
render :action => 'finished'
else
order.transaction = response
@message = order.transaction.message
order.transaction.save
render :action => 'charge_failed'
end
end
我希望能像这样放置一个块:
def process_order
transaction
cart = Cart.new(params[:cart])
load_order
response = credit_card.charge
if response
submit_order
order.receipt = Pdf.new(render_to_string(:partial => 'receipt')
order.receipt.pdf.generate
order.receipt.save
render :action => 'finished'
else
order.transaction = response
@message = order.transaction.message
order.transaction.save
render :action => 'charge_failed'
end
rollback
credit_card.cancel_charge
...
end
end
这只是一个人为的例子,我不确定它在实践中会起作用。通常情况下,我们会为ActiveRecord::StatementInvalid: : execution expired
的行获取submit_order
之类的异常,然后我们必须手动运行应该运行的其余行。
答案 0 :(得分:3)
这是一个通用解决方案。
class Transactable
def initialize(&block)
raise LocalJumpError unless block_given?
@block = block
end
def on_rollback(&block)
raise LocalJumpError unless block_given?
@rollback = block
self
end
def call
@block.call
end
def rollback
@rollback.call if @rollback
end
end
class Transaction
def initialize(tasks)
tasks = Array(tasks)
tasks.each do |t|
Transactable === t or raise TypeError
end
@tasks = tasks
end
def run
finished_tasks = []
begin
@tasks.each do |t|
t.call
finished_tasks << t
end
rescue => err
finished_tasks.each do |t|
t.rollback
end
raise err
end
end
end
if __FILE__ == $0
Transaction.new([
Transactable.new { puts "1: call" }.on_rollback { puts "1: rollback" },
Transactable.new { puts "2: call" }.on_rollback { puts "2: rollback" },
Transactable.new { puts "3: call"; raise "fail!" }.on_rollback { puts "3: rollback" },
]).run
end
请注意,它不会:
答案 1 :(得分:1)
将其包装在
中cart.transaction do
# ...
end
使用交易。有关详细信息,请参阅http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html