Using unless in rails uniqueness validation - ruby-on-rails

I am just starting out in Rails, and trying to develop a simple application. I need to validate three values submitted to the application - each must meet the same validation criteria.
The validation is pretty simple:
Value is valid if unqiue, null or equal to "p" or "d".
The following gets me halfway there:
validates_uniqueness_of :value1, :value2, :value3, :allow_nil => true
I think I can use :unless to check whether the value is either "p" or "d", however I can't figure out how.
I guess I am trying to combine validates_uniqueness_of with validates_inclusion_of.
Any suggestions?

There are a few ways to do that. I'd probably go with something like this:
validates_uniqueness_of :wallport_id, :allow_nil => true, :unless Proc.new { |u| u.wallport_id == 'p' or u.wallport_id == 'd' }
You could also break the extra logic out in to its own method, like this:
validates_uniqueness_of :wallport_id, :unless :has_nonunique_wallport_id
NONUNIQUE_WALLPORT_IDS = ['p', 'd', nil]
def has_nonunique_wallport_id
NONUNIQUE_WALLPORT_IDS.include? wallport_id
end
or push the whole validation in to its own method, like this:
validate :has_valid_wallport_id
def has_valid_wallport_id
# check wallport id and return either true or false
end
The best approach probably depends on which you find most readable, which functionality you might want to expose to other parts of your code (eg, does anybody else need to know about valid non-unique wallport ids?), and which things you expect might change (eg, will you add more valid non-unique ids later?). There are also a few different ways of doing the actual check, which again just comes down to what you find most readable and how you think the system might evolve.

Related

Rails validates :username, exclusion

