views:

634

answers:

4

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

A: 

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

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

T.Raghavendra
Not true. If you want to do something more than a simple rollback, you'll need a `begin`..`rescue`..`end` block, but an exception handler higher up the stack can just as easily handle it after the rollback is done. You can also let Rails handle it and show an error page.
Steve Madsen
A: 

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?

ramonrails
+2  A: 

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.

Kevin
A: 

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.

Hartog