Saving records from multiple tables

I hav page where I am saving a “client” record and a “person” record.
Sometimes the “person” is an existing record and sometimes it is a new
record. The “client” is always new.
Here is some code:

class Person < ActiveRecord::Base
has_many :clients

class Client < ActiveRecord::Base
belongs_to :person
validates_presence_of :person_id

def create
@client = Client.new(params[:client])
begin #check if the person already exists
@person=Person.find(params[:person][:id])
rescue #otherwise create new person
@person=Person.new(params[:person])
end
@client.person=@person
begin
Client.transaction do
@person.save!
@client.person=@person
@client.save!
flash[:notice] = ‘Client was successfully created.’
redirect_to :action => ‘edit’, :id => @client.id
end
rescue
render :action => ‘new’
end
end

Im not really sure what the best way and order to do the saves is.
If I try to save client first then it wont have a person_id yet and
will fail validation.
If I save the person first and then the client fails validation for
some other reason the code sort of works in that it rolls back but the
@person record has an id set even though the save failed. It also
appears to return false for @person.new_record?

Is there a tidier way to do this? I have tried @client.person.save and
various other combinations but it is not really clear what actually
gets saved.

Any comments appreciated.

George

I see 2 things that might help you. On one hand you could use
accepts_nested_attributes_for in your person model and fields_for in
your view, then you would only need to save the person and that would
take care of the client as well but based on what I see in your code
that might not be feasible. On the other hand you could wrap the code
for your save commands in a transaction and if there is any type of
error during any of the saves Rails will roll back the transaction and
you’re golden.

Thanks for reply pepe.
You say to use a transaction… I already am using a transaction!

The issue is that if the person save succeeds and the client save
fails then the transaction is rolled back fine. However the person
record now has a person.id and subsequent code assumes that the record
is saved and tries to refind the record which of course does not
exist.

I have got around this by saving the original value of the person.id
and then resetting it in the rescue clause but it is a bit messy.

One other issue is that the only validation errors that get set are
the errors from the first record (person) that is saved so the user
may fix these up only to find new validation errors from the second
record…

I’m sure there must be a tidy way!

My latest version which works but is not pretty is:

def create
begin #try to find existing person
@person=Person.find(params[:person][:id])
rescue #they dont exist so create new
@person=Person.new(params[:person])
end
[email protected]

@client=Client.new(params[:client])
begin
  Person.transaction do
    @person.save!
    @client.person=@person
    @client.save!
    flash[:notice] = 'Client was successfully created.'
    redirect_to :action => 'edit', :id => @client.id
  end
rescue
  #reset client and person to param values
  @person.id=person_id
  render :action => 'new'
end

end

Cheers
George

You say to use a transaction… I already am using a transaction!

Sorry about that. I obviously didn’t read carefully your post.

the errors from the first record (person) that is saved so the user
may fix these up only to find new validation errors from the second
record…

I have not tried before what you are trying to do but I will as soon
as I have some time to spare. I don’t understand why Rails would not
clear the ID value of the record and reset the record in order for
new_record? to return true.

I’m sure there must be a tidy way!

If you use acceptes_nested_attributes_for and field_for the
validations will work just like you want them to work, however you
will need to change your form to be based on your person model, which
based on what you have explained would be pretty much impossible since
the person is selected in the form and you don’t have a person object
to base the form on.