Returning changed fields when transaction fails...

I hope I'm explaining this correctly. Be gentle. Noob here. :slight_smile:

Ideas on how to solve this? Kind of a chicken or the egg scenario. Check out this update function in my controller:

  def update     @snowplow_registration = SnowplowRegistration.find(params[:id])     @customer = Customer.find(@snowplow_registration.customer)

    SnowplowRegistration.transaction do       @snowplow_registration.update_attributes!(params[:snowplow_registration])       @customer.update_attributes!(params[:customer])       flash[:notice] = 'Snow Plow Registration was successfully updated.'       redirect_to(@snowplow_registration)     end

  rescue ActiveRecord::RecordInvalid => e     @customer.valid?     render :action => "edit"   end

When the transaction starts, if validation fails on the @snowplow_registration, it doesn't check the validation on my @customer for the fields that were updated. Validation for both works properly when testing separate... but @customer returns to the edit page without passing the changed (params[:customer]) because the failure in the transaction happened before I can call @customer.update_attributes!(params[:customer]). Is there a way to update the model in memory before updating the database?

Thank you for looking.

Michael Kahle wrote:

I hope I'm explaining this correctly. Be gentle. Noob here. :slight_smile:

Ideas on how to solve this? Kind of a chicken or the egg scenario. Check out this update function in my controller:

  def update     @snowplow_registration = SnowplowRegistration.find(params[:id])     @customer = Customer.find(@snowplow_registration.customer)

    SnowplowRegistration.transaction do       @snowplow_registration.update_attributes!(params[:snowplow_registration])       @customer.update_attributes!(params[:customer])       flash[:notice] = 'Snow Plow Registration was successfully updated.'       redirect_to(@snowplow_registration)     end

  rescue ActiveRecord::RecordInvalid => e     @customer.valid?     render :action => "edit"   end

When the transaction starts, if validation fails on the @snowplow_registration, it doesn't check the validation on my @customer for the fields that were updated. Validation for both works properly when testing separate... but @customer returns to the edit page without passing the changed (params[:customer]) because the failure in the transaction happened before I can call @customer.update_attributes!(params[:customer]). Is there a way to update the model in memory before updating the database?

This is one way:

   def update      @snowplow_registration = SnowplowRegistration.find(params[:id])      @customer = @snowplow_registration.customer

     @snowplow_registration.attributes = params[:snowplow_registration]      @customer.attributes = params[:customer]

     customer_valid = @customer.valid?      if @snowplow_registration.valid? && customer_valid        SnowplowRegistration.transaction do          @snowplow_registration.save!          @customer.save!          flash[:notice] = 'Snow Plow Registration was updated.'          redirect_to(@snowplow_registration)        end      else        render :action => :edit      end    end

If you add "validates_associated :customer" to the SnowplowRegistration class you only then need to check the validity of @snowplow_registration, but the validity of the customer will now be checked whenever a SnowplowRegistration instance is saved.

Mark Reginald James wrote:

Michael Kahle wrote:

      @snowplow_registration.update_attributes!(params[:snowplow_registration])

When the transaction starts, if validation fails on the @snowplow_registration, it doesn't check the validation on my @customer for the fields that were updated. Validation for both works properly when testing separate... but @customer returns to the edit page without passing the changed (params[:customer]) because the failure in the transaction happened before I can call @customer.update_attributes!(params[:customer]). Is there a way to update the model in memory before updating the database?

This is one way:

   def update      @snowplow_registration = SnowplowRegistration.find(params[:id])      @customer = @snowplow_registration.customer

     @snowplow_registration.attributes = params[:snowplow_registration]      @customer.attributes = params[:customer]

     customer_valid = @customer.valid?      if @snowplow_registration.valid? && customer_valid        SnowplowRegistration.transaction do          @snowplow_registration.save!          @customer.save!          flash[:notice] = 'Snow Plow Registration was updated.'          redirect_to(@snowplow_registration)        end      else        render :action => :edit      end    end

If you add "validates_associated :customer" to the SnowplowRegistration class you only then need to check the validity of @snowplow_registration, but the validity of the customer will now be checked whenever a SnowplowRegistration instance is saved.

Super! Thanks so much. It worked perfectly. The only thing I changed from your example was to handle the update as a transaction. Updating @registration and @customer with the ".attributes" method did the trick perfectly. I am off to experiment with the "validates_associated :customer".

Thanks again.

Michael

Michael Kahle wrote:

Super! Thanks so much. It worked perfectly. The only thing I changed from your example was to handle the update as a transaction. Updating @registration and @customer with the ".attributes" method did the trick perfectly. I am off to experiment with the "validates_associated :customer".

Could you explain a little more what you changed and why.

One little improvement: Use save_without_validation! to prevent the validations from being run twice.

Mark Reginald James wrote:

Michael Kahle wrote:

Super! Thanks so much. It worked perfectly. The only thing I changed from your example was to handle the update as a transaction. Updating @registration and @customer with the ".attributes" method did the trick perfectly. I am off to experiment with the "validates_associated :customer".

Could you explain a little more what you changed and why.

