Custom validation Rails for boolean attributes - ruby-on-rails

I need to add custom validation to my AR model: there is Order model with 'approved' attribute - this attribute cannot be approved twice. It's boolean attribute. I don't understand how can I check if this attribute has been approved already.
validate :cannot_be_approved_twice
def cannot_be_approved_twice
errors[:base] << ERROR_MSG if ...
end
How can I check it? Thanks!

If it is approved already then the value of that field would be something and not nil so you can do it like this:
def cannot_be_approved_twice
errors[:base] << ERROR_MSG unless approved.nil?
end
And if suppose it would have true value if it is approved then you can do:
def cannot_be_approved_twice
errors[:base] << ERROR_MSG if approved
end
Hope this helps.

I'd prefer attaching the error to the approved attribute, so the error would be something like this
validate :prevent_double_approval, if: :some_check
def prevent_double_approval
errors[:approved] << "can't be approved more than once" if approved?
end
This some_check method should be true when you detect that the user is trying to change the value, then the inner check if approved? checks if the value is already true before that second approval.

You can do it by checking previous value in your validation. Something like this:
def cannot_be_approved_twice
errors[:approved] << "can't be approved more than once" unless self.approved_was.nil?
end
The "_was" postfix gives the value before the update. So, if it was not nil then someone has set it to true or false (meaning approved or disapproved).
Update
I read through your comments and realized that what when order is created it is set to false (initially, I thought that it is set to nil, so you could approve or disapprove it). Thus, if the field initially set to false, then the method above becomes:
def cannot_be_approved_twice
errors[:approved] << "can't be approved more than once" if self.approved_was # if approved field was true (approved)
end
One more thing, with the way you are doing order update (order.update!(), with the bung), and your order has been approved then your application will fail with validation error because bung(!) explicitly tells application to fail in case of validation errors. You probably want to use just the "update" method and not "update!".

Related

Before save validation not working properly

I have a concern that checks addresses and zip codes with the intention of returning an error if the zip code does not match the state that is inputed. I also don't want the zip code to save unless the problem gets fixed.
The problem that I am having is that it appears that the if I submit the form, the error message in create pops up and I am not able to go through to the next page, but then somehow the default zip code is still saved. This only happens on edit. The validations are working on new.
I don't know if I need to share my controller, if I do let me know and I certainly will.
In my model I just have a
include StateMatchesZipCodeConcern
before_save :verify_zip_matches_state
Here is my concern
module StateMatchesZipCodeConcern
extend ActiveSupport::Concern
def verify_zip_matches_state
return unless zip.present? && state.present?
state_search_result = query_zip_code
unless state_search_result.nil?
return if state_search_result.upcase == state.upcase
return if validate_against_multi_state_zip_codes
end
errors[:base] << "Please verify the address you've submitted. The postal code #{zip.upcase} is not valid for the state of #{state.upcase}"
false
end
private
def query_zip_code
tries ||= 3
Geocoder.search(zip).map(&:state_code).keep_if { |x| Address::STATES.values.include?(x) }.first
rescue Geocoder::OverQueryLimitError, Timeout::Error
retry unless (tries -= 1).zero?
end
def validate_against_multi_state_zip_codes
::Address::MULTI_STATE_ZIP_CODES[zip].try(:include?, state)
end
end

rails prevent object creation in before_create callback

I want to check some attributes of the new record, and if certain condition is true, prevent the object from creation:
before_create :check_if_exists
def check_if_exists
if condition
#logic for not creating the object here
end
end
I am also open for better solutions!
I need this to prevent occasional repeated API calls.
before_create :check_if_exists
def check_if_exists
errors[:base] << "Add your validation message here"
return false if condition_fails
end
Better approach:
Instead of choosing callbacks, you should consider using validation here.Validation will definitely prevent object creation if condition fails. Hope it helps.
validate :save_object?
private:
def save_object?
unless condition_satisifed
errors[:attribute] << "Your validation message here"
return false
end
end
You can use a uniqueness validator too... in fact is a better approach as they are meant for those situations.
Another thing with the callback is that you have to be sure that it returns true (or a truthy value) if everything is fine, because if the callback returns false or nil, it will not be saved (and if your if condition evaluates to false and nothing else is run after that, as you have written as example, your method will return nil causing your record to not be saved)
The docs and The guide

