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     person_id=@person.id

    @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 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 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.