Atomic transactions in rails controllers

Hi,

When I have a transaction block like this:

ActiveRecord::Base.Transaction do   cars.each do | car |     car.dosomething     car.save!   end end

When the last car in the collection fails, all the others will have gotten an ID. That means that if the fault results in a validation error, and I correct the form and resubmit, rails say it can't find a car with that id.

It's not very atomic this way.

I can solve it partly by setting the id's to nil in a rescue clause, but that has a drawback. In my form, I want certain fields to be disabled when editing an existing record (as opposed to creating a new one). I use car.new_record? for that. But, when the validation fails and I submit, and submit again, because of the loss of ID, rails doesn't know anymore that it was an existing record, and new_record? will return true.

So, my question: is there a way to unset the id and still let rails think the record already exists in the DB? Is it dangerous to make a method in AR::Base, which sets @new_record to true and id to nil?

Or, even better, is it possible to have true atomic transactions in rails models? (probably not, because there is no uncomitted state of variables in ruby, but I thought I'd ask...)

From: rubyonrails-talk@googlegroups.com [mailto:rubyonrails-talk@googlegroups.com] On Behalf Of halfgaar Sent: Tuesday, June 26, 2007 3:47 PM To: Ruby on Rails: Talk Subject: [Rails] Atomic transactions in rails controllers

Hi,

When I have a transaction block like this:

ActiveRecord::Base.Transaction do   cars.each do | car |     car.dosomething     car.save!   end end

Rails seems to fall down a bit when you want to edit multiple child records on one form. I've solved this problem by tying each child record to it's parent, and then I save! the parent, which also seems to validate and save it's children if necessary. I assign ID numbers before I create the records so that I can manipulate the correct div via ajax calls. Each child is in a div like <div id="email_1234567">, so without an id, the div doesn't work right.

Because the child records are tied to the parent, the parent is saved first, and then it's ID is applied via the has_many relationship to the children records. So, the unique number that I temporarily assign for handling the div is correctly replaced by rails. Rails seems smart enough to run the validations before attempting to save, so no records wind up in the database until they all test as valid.

def signup     unless request.post?       # initialize the view, set temporary id numbers       @user = User.new       @user.emails << Email.new do |o|         o.id = Time.now.to_i         o.emailtype_id = 1 # business       end       @user.phones << Phone.new do |o|         o.id = Time.now.to_i         o.phonetype_id = 1 # business       end       @user.addresses << Address.new do |o|         o.id = Time.now.to_i         o.addresstype_id = 1 # business       end     else       # post, so assign fields to objects       @user = User.new(params[:user])

      # the model protects the user name, password and admin flag from mass assignment       # so set them here       @user.login = params[:user][:login]       @user.password = params[:user][:password]                    # attach sub-models to model       params[:email].each {|e| @user.emails << Email.new(e)} if params[:email]       params[:phone].each {|t| @user.phones << Phone.new(t)} if params[:phone]       params[:address].each {|a| @user.addresses << Address.new(a)} if params[:address]

      # validate and save the whole shebang       @user.save!       redirect_back_or_default(:controller => '/account', :action => 'thankyou')       flash[:notice] = "Thanks for signing up!"     end
    rescue ActiveRecord::RecordInvalid
      # hack, assign id numbers to the sub-models so that the view will render correctly       # things will get very weird if the id's in the user table approach that of the current       # time in seconds. It's quite unlikely, however.       count = 0       [@user.emails, @user.addresses, @user.phones].flatten!.each do

obj>

        obj.id = Time.now.to_i + count unless obj.id         count = count + 100 # try to make sure we won't get the same id for multiple objects       end   end

In order for this to work, the fields for, example, the phone partial need to look something like:

<% @phone = phone %> <div id="<%= "phone_#{phone.id}" %>">   <%= hidden_field_tag 'phone[id]', phone.id %>   <label>Telephone Number:<br/>   <%= text_field_tag 'phone[number]', phone.number, :size => 15 %><br/>   <%= error_message_on 'phone', 'number', 'Telephone Number ', '', 'errorMessage' %> </div>

HTH

Regards, Rich

If validates_associated.

-- fxn

Hey, now this may be very convenient. I was about to reply to Rich that in my controllers, rails was very unpredictable when it comes to validating has_many collections (it sometimes would and sometimes wouldn't), and I have some very klunky code to work around that, but this may just solve all of that.

First, see my reply to Xavier's message.

While it doesn't solve my new_record issue, you did help me with another issue, but I do have a question. When you assign ID's to new objects, doesn't it complain about trying to save records which don't exists, because the ID's are not present in the DB? Or, does it only do that when new_record? == false?

BTW, I always solved the id of the div issue by assigning id's based on Time.now.to_s when the partial in question was rendered for an object that didn't exist in the DB yet. I will be exploring your method, since it looks cleaner.

Another thing: why aren't you using text_field(model, method) as opposed to using text_field_tag, and then implementing what text_field already does for you, including marking it red on validation failures.