Rails Validate Field is True, If Present - ruby-on-rails

I've found plenty of posts around how to validate a field is present, if another condition is true, such as these:
Rails: How to validate format only if value is present?
Rails - Validation :if one condition is true
However, how do I do it the opposite way around?
My User has an attribute called terms_of_service.
How do I best write a validation that checks that the terms_of_service == true, if present?

You're looking for the acceptance validation.
You can either use it like this:
class Person < ApplicationRecord
validates :terms_of_service, acceptance: true
end
or with further options, like this:
class Person < ApplicationRecord
validates :terms_of_service, acceptance: { message: 'must be abided' }
end
[edit]
You can set the options you expect the field to be as well, as a single item or an array. So if you store the field inside a hidden attribute, you can check that it is still "accepted" however you describe accepted:
class Person < ApplicationRecord
validates :terms_of_service, acceptance: { accept: ['yes', 'TRUE'] }
end

I can't think of any default validation methods that could serve your purpose, but you can do with a custom validation. Also, a boolean can be either truthy or falsy, so you just need to check if its true or not. something like this should work.
validate :terms_of_service_value
def terms_of_service_value
if terms_of_service != true
errors.add(:terms_of_service, "Should be selected/True")
end
end

Related

How to handle dependant validations on Rails?

I've some Active Record validations on my model:
class Product < ApplicationRecord
validates :name, presence: true, length: { is: 10 }
end
That seems fine. It validates that the field name is not nil, "" and that it must have exactly 10 characters. Now, if I want to add a custom validation, I'd add the validate call:
class Product < ApplicationRecord
validates :name, presence: true, length: { is: 10 }
validate :name_must_start_with_abc
private
def name_must_start_with_abc
unless name.start_with?('abc')
self.errors['name'] << 'must start with "abc"'
end
end
end
The problem is: when the name field is nil, the presence_of validation will catch it, but won't stop it from validating using the custom method, name_must_start_with_abc, raising a NoMethodError, as name is nil.
To overcome that, I'd have to add a nil-check on the name_must_start_with_abc method.
def name_must_start_with_abc
return if name.nil?
unless name.start_with?('abc')
self.errors['name'] << 'must start with "abc"'
end
end
That's what I don't wan't to do, because if I add more "dependant" validations, I'd have to re-validate it on each custom validation method.
How to handle dependant validations on Rails? Is there a way to prevent a custom validation to be called if the other validations haven't passed?
I think there is no perfect solution unless you write all your validations as custom methods. Approach I use often:
class Product < ApplicationRecord
validates :name, presence: true, length: { is: 10 }
validate :name_custom_validator
private
def name_custom_validator
return if errors.include?(:name)
# validation code
end
end
This way you can add as many validations to :name and if any of them fails your custom validator won't execute. But the problem with this code is that your custom validation method must be last.
class Product < ApplicationRecord
validates :name, presence: true, length: { is: 10 }
validate :name_must_start_with_abc, unless: Proc.new { name.nil? }
private
def name_must_start_with_abc
unless name.start_with?('abc')
self.errors['name'] << 'must start with "abc"'
end
end
end
Please check allow_blank, :allow_nil and conditional validation as well for more options.

How do you declare an optional condition for an ActiveRecord belongs_to association?

Rails ActiveRecord provides an optional option for belongs_to. Consider the use case of allowing null for the foreign key and allowing the association to be null during object creation but requiring its presence during subsequent saves. For example, a new Member may have no initial Group, but any further updates of Member require a Group association.
Can the optional option value itself be conditional? For example,
class Member < ApplicationRecord
belongs_to :group, optional: -> { new_record? }
end
behaves the same as optional: true, and we can infer that the optional option parsing only checks for a truthy value.
Is a custom validator the pragmatic way to meet this use case?
It looks like providing a lambda to the optional option won't work (although I haven't tried it). I looked at the source code and this is how optional is used.
required = !reflection.options[:optional]
If required, Rails just adds a presence validation like this:
model.validates_presence_of reflection.name, message: :required
I believe you could go the custom route with something like this:
class Member < ApplicationRecord
belongs_to :group, optional: true
validates :group, presence: true, on: :update
end
You aren't describing a database constraint, you're describing business logic. Add a validator to enforce the condition:
validates :group, presence: true, on: :update
This only enforces the condition on update, not create.
I came into the same problem. My solution is to override belongs_to and add an unless option which takes a symbol for a predicate function. It seems like a good idea...
That way you can write belongs_to :group, unless: :new_record?
Here is my code
def self.belongs_to(name, scope = nil, options = {})
if scope.is_a?(Hash)
options = scope
scope = nil
end
if options.has_key? :unless
super name, scope, options.merge(optional: true).except(:unless)
validates name, presence: true, if: ->{ !self.send(options[:unless]) }
else
super
end
end

