Shagymoe
Shagymoe

Reputation: 1456

Rails transaction doesn't rollback on validation error

I have two models: user and company. They both get created from one form and I'm using a transaction like this:

 User.transaction do

  @user.save!

  @company.user = @user
  @company.save!

  @user.reload
  @user.company = @company
  @user.save!

 flash[:notice] = "Thank you for your registration."
  redirect_to_index
end

The user gets saved to the database even when one of the company's validations fails. I've tried adding explicit error handling of ActiveRecord::RecordInvalid but it didn't help. I thought the validation would raise the error to rollback the transaction anyway. Any help is greatly appreciated.

Thanks

Upvotes: 11

Views: 8816

Answers (5)

Douglas Lovell
Douglas Lovell

Reputation: 1607

attempting to save a new entry and revise an existing entry (based on the new entry) at the same time, ran into a similar problem. Tried transaction with rescue failed validation, but settled on this instead:

if @new_entry.valid? && @existing_entry.valid?
  ActiveRecord::Base.transaction do
    @new_entry.save!
    @existing_entry.save!
  end
end

the code validates first. it doesn't attempt to save unless both entries are valid. transaction semantics guard against incomplete entry on other errors if the database supports it. hope that's a good solution.

Upvotes: 3

Hartog
Hartog

Reputation: 108

save() and destroy() are always under transaction (see http://railsapi.com/doc/rails-v2.3.5/classes/ActiveRecord/Transactions/ClassMethods.html).

What I think you want to be doing is

  begin
    @company = Company.create!(params[:company])
    @user    = User.create!(params[:user]) { |user| user.company => @company }
  rescue => ex
    # ... handle your validation errors
  end

This would solve your problem as when company validations fail an exception will be raised and the User.create statement never executes.

Upvotes: 0

Kevin
Kevin

Reputation: 549

You must use a database engine that supports ACID transactions. For mysql that is INNODB.

show table status\G

If users or companies is not using InnoDB engine, you can change it w/ this command.

ALTER TABLE <table name> ENGINE INNODB;

the exception thrown from @company.save! *should trigger a ROLLBACK command to be sent to the database. you can verify this in the console/logfile when running script/server with DEBUG log level.

Upvotes: 2

Ram on Rails
Ram on Rails

Reputation: 1369

You will have to consider these scenarios

  1. Use a database system that supports transaction
  2. Re-Factor that code for a more logical approach

As an example, if you only have a one-to-one relation, it can be handled in more optimized way

Company has_one User
User belongs_to Company

Now, in Company model

@company.user.build(user_attributes)
@company.save  # will save company as well as user

I have not tested it as an example. Just off my mind. Have I understood the problem correctly?

Upvotes: 0

T.Raghavendra
T.Raghavendra

Reputation: 362

http://tempe.st/2007/05/transaction-in-rails/

I believe you must use a begin end block outside the transaction loop.

Upvotes: -3

Related Questions