validates :username, exclusion: { in: %w(about account root etc..)
I am using the above to disallow users from using reserved usernames but they are still able to use them between underscores (I do allow underscores in usernames)
Is there anyway I can make rails validate the reserved username even if it is before or after an underscore?
Thanks
You could create a method to do your validations for you and use plain old ruby in that. As you can see in the docs here
This would look something like this for you:
validate :my_validation_method
def my_validation_method
errors.add(:username, :exclusion) if some_condition
end
What this does is say that the model needs to be validated with the my_validation_method as well as all your normal other validations. You then manually add the field that is in error (in your case :username) to the errors of the model, thus it fails validation.
Also note the validate rather than validates.
Your other question is basically how to check whether an entered value includes some words. You could do this like so:
def my_validation_method
forbid = %w(luke darth artoo fry bender)
errors.add(:username, :exclusion) if forbid.find { |w| username.include?(w) }
end
Here I added a condition to the adding of the error where we loop through each word in the forbidden list and check if username includes this word. Note that "blablaluke" would fail too! So it is not completely what you'd want. But you can play around with this yourself of course.
The level of normalization you do (e.g. stripping away other characters) could give you more control, like preventing ad_min, etc.
Update:
You can strip away characters like so:
username.tr('-_+$^&', '')
You can add whatever you want to strip away to that first string in tr.
according to the rails docs this way you can only check if a value is in a set of given values.
This helper validates that the attributes' values are not included in a given set.
i would just write a custom validation method - you can do whatever you want in there.
I'd probably use a Regex. Like this one:
validates_format_of :username, :with => /\A(([ _]*)(?!(about|admin|root|etc)[ _])[^ _]+)+([ _]+|\z)\z/

Rails merge duplicates if value is matching or nil

I'm trying to perform a clean up of some data.
I have details in various forms with various duplicates.
models/object.rb
attr_accessible :name, :email, :assoc_id
I want to merge duplicates where the name is matching and the email is either matching or nil, and the assoc_id is either matching or nil.
Not sure how I write the query to bring back groups of objects that are either matching or nil..
i.e.
grouped_objects = Object.group_by{|o| [o.name]}
brings me grouped just on the name
grouped_objects = Object.group_by{|o| [o.name, o.email]}
brings me grouped on name and email.
the issue is that many of the objects have missing data.
Just want a quick and dirty so that, in the absence of other information, I'll merge the records together.
However, if there's someone with a different email, or a different assoc_id I won't merge that. Appreciate that there'll be some false records, but what we'll end up with will be an improvement
How do I write that activerecord query?
grouped_objects = Object.group_by{|o| [o.name, o.email || o.email == nil]}
Hope that makes sense,
I think a better way is too make your model intolerant with duplication. You can prevent duplication directly in the model. So when your controller try to create an object, it checks before if it doesn't exist by some element you decide.
So if you want your object be unique by some element, better do something like that (assuming you want the uniqueness from name and email field) in MyModel.rb :
class MyModel < ActiveRecord::Base
attr_accessible :name, :email, :assoc_id
validates_uniqueness_of :name
validates_uniqueness_of :email, :allow_nil => true # or :allow_blank => true
# Your code...
end
You can also use :case_sensitive => false if you don't want upper case be differenciated from lower case.
Hope this is what you are looking for !

Rails validate parameter combinations

I have a search form with many fields. Each field is a parameter for searching a very large database. These parameters are passed to a search object. Some combinations of parameters are valid, others are not (they put too heavy a load on the system). What is the best way to write validations for combinations of parameters? For example: You should be able to search by name and one other field, but not name alone. In some cases if you enter a value in one field, you cannot enter a value in others
Currently I have something like this in my search class.
SEARCHABLE_ATTRIBUTES = [:name, :city, :state, :phone, :invoice_number, :payment_amount, :payment_date, :current_balance]
validate: valid_combinations
def valid_combinations
unless name.present? && (SEARCHABLE_ATTRIBUTES - [:name, :invoice_number]).select{|sa| send(sa).present?}.any?
errors.add(:name, "can't be given alone.")
end
if name.present? && invoice_number.present?
errors.add(:invoice_number, "can't be searched with name")
end
end
My valid search param restrictions get more complex than this, but this is just an example. Is there a better way to do this? I'd like to avoid one big valid_combinations method.
You can pass a condition to the validation and it will run only if that condition returns true.
So you could create separated validation methods and use them like this:
validate :at_least_another_column, if: Proc.new{|record| record.name }
Or, if you create a condition method named name_present you could code it like this:
validate :at_least_another_column, if: :name_present
To substitute your second condition you could use:absence and :message options. Looking like this:
validates :invoice_number, absence: true, if: :name_present , message: "can't be searched with name"
As you can see the code becomes much cleaner and understandable when using separated validations. But depending on how complex your conditions may be, it could be easier to create a giant validator method
There's a lot more about validations here

Possible bug in rails conditional validation?

I have a form that allow me to edit an entry. I have a secondary set of attributes that must all be here if one of them have a value. I use conditional validates to do so :
validates_presence_of :raison_sociale,:nom_contact,
:prenom_contact,:telephone,
if: (:nom_contact?||:raison_sociale?||
:prenom_contact? || :telephone?)
But the strange thing is, those 4 fields are not evaluated the same way! If i remove nom_contact, it save. But if i remove it and telephone, it fails.
What i observed is that it was ignoring a blank field if it was the first one in the if condition! As soon as i put prenom contact as first condition i cannot save without nom_contact, but now it's prenon_contact that is ignored!
Why does my conditions behave strangly and what can i do?
What ca i do to avoid that?
You could put the if validation in a proc.
"Symbol-only" conditional validation expects a symbol for the name of a method that will be called.
You're including conditionals, which AFAIK won't work without being in a proc.
You can do some combinations by using an array, as per the docs:
class Computer < ActiveRecord::Base
validates :mouse, presence: true,
if: ["market.retail?", :desktop?]
unless: Proc.new { |c| c.trackpad.present? }
end
But those are and, not or.
I'd probably wrap it up in a method anyway; IMO it's a bit long for an in-line block, but that's more a matter of opinion.

Validates uniqueness of :link

I have a url field named link in my model with the following validation
validates_uniqueness_of :link, :case_sensitive => false
When I put "http://stackoverflow.com", it goes well.
Now when I put "https://stackoverflow.com/" (with the trailing slach), this is also accepted as unique.
But I want it to be invalid though there is "/" at the last?
I'd suggest that you normalize your URLs (add/strip trailing slash, etc. see http://en.wikipedia.org/wiki/URL_normalization) before storing them in the DB and even before validation.
validates_uniqueness_of :link, :case_sensitive => false
before_validation :normalize_urls
def normalize_urls
self.link.strip!
self.link.gsub!(/\/$/,'')
end
This isn't quite what you were asking for but if you don't store normalized URLs, you'll have to query your DB for all possible variations during validation and that could quickly get expensive.
You could always do a custom validator (by using the validate method, for example).
It might look something like this:
class MyModel < ActiveRecord::Base
validate :link_is_unique
def link_is_unique
#Clean up the current link (removing trailing slashes, etc)
link_to_validate = self.link.strip.gsub(/\/$/,'')
# Get the current count of objects having this link
count = MyModel.count(:conditions => ['link = ?', link_to_validate])
# Add an error to the model if the count is not zero
errors.add_to_base("Link must be unique") unless count == 0
end
end
You could then add other logic to clean up the link (i.e. check for http://, www, etc.)
You can customize validations. See this railscast.

Resources