Validate attribute for multiple values

In my model, I would like to add a validation to check if my attribute presents both of its values (e.g., one of the record has its value ValueA and another record has its value ValueB-those being the only possibilities, and it needs to have at least one or both of them).
What is the best way to achieve this?
Try out this
class YourModel < ActiveRecord::Base
VALID_VALUES = ['Value1', 'Value2']
with_options presence: true do
validates :your_field, inclusion: { in: VALID_VALUES, allow_blank: true }
end
end

Partly invoke validation process on some Activerecord object attributes

I have a situation where User has_one :address and Address belongs_to :user.
I need to be able to validate the address object in these cases:
After a user has signed up, he has an option to partly fill in the address form. In this state I would like to validate for example validates :phone_number, :postal_code, numericality: true but the user can leave the field blank if he wants to.
When user is making a purchase he has to complete the address form. And all the fields have to be validated by validates presence: true + previous validations.
I understand that one approach would be to attach another parameter to the form (i.e.full_validation) and then add a custom validation method that would check for this parameter and then fully validate all attributes.
I was just wondering is there a more code efficient and easier way to do this.
So far I have only found ways to validate some attributes (seethis blog post) but I have not yet found suggestions on how to invoke part of the validation process for certain attributes.
Any help/suggestions will be appreciated :)
#app/models/user.rb
class User < ActiveRecord::Base
has_one :address, inverse_of: :user
end
#app/models/address.rb
class Address < ActiveRecord::Base
belongs_to :user, inverse_of: :address
validates :phone_number, :postal_code, numericality: true, if: ["phone_number.present?", "postal_code.present?"]
validates :x, :y, :z, presence: true, unless: "user.new_record?"
end
--
After a user has signed up
Use if to determine if the phone_number or postal_code are present.
This will only validate their numericality if they exist in the submitted data. Whether the User is new doesn't matter.
--
When user is making a purchase
To make a purchase, I presume a User has to have been created (otherwise he cannot purchase). I used the user.new_record? method to determine whether the user is a new record or not.
Ultimately, both my & #odaata's answers allude to the use of conditional evaluation (if / unless) to determine whether certain attributes / credentials warrant validation.
The docs cover the issue in depth; I included inverse_of because it gives you access to the associative objects (allowing you to call user.x in Address).
If you give more context on how you're managing the purchase flow, I'll be able to provide better conditional logic for it.
For your first use case, you can use the :allow_blank option on validates to allow the field to be blank, i.e. only validate the field if it is not blank?.
http://guides.rubyonrails.org/active_record_validations.html#allow-blank
For both use cases, you can tell Rails exactly when to fire the validations using the :if/:unless options. This is known as Conditional Validation:
http://guides.rubyonrails.org/active_record_validations.html#conditional-validation
For Address, you might try something like this:
class Address
belongs_to :user
validates :phone_number, :postal_code, numericality: true, allow_blank: true, if: new_user?
def new_user?
user && user.new_record?
end
end
This gives you an example for your first use case. As for the second, you'll want to use conditional validation on User to make sure an address is present when the person makes a purchase. How this is handled depends on your situation: You could set a flag on User or have that flag check some aspect of User, e.g. the presence of any purchases for a given user.
class User
has_one :address
has_many :purchases
validates :address, presence: true, if: has_purchases?
def has_purchases?
purchases.exists?
end
end

validate uniqueness of polymorphic association

I'm trying to implement a validation for a polymorphic association, where I only want it to trigger on a certain type. Which is user.
I'd want something like this:
validates :room_id, uniqueness: { scope: tokenable_id if tokenable type is User }
how do I go about doing this. The other type is Customer. Which I want to allow the opportunity to be several.
I think you can use Conditional Validation
Sometimes it will make sense to validate an object only when a given
predicate is satisfied. You can do that by using the :if and :unless
options, which can take a symbol, a string, a Proc or an Array. You
may use the :if option when you want to specify when the validation
should happen. If you want to specify when the validation should not
happen, then you may use the :unless option.
5.1 Using a Symbol with :if and :unless
You can associate the :if and :unless options with a symbol
corresponding to the name of a method that will get called right
before validation happens. This is the most commonly used option.
class Order < ActiveRecord::Base
validates :room_id, uniqueness: { scope: :tokenable_id }, if: :is_right_type?
# or maybe this will work
validates :room_id, uniqueness: { scope: :tokenable_id }, if: :user?
def is_right_type?
type == "user"
end
end

Resources