Calling a specific validator on a specific attribute - ruby-on-rails

I defined a custom EachValidator to see if an attribute has leading or trailing whitespace. I know you can add it to the model like so:
validates :name, whitespace: true
But in the controller I want to call just run just the whitespace validator for some form feedback.
I can run it like this:
Validators::WhitespaceValidator.new(attributes: :name).validate_each(obj, :name, obj.name)
Is there a shorter way to call the specific validator? Like you can do user.valid? but that runs all of the validations. I only want the whitespace validator on the name attribute.

Since you did not come here to be told that your idea is bad and people will hate it: here is an idea that you can play with: :on
https://guides.rubyonrails.org/active_record_validations.html#on
validates :name, whitespace: true, on: :preview
and then in the controller:
def something
#model.valid?(:preview)
end
If you want to run the validation also on createand update you can specify something like
on: [:create,:update,:preview]
(Thanks engineersmnky)
Also: I don't think that giving early feedback to users, before they actually save the record is a bad idea if properly implemented.

This feels like trying to solve a problem (leading/trailing whitespace) by creating a new problem (rejecting the object as invalid), and I agree with the comment about this being a brittle strategy. If the problem you want to solve here is whitespace, then solve that problem for the specific attributes where it matters before saving the object; check out
Rails before_validation strip whitespace best practices

Related

How to validate unique url with Rails and validates_url?

Use validates_url can validate url format.
Set validation in model:
validates :homepage, uniqueness: true, url: true
Can't validate these two cases as unique url:
https://stackoverflow.com
https://stackoverflow.com/
But they should be the same. If the first one is inserted into database, the second one should be validated and not allow to be inserted.
I didn't find a way to realize it using validates_url. Is it possible?
So the two validations, uniqueness and URL, happen separately, and there is nothing in the uniqueness check to handle the fact that those two URLs are essentially the same - instead, the string values are technically different, and thus it doesn't trip the uniqueness validation.
What you could do is look to tidy up your URL data before validation, with a before_validation callback in your model:
before_validation :process_url
def process_url
self.homepage = self.homepage.slice(0, self.homepage.length - 1) if self.homepage.present? && self.homepage.ends_with?("/")
end
This is called before the validations kick in, and will make sure that if the homepage attribute is present (even if you add a presence validation later if it becomes non-optional, remember this is running before validations), then any trailing / is removed.
Those two URL strings will then be the same after tidying up, and thus the second time around the validation will kick in and stop it from being saved.
Hope that helps!
I do use rubular.com to validate using regex. You can try this ^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\.*)?$

How can you limit ïnput to English characters in Rails?

I'm getting a lot of submissions to my publicly editable site in different languages, but would like to limit it to just English characters. Is there any simple way to do this? Should I just do a validation with a regex limiting characters, or are there any common issues with that method?
Note: The text will not contain HTML or any other markup. It should just be plain text, maybe with common characters like dashes, dots, etc.
validates :comment, format: { with: [a-zA-Z0-9\s]+, on: :create } - or other action
It sounds like your problem maybe spam, so there are better solutions you can use. If this is true, see the end of this answer for details.
I commented that depends on which languages you want to block. A dozen or more languages use a latin charset, and even more use a latin charset subset. English uses accents too, such as the word naïve. So I do not recommend you try and do this.
If you must, and you want to block none-latin characters, you can write a custom validator.
class LatinCharsetValidator < ActiveModel::EachValidator
# Regex taken from this answer http://stackoverflow.com/a/13671443/276959
LATIN_CHARSET_REGEX = /[\p{L}&&[^a-zA-Z]]/
def validate_each(object, attribute, value)
object.errors.add(attribute) if value =~ LATIN_CHARSET_REGEX
end
end
You can then call this validator in your model as such:
class Comment < ActiveRecord::Base
validates :comment, latin_charset: true
end
This assumes that your users are legitimately trying to comment. If not, you can use the regex to skip the comment creation without providing feedback.
I believe this is a bad idea, though. You can't control what people end up writing or pasting in the text area, and you might end up blocking legitimate comments. What if someone wants to include a foreign word while trying to explain it? It's better to politely ask your users to only comment in English.
If your problem is spam, however, you are better off implementing a honeypot or some other type of spam protection.

RoR EMail Validations--Multiple Regexes/formats