I will do my best; it certainly helps to re-enforce what I've learned.

In your example you provided, what was to me, a novel idea in terms of updating the @snowplow_registration and @customer objects. When these objects were first created in the update method, they had the properties of a newly fetched record from the database. It wasn't until I called on each of these:       @snowplow_registration.update_attributes!(params[:snowplow_registration])       @customer.update_attributes!(params[:customer]) respectively, did the newly created objects, instantiated at the beginning, take on the properties of my submitted form. My problem was that when the transaction failed during the first update, I would never see the newly created @customer object updated with my form information. By calling the ".attributes = params" method on each of these BEFORE I started my transaction I now had in memory the new form data I entered to pass to my rescue catch. In your code, you check the Customer object before submitting it to be written.

I was about to write about how important it was for me to do this update in a transaction because of the possibility of the database failing in between writes and how your code didn't account for that. I glanced over your code when I saw:       if @snowplow_registration.valid? && customer_valid and simply assumed you were doing a standard non-transactional save to the database. Now that I'm inspecting this more closely, I see that you did-in-fact keep this a transactional operation. Here is the code I am using now:

  def update     @snowplow_registration = SnowplowRegistration.find(params[:id])     @customer = @snowplow_registration.customer

    @snowplow_registration.attributes = params[:snowplow_registration]     @customer.attributes = params[:customer]

    SnowplowRegistration.transaction do       @snowplow_registration.update_attributes!(params[:snowplow_registration])       @customer.update_attributes!(params[:customer])       flash[:notice] = 'Snow Plow Registration was successfully updated.'       redirect_to(@snowplow_registration)     end

  rescue ActiveRecord::RecordInvalid => e     @customer.valid?     render :action => "edit"   end

As you can see the only changes I've made from my first post are:

1) I originally had in my 3rd line:       @customer = Customer.find(@snowplow_registration.customer)    and changed it to what you had:       @customer = @snowplow_registration.customer    I thought your way looked cleaner.

2) I then added:       @snowplow_registration.attributes = params[:snowplow_registration]       @customer.attributes = params[:customer]    as I explained above, this did the trick to update BEFORE    my transaction occurred, the @customer and @registration classes.

I had to change nothing else from my original example as the checks are already in place.

One little improvement: Use save_without_validation! to prevent the validations from being run twice.

Another good thought, however what I am using now doesn't have the same duplication as your suggested code does. Unless of course when I call the "attributes" method it runs a check then as well. I'm not sure that is the case. I'll have to think about that.

Thank you so much for this discussion and all the wonderful new tidbits you've showed me. Like I said above, this discussion just helps reinforce the things that I am learning.

Michael Kahle wrote:

Mark Reginald James wrote:

Michael Kahle wrote:

Super! Thanks so much. It worked perfectly. The only thing I changed from your example was to handle the update as a transaction. Updating @registration and @customer with the ".attributes" method did the trick perfectly. I am off to experiment with the "validates_associated :customer".

Could you explain a little more what you changed and why.

Ooo. Another thought. I noticed that you are doing a transaction without using the "rescue" catch. If something else goes wrong, besides validation, you will never know what happened! :slight_smile:

     if @snowplow_registration.valid? && customer_valid        SnowplowRegistration.transaction do          @snowplow_registration.save!          @customer.save!          flash[:notice] = 'Snow Plow Registration was updated.'          redirect_to(@snowplow_registration)        end      else        render :action => :edit      end

So in that case, I prefer a sprinkle of your code with mine. Well, to be honest I probably lifted this off of one of the pages in my Agile Web Development with Rails book. So I won't take any credit for this. I wish I could! Cheers.

Michael Kahle wrote:

did-in-fact keep this a transactional operation. Here is the code I am using now:

  def update     @snowplow_registration = SnowplowRegistration.find(params[:id])     @customer = @snowplow_registration.customer

    @snowplow_registration.attributes = params[:snowplow_registration]     @customer.attributes = params[:customer]

    SnowplowRegistration.transaction do       @snowplow_registration.update_attributes!(params[:snowplow_registration])       @customer.update_attributes!(params[:customer])       flash[:notice] = 'Snow Plow Registration was successfully updated.'       redirect_to(@snowplow_registration)     end

  rescue ActiveRecord::RecordInvalid => e     @customer.valid?     render :action => "edit"   end

Michael, you no longer have to use update_attributes!, because this is the same as attributes= followed by save!

One little improvement: Use save_without_validation! to prevent the validations from being run twice.

Another good thought, however what I am using now doesn't have the same duplication as your suggested code does. Unless of course when I call the "attributes" method it runs a check then as well. I'm not sure that is the case. I'll have to think about that.

It's the validation code that's run twice, once at valid? and once at save!, which you'll see if you put a logger.debug call in say a Customer.validate method.

This usually doesn't cause problems, only unnecessary extra CPU load.

Michael Kahle wrote:

Ooo. Another thought. I noticed that you are doing a transaction without using the "rescue" catch. If something else goes wrong, besides validation, you will never know what happened! :slight_smile:

