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.
Related
I have this basic validation in my model:
validates :student_number, :presence => true,
:length => { :maximum => 255 },
:uniqueness => true
So what is all that? Here's my best guess, if you would kindly tell me where I'm mistaken, I'd appreciate it.
validates is a method. I send it the symbol :first_name, then :presence => true, which is...a hash with :presence for a key and true as a value?
Except it doesn't really look like a hash, at least not according to the docs.
And then :length => { :maximum => 255 } is the same sort of entity (hash?) as :presence => true but it expects another hash as an argument?
Thanks for any assistance.
Ruby allows you to drop parentheses and brackets if it can infer their locations by itself; in your case, you could rewrite the code as:
validates(:student_number, { :presence => true,
:length => { :maximum => 255 },
:uniqueness => true })
which is a method call, passing a first argument which is the attribute to validate, and a second argument which is the validation options, a hash.
Note: This explanation is a bit of a simplification, validates is actually a bit more complicated in how it handles its arguments. See here for more details on how this works exactly.
close but not close enough. All :presence => true, :length => { :maximum => 255 }, :uniqueness => true is ONE hash with three keys presence, length, uniqueness and three coresponding values. In fact it is the same as you would write
{ :presence => true, :length => { :maximum => 255 }, :uniqueness => true } but first way is shorter
Is there a way to specify many validations like this more concisely?
validates :col_a, :presence => {:message => 'col_a cannot be blank'}
validates :col_b, :presence => {:message => 'col_b cannot be blank'}
validates :col_c, :presence => {:message => 'col_c cannot be blank'}
I'd settle for a generic message if I had to.
You can give multiple field names to a validator
validates :col_a, :col_b, :col_c, :presence => true
You can specify multiple validators in the same line.
validates :col_a, :col_b, :col_c, :presence => true, :numericality => true
The full error message will contain the field name. You don't need to add the field name prefix. If you want to use a custom message then:
validates :col_a, :col_b, :col_c, :presence => {:message => "empty value found"}
You can use
validates :col_a, presence: true
validates :col_b, presence: true
validates :col_c, presence: true
Use the validates_presence_of helper.
validates_presence_of :col_a
EDIT
You could clean it up a bit with validates_each. There is an example on the api page. http://api.rubyonrails.org/classes/ActiveModel/Validations.html
Hope that helps
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? }
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 .
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