I am trying to use validates_format_of in my model file to validate an email address. However, I need to enter two regexes for two special cases. Basically, only a few top-level domain names are allowed.
However, it seems according to the source code for validates it only allows one :format key and one set of options as the hash value. Is there a way to use multiple regexes. I have tried the logical operators but it seems to accept only the first one. Also, using two validates method on the same field leads to nothing getting accepted as one violates the other condition
To explain in actual terms, say I want to only emails that are either gmail or yahoo and nothing else. How do I use regexes to represent both and nothing else? This is my gmail code and it works:
validates_format_of :email, :with => (/^([^#\s]+)#((?:gmail+.)+[a-z]{2,})$/i)
How do I change it to include another top level domain name?
validates_format_of :email,
:with => (/^([^#\s]+)#((gmail|yahoo|hotmail)\.+[a-z]{2,})$/i)
something like this?
[a-z] at the end won't capture something like '.com.au' or similar, is that okay?
Also,
. is for single character, you want \. for an actual period
otherwise 'gmailxcom' would be valid
To build upon the answer of #Neet324, you could use:
validates_format_of :email, :with => (/^([^#\s]+)#((\.?\w+)+)$/i)
to capture any email domains, including subdomains and country level top domains.

Validate an ActiveRecord model as if it has one hypothetical change

Lets say I have some validations that I only want to run if my record is in a specific state. This allows a draft record to be saved that is incomplete, and the rest of the content can be filled in later.
validates_presence_of :intro, :codename, :body,
if: lambda { |o| o.content_state == :review }
Now I want to know if this record's content can be considered complete, which will allow it to be moved to the review state. Validations provide a fantastic framework for applying constraints and requirements to model properties, and I want to leverage that.
So I have to take a draft content record, and then validate it as if it was in the review state, and if it comes up with any errors, the content is not yet complete.
But the only way that I have managed to do this is as follows:
def content_completable?
old_content_state = content_state
self.content_state = 'review'
return valid?
ensure
self.content_state = old_content_state
end
This feels pretty kludgy to me. It seems to work, however I'm powering these states with an actual sate machine, the docs of which say manually assigned a state name is not a good thing. But I have to because there may not be a transition back.
I don't actually want to change the model at this point, even if it is valid. I only want to know if the model would be valid if it were in a different state?
I was hoping ActiveRecords #valid? method would accept a hash attributes that would override the current values on the model, but it doesn't appear to work this way.
Is there a better way to do what I'm doing?
You could clone the record, modify the state and call valid? on the cloned record.
You can set a virtual attribute and test that first:
attr_accessor :new_content_state
validates_presence_of :intro, :codename, :body,
if: lambda { |o| (o.new_content_state || o.content_state) == :review }
def content_completable?
self.new_content_state = 'review'
return valid?
end
Naturally, you may still need to cleanup after the virtual state, but that depends on how long this model persists. It's also less intrusive since you only use this attribute for a limited purpose.
A completely different approach would be to avoid using AR validators in the first place. They aren't quite designed for this purpose, and although it seems elegant at first glance, the abstraction is leaking through...

Cannot skip validation in Rails 3?

I'm working on a project in Rails 3 where I need to create an empty record, save it to the database without validation (because it's empty), and then allow the users to edit this record in order to complete it, and validate from then on out.
Now I've run into a pretty basic problem: I can't seem to save a model without validating it under any circumstances.
I've tried the following in the console:
model = Model.new
model.save(false) # Returns RuntimeError: Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
model.save( :validate => false ) # Returns same error as above
model = Model.create
model.save(false) # Same runtime error
model.save( :validate => false ) # Same runtime error
I then tried changing all the validations in the model to :on => :update. Same error messages on any attempt to save.
So what am I missing here? How can I create an empty record and then let validation occur as the user edits it?
Thanks!
It is a bad practice to have invalid models saved by normal use cases. Use conditional validations instead:
validates_presence_of :title, :unless => :in_first_stage?
or if you have many:
with_options :unless => :in_first_stage? do
validates_presence_of :title
validates_presence_of :author
end
This way nothing stands in way to have nightly integrity tests, which checks all records for validity.
A valid use case for saving without validations would be for testing edge cases, e.g. to test that a database constraint is enforced.
*sigh...*
Found the problem... one of my after_validate method calls was adding information and resaving the model, hence the errors I was getting weren't from the console input, they were coming from the after_validate method which was saving again.
Thanks all.
For emergencies only
Assuming that you have considered this very carefully and are certain that this is a good idea, you can save without validation using:
my_model.save validate: false
There are almost no valid use cases for this, and it should be considered an emergency one off procedure. Your use case does not qualify.
Problems with invalid records
Having invalid records in the database leads to all manner of problems down the line. For example, you send an email to all users and update a 'last_contacted_at' field on your user model. Your invalid users will not be updated and will descend into an email spiral of death.
Conditional validation
As other posters have pointed out, conditional validation will solve most issues for which you might otherwise have used validate: false.
Instead of placing an invalid model in the database, store the partially completed model (created with Model.new) in a session. Only save it to the database when it is completely valid.

Resources