2010-03-01 34 views
4

我有一个after_create回调模型。此回调会导致在另一个模型中创建新记录。但是,如果创建子记录时验证失败,则原始交易仍在保存中。为什么在回调引用的模型中验证不会导致原始事务失败?

这看起来不对。根据Rails文档,整个事情被包裹在一个事务中。难道我做错了什么?

class ServiceProvision < ActiveRecord::Base 
    has_one :cash_receipt 
    after_create :receive_payment_for_service_provision, :if => Proc.new { |sp| sp.immediate_settlement == true } 

    private 

    def receive_payment_for_service_provision 
    cash_account = CashAccount.find_by_currency_id_and_institution_id(self.currency_id, self.institution_id) 
    CashReceipt.create(:account_id => account.id, :service_provision_id => self.id, :amount => self.amount, :currency_id => self.currency.id, :cash_account_id => (cash_account ? cash_account.id : nil)) 
    end 
end 

class CashReceipt < ActiveRecord::Base 
    belongs_to :service_provision 
    validates_presence_of :cash_account_id 
end 

的CashReceipt不会失败,但是我的新ServiceProvision对象仍然被保存了被传为cash_account_id零,当返回一个错误。

it "should fail if a cash account doesn't exist for the currency and institution" do 
    currency = Factory.create(:currency) 
    institution = Factory.create(:institution) 
    service_provision = Factory.build(:service_provision, :currency_id => currency.id, :institution_id => institution.id, :immediate_settlement => true) 

    service_provision.save.should == false 
    service_provision.should have(1).error  
end 


'ServiceProvision service provision creation should raise an error if a cash account doesn't exist for the currency and institution' FAILED expected: false, 
    got: true (using ==) 

这似乎从文档

两个基地#保存违背这一点,基料#破坏进来 包裹在确保 ,不管你在验证做或 回调将在发生交易 保护交易的保护。因此,您可以使用验证来检查 值,该交易取决于 ,或者您可以在 回调中引发异常以回滚,包括 after_ *回调。

如果我尝试手动取消交易回调,像这样:

cr = CashReceipt.create(:account_id => account.id, :service_provision_id => self.id, :amount => self.amount, :currency_id => self.currency.id, :cash_account_id => (cash_account ? cash_account.id : nil)) 
unless cr.errors.empty? 
    errors.add_to_base("Error while creating CashReciept [#{cr.errors}].")     
    return false 
end 

那么新ServiceProvision对象仍保存。

回答

0

感谢@KandadaBoggu,谁领导我的解决方案...

原来的解决办法是改变回调before_create,然后做到这一点:

def receive_payment_for_service_provision 
    cash_account = CashAccount.find_by_currency_id_and_institution_id(self.currency_id, self.institution_id) 
    cr = self.create_cash_receipt(:account_id => account.id, 
           :amount => self.amount, 
           :currency_id => self.currency.id, 
           :cash_account_id => (cash_account ? cash_account.id : nil)) 
    unless cr.errors.empty? 
     errors.add_to_base("Error while creating CashReciept [#{cr.errors}].")     
     return false 
    end 
    end 

换句话说,我们仍然需要手动检查关联中的验证错误。

0

您必须在receive_payment_for_service_proviion方法中检查CashReceipt.create调用的执行状态。

def receive_payment_for_service_provision 
    cash_account = CashAccount.find_by_currency_id_and_institution_id(self.currency_id, self.institution_id) 
    cr = CashReceipt.create(:account_id => account.id, :service_provision_id => self.id, :amount => self.amount, :currency_id => self.currency.id, :cash_account_id => (cash_account ? cash_account.id : nil)) 
    unless cr.errors.empty? 
     # Make the ServiceProvision instance invalid 
     errors.add_to_base("Error while creating CashReciept [#{cr.errors}].")     
     return false # terminate the callback chain and roll back the TX immediately. 
    end 
    end 

PS:您可以简化after_create规格如下:

after_create :receive_payment_for_service_provision, :if => :immediate_settlement? 
+0

这没有什么区别。虽然错误被提出,但交易仍然完成。哪些飞在面对AR ::交易文档 – Nick 2010-03-01 06:20:50

1

回滚只有before回调自动发生:

整个回调链封装在一个事务。如果之前回调方法返回正好为false或引发异常执行链暂停并发出ROLLBACK。回调之后只能通过引发异常来实现。

这很有意义,因为它允许AR在应用事务之前填充模型并将其保存在内存中。既然你已经完成了一个after它也不知道什么回滚。为什么不试试before_save并看看你得到了什么。

+0

随着before_save我不会有一个对象ID传递给我的关联模型? – Nick 2010-03-01 06:18:42

+1

您不需要它用于新对象。 – 2010-03-01 07:34:09

2

CacheReceipt创建为before_validation过滤器。由于ServiceProvision上有一个has_one关联,所以CacheReceipt对象在保存后将具有正确的:service_provision_id。您的代码将如下:

before_validation :receive_payment_for_service_provision, :if => :immediate_settlement? 

def receive_payment_for_service_provision 
    cash_account = CashAccount.find_by_currency_id_and_institution_id(self.currency_id, self.institution_id) 
    self.cash_receipt.build(:account_id => account.id, 
          :amount => self.amount, 
          :currency_id => self.currency.id, 
          :cash_account_id => (cash_account ? cash_account.id : nil)) 
end 

现在节省ServiceProvision实例将返回false如果有错误,同时节省相关的CacheReceipt

+0

谢谢,你在那里的正确轨道。我已将回调更改为before_create,但仍需检查关联错误并手动回滚。 – Nick 2010-03-01 08:02:01

相关问题