Unless you have a pre-save model callback preventing the save for a specific reason by returning false, that you wish to signal to the user, such exceptions are better handled by an app-wide catcher.

Mark Reginald James wrote:

Michael Kahle wrote: Another good thought, however what I am using now doesn't have the same duplication as your suggested code does. Unless of course when I call the "attributes" method it runs a check then as well. I'm not sure that is the case. I'll have to think about that.

It's the validation code that's run twice, once at valid? and once at save!, which you'll see if you put a logger.debug call in say a Customer.validate method.

This usually doesn't cause problems, only unnecessary extra CPU load.

Ok. I'm confused. If I step through my code line by line it would seem to me that it does the following:

1.) Creates a new object to dump stuff into. 2.) Dumps everything from the form (params) into these new objects. 3.) Begins the transaction. 4.) First updates @snowplow_registration, the ! at the end of the update method forces a check against the model for validation and returns any errors that come from the update. If it doesn't succeed, then it is caught by the rescue case and then checks the @customer object for errors.

Does this sound right? It sounds like I might be missing something fundamental here.

Thanks for looking. :slight_smile:

Michael Kahle wrote:

Mark Reginald James wrote:

Michael Kahle wrote: Ooo. Another thought. I noticed that you are doing a transaction without using the "rescue" catch. If something else goes wrong, besides validation, you will never know what happened! :slight_smile:

Unless you have a pre-save model callback preventing the save for a specific reason by returning false, that you wish to signal to the user, such exceptions are better handled by an app-wide catcher.

I'm really not sure what a pre-save model callback is. This is rapidly getting over my head! :slight_smile:

If a model before_validation, after_validation, or before_save method or block returns false, a valid record won't be saved, but you only get an exception if the saving method ended with an exclamation mark.

Are you saying that anything caught by "rescue" is something that you wouldn't want to pass back to the user? Is this because, let's say, in the event of a database failure it would better to send this to a log file or something similar? Is the idea behind what you are saying that we should only be passing validation errors back to the user?

While you can use rescue sections in individual actions to specially handle and inform users about action-specific errors, to prevent duplication it's usually best to have other exceptions handled by a single method in application.rb that informs both the user and the developer than an unexpected error has occurred. e.g. See the exception_notification plugin.

Michael Kahle wrote:

Ok. I'm confused. If I step through my code line by line it would seem to me that it does the following:

1.) Creates a new object to dump stuff into. 2.) Dumps everything from the form (params) into these new objects. 3.) Begins the transaction. 4.) First updates @snowplow_registration, the ! at the end of the update method forces a check against the model for validation and returns any errors that come from the update. If it doesn't succeed, then it is caught by the rescue case and then checks the @customer object for errors.

The "!" just causes an exception to be raised if the record is invalid, or if saving was prevented by a callback.

Here you've already checked that both the records are valid, so you just want to ensure that any other problem causes the propagation of an app-wide exception (which will also rollback the transaction). Also, you have already updated the records from the user-form using attributes=.

Therefore you want to use save_without_validation! rather than update_attributes!, save!, or save.

Mark Reginald James wrote:

Michael Kahle wrote:

errors.

The "!" just causes an exception to be raised if the record is invalid, or if saving was prevented by a callback.

Ok.

Here you've already checked that both the records are valid, so you just want to ensure that any other problem causes the propagation of an app-wide exception (which will also rollback the transaction). Also, you have already updated the records from the user-form using attributes=.

Correct. When you say, "Here you've already checked that both the records are valid...", you must be referencing the way you coded it. I don't think I'm checking it any time before I run the "update.bla.bla!" method. See my other post. I think I do not understand when Rails does it's checking automatically.

Therefore you want to use save_without_validation! rather than update_attributes!, save!, or save.

Sure. If I can get it straightened out, when Rails is doing its non-explicit checking; via - valid?, .save!, update.bla.bla! or otherwise, I am sure you are correct here and would see a (perhaps only minor, but still better!) performance upgrade.

Michael Kahle wrote:

Correct. When you say, "Here you've already checked that both the records are valid...", you must be referencing the way you coded it. I don't think I'm checking it any time before I run the "update.bla.bla!" method. See my other post. I think I do not understand when Rails does it's checking automatically.

Yes, it's possible to eliminate the explicit validity checks and jump straight into the transaction, allowing the validations to happen automatically during calls to save! or update_attributes!.

The disadvantage of this is that if the second object saved is invalid, the save of the first object has to be rolled-back, which is significantly slower than checking the validity of both before hitting the database. You also have to add that rescue block to ensure that the validity of the second object is checked for purposes of form error display.

Mark Reginald James wrote: Yes, it's possible to eliminate the explicit validity checks and jump straight into the transaction, allowing the validations to happen automatically during calls to save! or update_attributes!.

The disadvantage of this is that if the second object saved is invalid, the save of the first object has to be rolled-back, which is significantly slower than checking the validity of both before hitting the database. You also have to add that rescue block to ensure that the validity of the second object is checked for purposes of form error display.

Perfect! Thanks so much for your patience and very helpful answer to my problem.

Michael