I have a model where there are two fields that can technically be null. The field names are :is_activated and :activated_at. :activated_at is only required if :is_activated is set to true. It does not need to be present if :is_activated is false. What's the appropriate way in Rails to set this validation directly into ActiveRecord?
You can use a Proc on the :activated_at validator.
validates :activated_at, :presence, if: Proc.new { |a| a.is_activated? }
Recommended Reading:
http://guides.rubyonrails.org/active_record_validations.html#using-a-proc-with-if-and-unless
http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html
Finally, you should consider renaming :is_activated to simply :activated. This is considered better practice in Rails. The is_ prefix is used in other languages for boolean attributes because their method names don't support a ? character. Ruby does, and Rails generates ___? methods on boolean attributes. Because of this it's better to just call the attribute :activated and check it with activated?.
Non-Boolean Attributes.
If you're not using a Boolean attribute, like is_activated, and want to ensure that one attribute is present when another, non-Boolean attribute is present, you can simplify it:
validates :activated_at, presence: true, if: :activated_by?
activated_by? will return false when null and true otherwise.
You could do something like this:
validates :activated_at, presence: true, if: :is_activated?
def is_activated?
self.is_activated
end
This will only validate :activated_at if the method is_activated? returns true.
Related
I have this field :endpoint that I need to validate differently based on the presence of another attribute (:subscribed_events).
I need :endpoint to:
Be allowed to be blank in the absence of :subscribed_events
Have a valid format (http_url) if it's not nil
Be present and with a valid format (http_url) if :subscribed_events is present
I've written something like this:
validates :endpoint, http_url: { allow_blank: true }, presence: true, if: :subscribed_events?
But the http_url format is only validated in the presence of :subscribed_events, otherwise it'll allow anything such as "hello".
Can I make http_url validation non dependant of :subscribed_events and validate all of the conditions described above in a single line or do I have to write different validations?
You can write custom validators. Here is a starting point for you:
validate :endpoint_validator
def endpoint_validator
unless subscribed_events.blank?
if endpoint.blank?
errors.add(:enpoint, "can't be blank if subscribed_events is blank")
return false
end
end
# http_url validation here
# other code here
return true
end
Or, you can create one function for each field you want to custom validate. It's actually more organized.
class Person < ApplicationRecord
validates :name, uniqueness: { case_sensitive: false }
end
When a model has above definition, what exactly is happening behind the scenes?
My guess is there exists some validates method and a parameter is passed with symbol name. What is second parameter? a hash with a value that is hash?
First validation :name lets know that Person is not valid without a name attribute.
Second validation uniqueness
This helper validates that the attribute's value is unique right before the object gets saved. It does not create a uniqueness constraint in the database, so it may happen that two different database connections create two records with the same value for a column that you intend to be unique. To avoid that, you must create a unique index on that column in your database.
Third { case_sensitive: false }
There is also a case_sensitive option that you can use to define whether the uniqueness constraint will be case sensitive or not. This option defaults to true
Finally validates :name, uniqueness: { case_sensitive: false }
It means in Person model name attribute must be present with uniqueness not be case sensitive.
Simple-Form automatically detects whether there is a validates :xxx, presence: true validation, and displays the field as a required one (e.g. with an asterisk appended to the label).
validates :parent, presence: true
This results in:
Interestingly, as soon as I add a condition to this validation...
validates :parent, presence: true, if: -> { true }
...it doesn't do this no more:
Is this a bug or a feature?
This is the expected behavior, validations are only run when you save the object to the db, so you have no way of knowing whether the lambda returns true or not until then. Of course in your case it always returns true, but imagine you have a time constraint in your lambda or some other more complex condition, e.g.
...., -> { Time.zone.now > Date.new(2017, 1, 1) }
Maybe when you create the object for the form this returns false, but when the form is actually submitted and saved to the db enough time had passed for it to return true.
So there is no way for simple_form to know when the form is created whether the field is required or not.
You could call it a feature, since it's deliberate. If you look at the gem code (especially at calculate_required and conditional_validators?) you'll notice that if the presence validator has a condition, like if or unless, the field is no longer marked as required.
I solved like this for :terms being a checkbox element:
validates :terms, presence: true, acceptance: true
With this, it validates at form level for checking that 'term' checkbox is submitted only after it's checked. Hope it helps someone in the future.
Simple form validations don't work if you have conditions in your validations.
This is intentional as you can put whatever you want in an :if or :unless and calling this could cause unintended side effects.
source: https://github.com/heartcombo/simple_form/issues/322
I'm trying to implement to validations on a given model array-like field, using the Enumerize Gem. I want to:
validate that all the elements of a list belong to a given subset, using Enumerize
validate that the list is not empty (using validates :field, presence: true)
It seems that when I provide a list containing an empty string, the presence validator fails. See this example.
class MyModel
include ActiveModel::Model
extend Enumerize
enumerize :cities, in: %w(Boston London Berlin Paris), multiple: true
validates :cities, presence: true
end
# Does not behave as expected
a = MyModel.new(cities: [""])
a.cities.present? # => false
a.valid? # => true, while it should be false.
It seems to work in some other cases (for instance when you provide a non empty string that is not in the Enum). For instance
# Behaves as expected
a = MyModel.new(cities: ["Dublin"])
a.cities.present? # => false
a.valid? # => false
Is there a workaround available to be able to use both Enumerize validation and ActiveModel presence validation?
Thanks!
The enumerize gem is saving your multiple values as an string array. Something like this: "[\"Boston\"]". So, with an empty array you have: "[]". The presencevalidator uses blank? method to check if the value is present or not. "[]".blank? returns false obviously.
So, you can try some alternatives:
Option 1:
validates :cities, inclusion: { in: %w(Boston London Berlin Paris) }
Option 2:
Add a custom validator
validate :ensure_valid_cities
def ensure_valid_cities
errors.add(:cities, "invalid") unless cities.values.empty?
end
This is actually a bug that should be fixed in a later version of Enumerize (See https://github.com/brainspec/enumerize/pull/226).
In the meantime, you can use a custom validator as a workaround (See Leantraxx's answer).
I find myself doing this:
validates_numericality_of :mileage,:if => Proc.new {|car| car.mileage.present? }
Sometimes, the mileage field may not be sent, but when it is, I want it validated. I have no problem in having the Proc inside my code, but it's code which I'm kind of duplicating for all the other optional fields. Is there a shortcut like :if => present? ?
I'm using Rails 3.0.5.
Check out :allow_nil
:allow_nil - If set to true, skips
this validation if the attribute is
nil (default is false).
According to the API docs, empty strings are converted to nils before validation, so this should work in either case.
You could use a before_validation hook to convert empty strings to nil and then use the :allow_nil option to validates_numericality_of:
before_validation :clean_up_milage # This would replace '' with nil
validates_numericality_of :milage, :allow_nil => true