Here's what I expected to be a perfectly straightforward question, but I can't find a definitive answer in the Guides or elsewhere.
I have two attributes on an ActiveRecord. I want exactly one to be present and the other to be nil or a blank string.
How do I do the equivalent of :presence => false? I want to make sure the value is nil.
validates :first_attribute, :presence => true, :if => "second_attribute.blank?"
validates :second_attribute, :presence => true, :if => "first_attribute.blank?"
# The two lines below fail because 'false' is an invalid option
validates :first_attribute, :presence => false, :if => "!second_attribute.blank?"
validates :second_attribute, :presence => false, :if => "!first_attribute.blank?"
Or perhaps there's a more elegant way to do this...
I'm running Rails 3.0.9
For allowing an object to be valid if and only if a specific attribute is nil, you can use "inclusion" rather than creating your own method.
validates :name, inclusion: { in: [nil] }
This is for Rails 3. The Rails 4 solution is much more elegant:
validates :name, absence: true
class NoPresenceValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors[attribute] << (options[:message] || 'must be blank') unless record.send(attribute).blank?
end
end
validates :first_attribute, :presence => true, :if => "second_attribute.blank?"
validates :second_attribute, :presence => true, :if => "first_attribute.blank?"
validates :first_attribute, :no_presence => true, :if => "!second_attribute.blank?"
validates :second_attribute, :no_presence => true, :if => "!first_attribute.blank?"
use custom validation.
validate :validate_method
# validate if which one required other should be blank
def validate_method
errors.add(:field, :blank) if condition
end
It looks like :length => { :is => 0 } works for what I need.
validates :first_attribute, :length => {:is => 0 }, :unless => "second_attribute.blank?"
Try:
validates :first_attribute, :presence => {:if => second_attribute.blank?}
validates :second_attribute, :presence => {:if => (first_attribute.blank? && second_attribute.blank? )}
Hope that help .
Related
Does validates :uniqueness get called every time an object is saved even if a field has not changed? Isn't this a performance issue?
validates :name, :schedule_id, :uniqueness => true
It seems to be the case that it does. So isn't it almost always necessary to make sure a change has taken place before running the validation? As every field being checked for uniqueness requires a database hit.
This would be better:
validates :name, :schedule_id, :uniqueness => true, :if => "name_changed? || schedule_id_changed?"
And this much better, if a bit more verbose:
validates :name, :uniqueness => true, :if => :name_changed?
validates :schedule_id, :uniqueness => true, :if => schedule_id_changed?
Gist here: https://gist.github.com/4017019
try this
validates :name, :uniqueness => true, :if => lambda {self.name_changed? }
I have this validation
validates :contact_id, :presence => true, :uniqueness => {:message => 'has an account already.'}
in the application.rb model
All is good but I need to only do this validation if the state is "invalid"
For example in the applications table there is a field called state and if there is a application with a contact_id of a user and the state is "invalid" then this validation should not take effect and should let the user save the application
I believe this should do it:
validates :contact_id,
:presence => true,
:uniqueness => {:message => 'has an account already.'},
:if => :invalid?
def invalid?
state == 'invalid'
end
you could also inline that to:
validates :contact_id,
:presence => true,
:uniqueness => {:message => 'has an account already.'},
:if => lambda{ state == 'invalid' }
Hope this helps.
If you are going to do it when the state is not invalid, then you could do that two ways:
validates :contact_id,
:presence => true,
:uniqueness => {:message => 'has an account already.'},
:unless => :invalid?
Or you could change it a bit more and have a valid message, which I might prefer:
validates :contact_id,
:presence => true,
:uniqueness => {:message => 'has an account already.'},
:if => :valid?
def valid?
state != 'invalid'
end
Did you try seeing this railscasts video ? http://railscasts.com/episodes/41-conditional-validations
validates :contact_id, :if => :should_validate_contactid?
def should_validate_contactid?
Check condition
end
I have a restrictive schema (i.e. the schema already has null restrictions and maximum length etc). Would putting all of them in the model also overkill and counterproductive?...
validates :CouponID, :presence => true,
:numericality => true
validates :MerchantName, :presence => true,
:length => { :maximum => 100 }
validates :MerchantID, :presence => true,
:numericality => true
validates :Network, :length => { :maximum => 20 }
validates :Label, :presence => true
validates :CouponCode, :length => { :maximum => 100 }
validates :EndDate, :presence => true
validates :Link, :presence => true
validates :Status, :presence => true,
:length => { :maximum => 45 }
validates :Country, :length => { :maximum => 100 }
No it's not an overkill. Putting these into validators your model would allow Rails to catch them before inserting them into the database. It's also good design and practice.
If you omitted these, you would get MySQL errors thrown instead.
For example. Let's say I have a Comment model which contains an attribute string called body which can not be nil in my table.
class Comment < ActiveRecord::Base
end
If I tried:
comment = Comment.create(body: nil)
I would get the following exception.
ActiveRecord::StatementInvalid: Mysql2::Error: Column 'body' cannot be null:
This is bad. The natural flow of your application would break.
But, if I put the validators in my model like so
class Comment < ActiveRecord::Base
validates :body, presence: true
end
and tried the following:
comment = Comment.create(body: nil)
I would not get an exception thrown but the errors array for my variable to tell me what went wrong.
comment.errors.full_messages
=> ["Body can't be blank"]
It's good practice to put validators in your models and allows for good design.
I have the following validation:
validates :password, :presence => true, :confirmation => true, :length => { :within => 6..40 }, :format => { :with => pass_regex }, :unless => :nopass?
Then, when I try to update without password (nopass? is true) the following errors appear:
There were problems with the following fields:
Password is too short (minimum is 6 characters)
Password is invalid
Notice that the :unless works on :presence and :confirmation but not in :lenght or :format.
How could I fix this?
I've had some strange issues with the :confirmation flag as well, which I never figured out, but that's how I solved the problem in my Rails 3.0.x app:
attr_accessor :password_confirmation
validates :password, :presence => true, :length => {:within => PASSWORD_MIN_LENGTH..PASSWORD_MAX_LENGTH}
validate :password_is_confirmed
def password_is_confirmed
if password_changed? # don't trigger that validation every time we save/update attributes
errors.add(:password_confirmation, "can't be blank") if password_confirmation.blank?
errors.add(:password_confirmation, "doesn't match first password") if password != password_confirmation
end
end
I realise this is not an explanation why your code isn't working, but if you're looking for a quick temporary fix - I hope this will help.
You might use conditional validations
class Person < ActiveRecord::Base
validates :surname, :presence => true, :if => "name.nil?"
end
UPDATE (4th Dec 2010):
I realized that each validates line is actually a method call (obviously) so requiring them like this wasn't exactly doing as I expected.
This works, but I'm not sure it's correct (fully qualify the Auction class name):
class Auction::Validations
Auction.validates :status, :presence => true,
:inclusion => { :in => [
Auction::CREATING,
Auction::OPEN,
Auction::PENDING,
Auction::CANCELLED,
Auction::SUSPENDED,
Auction::EXPIRED,
Auction::CLOSING_COMPLETED,
Auction::CLOSING_WON,
Auction::COMPLETED,
Auction::WON,
Auction::NEGOTIATING,
Auction::IN_ESCROW
] }
Auction.validates :user, :presence => true
Auction.validates :url, :presence => true,
# FIXME: Move this to a URLValidator and do :url => true
:format => /^https?:\/\/[a-z0-9-]+(\.[a-z0-9-])*\.[a-z0-9]+\/.*/i
Auction.validates :title, :presence => true,
:length => { :maximum => 255 }
Auction.validates :description, :presence => true
Auction.validates :reserve, :numericality => { :greater_than_or_equal_to => :minimum_bid }
end
When this is required (require 'auction/validations) into the Auction class, it does the correct thing.
Original Question follows:
A couple of my model classes are getting a little cluttered with all these "validates" calls, so I thought I'd be able to move them into a separate class and 'require' that, but it doesn't seem to work.
class Auction < ActiveRecord::Base
require 'auction/validations'
...
class Auction::Validations
include ActiveModel::Validations
validates :status, :presence => true,
:inclusion => { :in => [
... snip ...
] }
validates :user, :presence => true
validates :url, :presence => true,
# FIXME: Move this to a URLValidator
:format => /^https?:\/\/[a-z0-9-]+(\.[a-z0-9-])*\.[a-z0-9]+\/.*/i
validates :title, :presence => true,
:length => { :maximum => 255 }
validates :description, :presence => true
validates :reserve, :numericality => { :greater_than_or_equal_to => :minimum_bid }
validates_each :status, :on => :update do |auction, status_attr, value|
if auction.state_machine.current_state != value
# FIXME: Raise an Exception instead; this is a developer error, not a user error
auction.errors.add status_attr, "Status cannot be changed directly"
end
end
end
It doesn't error, but the validates_each doesn't execute the block at all (tested by adding a puts "here"), and the numericality check doesn't work any longer.
With the body of this class blindly copied back into the Auction class again everything works.
Am I misunderstanding what the "require" will do with these validations?
EDIT:
In fact, none of the validations are working. Not just those two. Hmmm.
I'm not sure if this is right but it somehow works for me:
module MyValidations
module User
def self.included(base)
base.validates_presence_of :firstname
end
end end
Then u can call
User.class_eval do
include MyValidations::User
end
Hope that helps.
Put at the end of Auction::Validations:
Auction.send :extend, Auction::Validations
and at the end of Auction put that require line.
Why just not include your module ?
module Auction::Validations
extend ActiveSupport::Concern
def included(base)
validates :status, :presence => true,
:inclusion => { :in => [
... snip ...
] }
validates :user, :presence => true
validates :url, :presence => true,
# FIXME: Move this to a URLValidator
:format => /^https?:\/\/[a-z0-9-]+(\.[a-z0-9-])*\.[a-z0-9]+\/.*/i
validates :title, :presence => true,
:length => { :maximum => 255 }
validates :description, :presence => true
validates :reserve, :numericality => { :greater_than_or_equal_to => :minimum_bid }
validates_each :status, :on => :update do |auction, status_attr, value|
if auction.state_machine.current_state != value
# FIXME: Raise an Exception instead; this is a developer error, not a user error
auction.errors.add status_attr, "Status cannot be changed directly"
end
end
end
end
In Rails 4 this is easy. Use model concerns.
# put this into: app/models/concerns/auction/validations.rb
class Auction
module Validations
extend ActiveSupport::Concern
included do
validates :status, presence: true
end
end
end
class Auction
include Validations
# other model code...
end