Do rails custom validations halt the save? - ruby-on-rails

Specifically http://guides.rubyonrails.org/active_record_validations.html#custom-methods
I feel like this isn't answered/documented properly. If you look at the example code -> all it does is call errors.add which according to this http://api.rubyonrails.org/classes/ActiveModel/Errors.html#method-i-add
doesn't do much other except add the message to the errors.
Upon testing, it does halt the save but I will leave this here for people to find.

An object is saved to the database only if it is valid. Using 'errors.add(:attribute_name, error_message)' associates an error with the object making it invalid, resulting in the object not being saved.

If an error exists on a field in an object (we'll call it "x"), then x.valid? is false. A failure of this validity check DOES prevent the object from saving. It will either return false if you call x.save (or create(x_params)), or raise an error if you call x.save! (or create!(x_params)). Raising errors is especially useful in the context of creating multiple record in a transaction, as that will break you out of the transaction and into your rescue block (assuming you allow for such).

Related

Rails Active Record - How to do validation which calls method rather than throws error

Active Record validations throw an error when they fail. What I have in a model is
validate_format_of :field_which_cannot_have_spaces, :with => /^[^\s]+$/, :message => "Some error message"
What I want instead, is for a string replacement to substitute spaces for underscores (snake_case).
The advantages of using validation for me, are that it runs every time the field is changed unless save(validate: false), and that I don't need to repeat the replacement in the create and update controller methods.
Front end javascript solutions won't help if the user hacks the form... a rails solution is needed!
It sounds like you want a callback rather than a validation. This can run each time your object is modified.
So, to remove spaces from your field before the object is saved you can do:
before_save :remove_spaces_from_x
def remove_spaces_from_x
self.field_which_cannot_have_spaces.gsub!("\s","_")
end
Note also that validation do not always raise an error when they fail. If you use save! or create! then an error is raised but if you use the equivalent save or create then no error is raised, false is returned and the object's errors are populated with details of the validation failure.
Co-worker just told me to do the following in the model:
def field_which_cannot_have_spaces=(input_from_form)
super(input_from_form.gsub("\s","_"))
end
This will change the value as it is set.
"Validations are for informing the client there is a problem, and shouldn't be doing something other than throwing an error."
Hope this helps someone else...

Rails before_save method fails to execute 'else' block

Here is my before_save method:
before_save :check_postal
def check_postal
first_three = self.postal_code[0..2]
first_three.downcase!
postal = Postal.find_by_postal_code(first_three)
if postal
self.zone_id = postal.zone_id
else
PostalError.create(postal_code: self.postal_code)
return false
end
end
Everything runs fine when self.zone_id = postal.zone_id but inside the else statement, my PostalError.create(postal_code: self.postal_code) doesn't save the record to the database..
I know it's got something to do with the return false statement, because when I remove it, it saves fine -- but then that defeats the purpose..
How can I get a new PostalError record to save while returning false to prevent the current object from saving..
You're exactly right: the problem is the before_save.
The entirety of the save process is wrapped in a transaction. If the save fails, whether it be because of a validation failure, an exception being rolled back or something else, the transaction is rolled back. This undoes the creation of your PostalError record.
Normally this is a good thing - it's so that incomplete saves don't leave detritus around
I can think of two ways to solve this. One is to not create the record there at all: use a after_rollback hook to execute it once the danger has passed.
The other way is to create that record using a different database connection (since transactions are a per connection thing). An easy way to do that is to use a different thread:
Thread.new { PostalError.create(...)}.join
I stuck the join on there so that this waits for the thread to complete rather than adding a degree of concurrency to your app that you might not expect.
i don't know, but i trying to guess the solve.
else
PostalError.create(postal_code: self.postal_code)
self.zone_id = postal.zone_id
end

How to debug silent database errors (such as "save") in Rails?

How can I get further information on errors that occur silently in Rails, such as #object.save?
Add bang so that an error is raised when validation fails.
#article.save!
# ActiveRecord::RecordInvalid: Validation failed: Title can't be blank...
Always use this method in preference to save if you don't expect validation to fail.
As per this post, something like the following works:
logger.debug #item.errors.full_messages
Sometimes AR silently fails for reasons other than validation errors. A couple of other things to check are:
AR callbacks that do not return true (eg before_save)
Invalid parent/ child records
#item.valid?, #item.errors.full_messages
#item.changes, #item.changed?
I have also included user456584's comment on checking for validation errors. And as Semyon said, #item.save! will at least raise an Exception even if it isn't particularly helpful.

Can an ActiveRecord before_save filter halt the save without halting the transaction?

Is there any way for a before_save filter to halt the entire save without halting the transaction? What I'm trying to do is have a "sample" version of my model that the user can interact with and save but the changes themselves are never actually saved. The following will halt the transaction and (naturally) return false when I call #model.update_attributes:
before_filter :ignore_changes_if_sample
def ignore_changes_if_sample
if self.sample?
return false
end
end
Thanks!
That's precisely what's happening here. If you look at your SQL, you should be seeing BEGIN and then COMMIT, without anything between them. The before_save is not halting the transaction; it's simply preventing the record from being saved by returning false.
To more generally answer your question, records that fail to persist do not halt transactions unless they also raise an exception. Exceptions trigger the ROLLBACK that prevents any part of the transaction from being committed. So even if you return false here, a larger, overarching transaction should continue just fine.
You can read more about transactions and how Rails uses them in the ActiveRecord::Transactions documentation.

When / why would I ever want to raise an exception on a method?

I've noticed that some methods in rails use the ! operator to raise an exception if invalid. For example, User.create!
Why and when would I want to use something like this?
Thanks.
I could want exceptions for various reasons
I might want to make sure that the method succeeds but I don't want to do any actual error handling here. But I might be fine with the request blowing up (e.g producing an HTTP 500) if something fails
I might want to have some error handling elsewhere, e.g some way up in the call chain. Generally, it's way more verbose and nasty to pull some error state with you a long way towards error handling. Structured error handling, i.e., begin ... rescue ... end make that clearer.
Exceptions can have additional data attached (e.g. an error message)
If you have different error types, it often much clearer to represent those with different exception classes instead of some magic id values (as is common in C)
There are good reasons to not use exceptions but status flags when the error states are few in number and are fully handled directly after the call. But every technique has its place and time.
And btw, the save method internally calls save!, handles the potential exception and returns false if there is any. See the code for the implementation.
ActiveRecord will roll back a transaction if an exception is thrown while a transaction is active.
So the methods that throw exceptions are nice to ensure the database doesn't commit a transaction when an exceptional condition occurs. When you can "handle" the problem yourself -- or, if it isn't actually a problem -- then you can use the variant without the !, check the return value for error conditions, and handle them yourself.
For something specific like User.create:
You might use the User.create method to determine if a user-supplied username is not yet picked and provide a prompt for the user to select another name, if it is already in use.
You might use the User.create! method when finally submitting the request, so that your integrity checks can fail the create and rollback the transaction in case the user attempts bypassing your friendly name check.

Resources