Reputation: 1456
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
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
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
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
Reputation: 1369
You will have to consider these scenarios
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
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