Error not Triggering in Custom Model Validation

I have a simple validation in one of my Rails models, but it doesn't seem to be triggering an error like I want.
before_save :check_future_date
private
def check_future_date
puts "=============================================================="
puts self.article.date
puts Date.today
if self.article.date <= Date.today
puts "error!!!!!!!!!!!!!!!!!"
errors[:base] << "Sorry, you must post at least a day in advance"
end
end
I know that the logic is being triggered because in the console, I see:
==============================================================
2013-04-06
2013-04-29
error!!!!!!!!!!!!!!!!!
However, the record is still saved, and no error message is shown. I have also tried:
errors.add(:base, 'Sorry, you must post at least a day in advance')
You need to register the custom validation method, instead of performing a before_save.
Change
before_save :check_future_date
To
validate :check_future_date

Locking an attribute after it has a certain value

I have a model where if it is given a certain status, the status can never be changed again. I've tried to achieve this by putting in a before_save on the model to check what the status is and raise an exception if it is set to that certain status.
Problem is this -
def raise_if_exported
if self.exported?
raise Exception, "Can't change an exported invoice's status"
end
end
which works fine but when I initially set the status to exported by doing the following -
invoice.status = "Exported"
invoice.save
the exception is raised because the status is already set the exported on the model not the db (I think)
So is there a way to prevent that attribute from being changed once it has been set to "Exported"?
You can use an validator for your requirement
class Invoice < ActiveRecord::Base
validates_each :status do |record, attr, value|
if ( attr == :status and status_changed? and status_was == "Exported")
record.errors.add(:status, "you can't touch this")
end
end
end
Now
invoice.status= "Exported"
invoice.save # success
invoice.status= "New"
invoice.save # error
You can also use ActiveModel::Dirty to track the changes, instead of checking current status:
def raise_if_exported
if status_changed? && status_was == "Exported"
raise "Can't change an exported invoice's status"
end
end
Try this, only if you really want that exception to raise on save. If not, check it during the validation like #Trip suggested
See this page for more detail.
I'd go for a mix of #Trip and #Sikachu's answers:
validate :check_if_exported
def check_if_exported
if status_changed? && status_was.eql?("Exported")
errors.add(:status, " cannot be changed once exported.")
end
end
Invalidating the model is a better response than just throwing an error, unless you reeeally want to do that.
Try Rails built in validation in your model :
validate :check_if_exported
def check_if_exported
if self.exported?
errors.add(:exported_failure, "This has been exported already.")
end
end

Rails 3, I need to save the current object and create another

I have
recommendations has_many approvals
Basically a recommendation gets created with an approval. The person who can approve it, comes in and checks an approve box, and needs to enter an email address for the next person who needs to approve (email is an attribute of an approval).
The caveat is that if the current_user has a user_type = SMT, then no more approvals are required. Thats the last approval.
I am using the recommendation/:id/approval/:id/edit action. I think I just need a Class method for the Approval. Something like:
before_save :save_and_create
def save_and_create
Some code that saves the current approval, creates a new one and asks me for the next admins email address, and send that user an email requesting that they approve
end
Any help would be greatly appreciated
# old post
class Recommendation << AR
validate :approval_completed?
def approval_completed?
if self.approvals.last.user.type == "SMT"
return true
else
return false # or a number for needed approvals: self.approvals.count >= 5
end
end
end
# new solution
class Approval << AR
validate :approvals_completed?
def approvals_completed?
if self.recommendation.approvals.last.user.type == "SMT"
return true
else
return false # or a number for needed approvals: self.approvals.count >= 5
end
end
end
I finally figured this one out. I simply created a before_save callback and the following method:
def create_next_approval
next_approval = self.recommendation.approvals.build(:email => self.next_approver_email, :user_id => User.find_by_email(next_approver_email))
next_approval.save if next_approver_email.present? && recently_approved?
end
hope it helps anyone else in my shoes.

Resources