I have a model with an after_create callback. This callback causes a new record to be created in another model. However if a validation fails in the child record creation, the original transaction is still being saved.
This doesn't seem right. According to Rails docs the whole thing 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
The CashReceipt does fail and returns an error when its passed nil for the cash_account_id, however my new ServiceProvision object is still being 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 in a transaction that ensures that whatever you do in validations or callbacks will happen under the protected cover of a transaction. So you can use validations to check for values that the transaction depends on or you can raise exceptions in the callbacks to rollback, including after_* callbacks.
And if I manually try to cancel the transaction in the callback like so:
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 still saved.