Rails Validation on presence of two or more entities - ruby-on-rails

I've got two columns by name,
product_available_count (integer) and product_available_on (date).
I need to perform a model level validation on these columns.
The validation should check that if product_required is true then either of fields should be populated.
When a Product Manager fill in the catalogue, we need to perform a model level validation that checks that he should fill in either of the fields.
Suggest me any elegant way of writing a custom validation for my requirement.
I've tried this approach
validates :product_available_count_or_product_available_on if product_required?
def product_available_count_or_product_available_on
//logic ???
end
Is Custom validation the only way forward to my requirement. Can I use Proc or any other approach to write a better code.

I think Custom validation is best approach for this kind of problem
validate :product_available_count_or_product_available_on if product_required?
def product_available_count_or_product_available_on
if [product_available_count, product_available_on].compact.blank.size == 0
errors[:base] << ("Please select alteast one.")
end
end
but if you really donot want to write custom validation then try this
validates :product_available_count, :presence => { :if => product_required? && product_available_on.blank? }
validates :product_available_on, :presence => { :if => product_required? && product_available_count.blank? }

Related

How can I implement dynamic validation in activerecord

What is the best way to adjust your validation of a model based on a parameter or action? Say I am entering a lead into a system, so all that is required is basic contact info. But then later I may enter a new contract for that user at which point I need more detailed information. So I may need phone number when just entering a lead but once they are entering into a contract I might need their birthdate and alternate phone number.
I considered using a state machine but the user could actually enter into two contracts at the same time so state doesn't really work for this scenario.
I also considered storing the extra info with the contract but since the user can have more than one contract, it needs to live with the user so it is not redundant.
So basically when saving a contract, it would tell me that the attached user is invalid if said user doesn't have the extra fields.
Check out conditional validations:
class Person
validates_presence_of :given_name, family_name
validates_presence_of :phone_number, :email_address, :if => :registered
with_options :if => :registered do |person|
# validations in this block are scoped to a registered user
person.validates_presence_of :dob
end
end
The :if option can take:
a symbol that corresponds to a method on the class that evaluates to true or false
a proc or lambda that returns a value that evaluates to true or false
a string containing ruby code (god knows why you'd want to do that)
You also have access to an :unless option which works in a similar fashion.
You can also encapsulate the logic to determine the current state of the user and use that to determine what validation steps you can take:
class Person
validates_presence_of :email_address, :if => ->(p) { p.state == :pending_confirmation }
# I actually prefer validations in this format
validate do # stricter validations when user is confirming registration
if confirming_membership && (given_name.blank? || family_name.blank?
errors.add(:base, 'A full name is required')
end
end
def state
# your logic could be anything, this is just an example
if self.confirmed_email
:registered
elsif confirming_membership
:pending_confirmation
else
:new_user
end
end
def confirming_membership
# some logic
end
end
You can use conditional validation for example:
validates_presence_of :phone, :if => Proc.new { |p| p.lead? }
In whatever action the lead posts to, you could just do this:
#object.save(validate: false)
Then, when they need to enter the contract, leave off that validate: false option to ensure that those validations run.
Also, see this post if you want to skip only certain validations.

Rails - Where to add validation code? (Controller or Model)

I'm new to Rails, and am following this tutorial
I creates a simple model called HighScores.
I would like to customize this so that I can add a validation method for the score. I know there are shortcuts like validates_ that we can use, but for the purpose of learning, I'd like to write a method that ensures the score is between a certain range.
Where should the validate method go? In models/high_score.rb or in controllers/high_scores_controllers.rb? Or maybe in `/helpers/high_scores_helper.rb?
Validation that the model has correct data should go in the model itself. This ensures that any future attempt to save the model's data will use this validation, regardless of the path taken.
models\high_score.rb
Also -- FWIW, the validates methods aren't short cuts, they are well tested code that you should embrace and use.
The validation should go in models.
Here is an example of a range validation:
validates :score, :numericality => { :greater_than => 0 }
validates :score, :numericality => { :less_than => 100 }

Validations that rely on associations being built in Rails

A Course has many Lessons, and they are chosen by the user with a JS drag-n-drop widget which is working fine.
Here's the relevant part of the params when I choose two lessons:
Parameters: {
"course_lessons_attributes"=>[
{"lesson_id"=>"43", "episode"=>"1"},
{"lesson_id"=>"44", "episode"=>"2"}
]
}
I want to perform some validations on the #course and it's new set of lessons, including how many there are, the sum of the lessons' prices and other stuff. Here's a sample:
Course Model
validate :contains_lessons
def contains_lessons
errors[:course] << 'must have at least one lesson' unless lessons.any?
end
My problem is that the associations between the course and the lessons are not yet built before the course is saved, and that's when I want to call upon them for my validations (using course.lessons).
What's the correct way to be performing custom validations that rely on associations?
Thanks.
looks like you don't need a custom validation here, consider using this one:
validates :lessons, :presence => true
or
validates :lessons, :presence => {:on => :create}
You can't access the course.lessons, but the course_lessons are there, so I ended up doing something like this in the validation method to get access to the array of lessons.
def custom validation
val_lessons = Lesson.find(course_lessons.map(&:lesson_id))
# ...
# check some things about the associated lessons and add errors
# ...
end
I'm still open to there being a better way to do this.

preventing rails validation based on previous validation

I have a model with 2 validations on the 'name' attribute. It goes something like this:
validates :name, :uniqueness => true
validate do
errors.add(:name, "is dumb") if name_is_dumb?
end
I don't want the 2nd validation to run if the first validation fails (the name is not unique).
What's the best and cleanest way to do this?
According to the documentation:
Callbacks are generally run in the
order they are defined, with the
exception of callbacks defined as
methods on the model, which are called
last.
So the following snippet should work:
validates :name, :uniqueness => true
validate do
errors.add(:name, "is dumb") unless errors[:name].nil?
end

Dynamic Model Validation

I want to create some validations for one of my models which contain location information(street, locality, postal_code, etc). I want to be able to change the validation rules based on which country is selected.
For example, the validation rules for postal_code will be different for the US & Canada. Furthermore, some countries don't have postal_codes so no validation rules would be needed.
How would I go about implementing something like this?
Put this in your model to run any custom logic for validation.
validate :location_should_be_valid
def location_should_be_valid
# run all your custom logic here
# if it isn't valid, add an error indicating why
if country == "Canada"
errors.add(:postal_code, "Invalid postal code for Canada") if postal_code.length != 7
end
end
Read more about this in the Rails Guides:
http://guides.rubyonrails.org/active_record_validations_callbacks.html#creating-custom-validation-methods
validates :postal_code, :presence => true, :if => :check_country
def check_country
["US", "Canada"].include?(self.country)
end

Resources