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