2012-02-14 2 views
1

컨트롤러 동작 내에서 긴 일련의 이벤트 중 하나가 실패합니다. 예를 들어 신용 카드가 처리되었지만 ActiveRecord 쿼리가 시간 초과되었습니다. 그 전화를 되돌릴 수있는 방법이 있습니까?Rails 컨트롤러 작업을 원 자성으로 만드시겠습니까?

예. 이 컨트롤러 액션과 :

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 

이 그냥 인위적인 예입니다 그리고 난 정말 모르겠어요 어떻게 것 실제로 일한다. 일반적으로 발생하는 것은 submit_order이있는 행에 대해 ActiveRecord::StatementInvalid: : execution expired과 같은 예외가 발생하면 실행해야하는 나머지 행을 수동으로 실행해야합니다.

답변

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 

가되지 않습니다 : 롤백 블록

  • 전화 실패한 작업에 대한 롤백에

    • 핸들 오류,하지만
  • +0

    아주 좋네요. 이 솔루션을 살펴보면 동료가 상태 시스템을 사용하도록 제안했습니다. 이것은 특정 상황에서 사용될 수 있지만 이것은 좋은 일반적인 해결책입니다. –

    1

    그냥 트랜잭션을 사용하는

    cart.transaction do 
        # ... 
    end 
    

    에 포장. 자세한 내용은 http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html

    +0

    내가 액티브 쿼리를 위해 잘 작동하는지 확인 해요. 어쩌면 내가 다른 종류의 이벤트에서 작동하는 솔루션을 찾고 있음을 분명히 해야겠다. 하나는 API 호출 일 수 있고 다른 하나는 ActiveRecord 쿼리 일 수 있으며 또 다른 하나는 몽고이 (Mongoid) 쿼리 일 수 있습니다. –

    +0

    그런 다음 예외를 '복구'하고 변경 사항을 롤백하십시오. – iblue

    1

    나 '를 조정하기 쉽다 조금 늦었지만 save 대신 save!을 사용해야한다고 생각합니다. 모델 내에서 실패했지만 저장하면 false를 반환합니다. 예외가 발생하고 ActiveRecord::Base.transaction do 블록 롤 올바르게 변경 ... 예를 들어

    백업 :

    def process_order 
        ActiveRecord::Base.transaction do 
        begin 
         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 
        rescue 
         # Exception raised ... ROLLBACK 
         raise ActiveRecord::Rollback 
        end 
    end 
    
    관련 문제