Rails validate parameter combinations - ruby-on-rails

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

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 !

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.

Rails object.valid? with arguments

Is it possible to pass :symbols to the valid? method so that I can define if the object is valid up to a certain point?
Eg. if I have an object Person and want to call Person.valid?(:basic_info) and it will return true only if a certain subset of fields (say name & gender) are present?
I saw something that I thought might be of use but cannot get it working, it's conditional validations http://guides.rubyonrails.org/active_record_validations_callbacks.html#conditional-validation , in particular grouping conditional validations, but I couldn't get it working...
Can anyone help me out here please...
I don't think there already present like this however you can write a method on your own like following
def is_valid_field?(field)
self.valid?
self.errors[field].blank?
end
and then just person.is_valid_field?(:basic_info)
To validate basic_info you'll have to define a custom validator:
class Person < ActiveRecord::Base
validate :basic_info_present
def basic_info_present
if name.blank? || gender.blank?
errors.add(:basic_info, "can't be in blank")
end
end
end
If you then want to see if there are errors on the specific field, you can use #Salil's approach.
Note however that since there is no actual attribute called basic_info in your model, the validation errors here will not come up in forms, etc. (although they will be in the errors hash). That may or may not be what you want.
I got this to work using conditional validations, so now i can use .valid?(:basic) say for when i only want to check that the person has a name with the call...
validates_presence_of :name, :when => [:basic]
Documentation here: http://apidock.com/rails/ActiveRecord/Validations/valid%3F
This way I can have the object return true when calling .valid? even when it doesn't have a name, good times...

Using unless in rails uniqueness validation

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.

Resources