One of my models contains a virtual attribute called things. This attribute is an array, and I'd like every element within that array to be validated against a set of rules. Here is my current attempt at validation:
validates :things, presence: true, length: { minimum: 2, maximum: 255 }
The problem with this code is that it validates the entire array itself, not each individual element within the array. I know I can write a custom validator, but is there any way to use the existing validation options to run these validations against every element in the array? The other topics I found on this are for older versions of Rails, so I'm not sure if Rails 4 has something new that could help with this.
Thanks in advance for any help!
Have you tried a custom validation? You can create a custom validation inside your model.
Something like this, where "things" is the name of the attribute that you want to validate:
model.rb
validate :check_each_thing
def check_each_thing
things.each do |thing|
if thing.present?
if thing.size < 2 || thing.size > 255
errors.add(:things, 'It should be longer than 2 and shorter than 255.')
end
else
errors.add(:things, 'It should be present.')
end
end
end
Hope it helps :)
Related
I hope the title is not too unclear.
I am making arails app and I have a question about rails validation. Consider this code in the User,rb model file:
validates :name,
presence: true,
length: { maximum: 50 },
uniqueness: { case_sensitive: false }
I am using the friendly_id gem to generate slugs for the users. I wont allow users to change their name. What I now need is to ensure that Names are unique in such a way that there will be no UUID's appended to slugs if two people have the same name converted in ascii approximation.
Current behaviour is:
User 1 signs up with a name and gets a slug like this:
name: "Jaiel" slug: "jaiel"
User 2 now does the same name but a bit different:
name: "Jàìèl" slug: "jaiel-6558c3f1-e6a1-4199-a53e-4ccc565657d4"
The problem here as you see I want such a uniqueness validation that User 2 would have been rejected because both names would generate the slug "jaiel" for their friendly_id's
I would appreciate your help on that matter
Thanks
Take a look into ActiveSupport::Inflector.transliterate:
ActiveSupport::Inflector.transliterate('Ærøskøbing')
#=> "AEroskobing"
Also, with this type of validation you might want to go with custom validation (alongside the one you already have):
class User
validate :unique_slug
private
def unique_slug
names = self.class.all.map(&:asci_name)
raise ActiveRecord::RecordInvalid.new(self) if names.include?(asci_name)
end
def asci_name
ActiveSupport::Inflector.transliterate(name)
end
end
Obviously, this is super inefficient to query whole table on each validation, but this is just to point you into one of the possible directions.
Another option would be going for a callback. Transliterating the name upon creation:
before_validation: :transliterate_name
def transliterate_name
self.name = ActiveSupport::Inflector.transliterate(name)
end
It will first transliterate the name, then validate uniqueness of already transliterated name with the validation you have. Looks like a solution, definitely not as heavy as initial one.
I have a conditional form field (which is shown by clicking a checkbox) which I want to validate with a custom method.
validates :number, length: { maximum: 20 }, if: :checksum_is_valid?
def checksum_is_valid?
if !Luhn.valid?(number)
errors.add(number, "is not valid.")
end
end
is my attempt. This technically works fine but the error is also shown, even if I don't enter any number at all (because the field is not mandatory). Any idea how I can check with a custom method, but only if the user provides any number at all.
Thanks
You could use validate instead of validates for custom validators and then move the check if there is a number present into the validator method:
validate :checksum_valid?
private
def checksum_valid?
if number.present?
errors.add(:number, "is not valid.") unless number_valid?
end
end
def number_valid?
number.length < 20 && Luhn.valid?(number)
end
You're mixing two things in your code, so let me help you understanding better what is happening.
1). conditional validation:
validates :number, length: { maximum: 20 }, if: :checksum_is_valid?
By this, Rails expects the model to implement method checksum_is_valid? which returns boolean value, based on which it will perform this validation. Something like would work:
def checksum_is_valid?
Luhn.valid?(number)
end
In case this method returns true, Rails would perform the validation of :number is not more than 20.
2). custom validation:
When using this custom method, you can build your own validation logic and error. So, if you have your method:
def checksum_is_valid?
if !Luhn.valid?(number)
errors.add(number, "is not valid.")
end
end
You should configure your validator with:
validate :checksum_valid?
Unfortunately, from your code is not clear what you would like to achieve (validate the number not to be more than 20, or perform the custom validator), but I hope what I've pointed will help you make the proper decision how to take that further.
Hope that helps!
So I am having an issue where length errors are rails exception because the model doesn't have a length validation on it.
I understand I can put a length validator on my string by using
validates :blah, length: { maximum: 255 }
However most of my strings are using the default size of 255, so I would have to repeat this validation in a lot of places.
Is there a DRY way to put a default validator on all strings to validate to the default database length of 255?
The gem schema_validations (https://github.com/SchemaPlus/schema_validations) does what you want.
Automatically creates validations basing on the database schema.
It inspect the database and automatically creates validations basing on the schema. After installing it your model is as simple as it can be.
class User < ActiveRecord::Base
end
Validations are there but they are created by schema_validations under the hood.
This probably isn't exactly the answer you are looking for but if you add multiple fields to the validator then you aren't really repeating the validator and seems fairly DRY to me.
validates :foo, :bar, :baz, length: { maximum: 255 }
class ModelName < ActiveRecord::Base
validates :name, length: { maximum: 255 }
end
Then use it by typing
ModelName.validators
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.
Is it possible to skip validations with a dynamic find/create by method?
For example with regular save I can do something like:
p = Post.new
p.title = nil
p.body = nil
p.save(:validate => false)
Would love to do the same with find_or_create_by_title.
It dosnt look possible with the code in Rails right now however you may have better luck being a little more verbose in how you write the code. You can use find_or_initialize_by_ which creates a new object but does not save it. You can then call save with your custom options, also in the documentation they have a neat demonstration that is hard to find so I will include it below:
# No 'Winter' tag exists
winter = Tag.find_or_initialize_by_name("Winter")
winter.new_record? # true
Good luck and let me know if you need more pointers in the right direction.
For some cases, find_or_initialize_by_ will not be useful and need to skip validations with find_or_create_by.
For this, you can use below alternative flow and method of ROR:
Update your model like this:
class Post < ActiveRecord::Base
attr_accessor :skip_validation
belongs_to :user
validates_presence_of :title, unless: :skip_validation
end
You can use it now like this:
Post.where(user_id: self.id).first_or_create!(skip_validation: true)
I have used first_or_create instead of find_or_create_by here. You can pass more column names and values with this, and your validation will not be worked with this.
You can continue without any changes for strong parameters end and no need to permit this 'skip_validation' so it will work with validations while adding entries.
Using this, you can use it with and without validations by passing a parameter.
Currently skipping validation DOES work with find_or_create_by.
For example, running:
Contact.find_or_create_by(email: "hello#corsego.com).save(validate: false)
will skip a validation like:
validates :name, :email, presence: true, uniqueness: true