I have a model with after_create callback. This callback causes a new record to be created in another model. However, if verification is not performed when creating the child record, the original transaction is still retained.
This does not seem right. According to the Rails docs, all of this is wrapped in a transaction. Am I doing something wrong?
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 exits and returns an error when its passed nil for cash_account_id, however my new ServiceProvision object is still saved.
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 ==)
This seems to contradict this from the docs.
Both Base # save and Base # destroy come wrapped up in a transaction, which ensures that everything you do in validation or callbacks will have a protected transaction cover. So you can use checks to check the value that the transaction depends on or you can throw exceptions in callbacks to rollback, including after_ * callbacks.
And if I manually try to cancel the transaction in the callback like this:
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
then the new ServiceProvision object is saved.