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.