Returning changed fields when transaction fails


#1

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.


#2

Michael K. 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.


Rails Wheels - Find Plugins, List & Sell Plugins -
http://railswheels.com


#3

Mark Reginald J. wrote:

Michael K. 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


#4

Michael K. 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.


Rails Wheels - Find Plugins, List & Sell Plugins -
http://railswheels.com


#5

Michael K. wrote:

Mark Reginald J. wrote:

Michael K. 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.


#6

Michael K. wrote:

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.


Rails Wheels - Find Plugins, List & Sell Plugins -
http://railswheels.com


#7

Michael K. 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.


Rails Wheels - Find Plugins, List & Sell Plugins -
http://railswheels.com


#8

Mark Reginald J. wrote:

Michael K. 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.


#9

Mark Reginald J. wrote:

Michael K. 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:

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?

This would make sense to me. I’m just far too green with Ruby at this
point to even think about that.

Thanks again.


#10

Michael K. wrote:

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.


Rails Wheels - Find Plugins, List & Sell Plugins -
http://railswheels.com


#11

Mark Reginald J. wrote:

Michael K. 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:


#12

Mark Reginald J. wrote:

Michael K. wrote:
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.

I understand. What I don’t understand is: when is the model
automatically checked? Anytime you submit the form back to the
controller?

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.

Nice advice. I’ll check out that plugin in the next few days. I need
to get this iteration complete. :slight_smile:


#13

Mark Reginald J. wrote:

Michael K. 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.


#14

Michael K. wrote:

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.


Rails Wheels - Find Plugins, List & Sell Plugins -
http://railswheels.com


#15

Mark Reginald J. 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


#16

Michael K. 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.


Rails Wheels - Find Plugins, List & Sell Plugins -
http://railswheels.com