rspec failing:expected 1 errors_on,got 3 - ruby-on-rails

class Card < ActiveRecord::Base
belongs_to :trade_type,:class_name => 'TradeType',:foreign_key => 'trade_type_id'
belongs_to :restaurant,:class_name => 'Restaurant',:foreign_key => 'restaurant_id'
validates_presence_of :card_no,:message => '卡号不能为空'
validates_numericality_of :card_no,:message => '卡号必须是数字'
validates_length_of :card_no,:is => 16,:message => '卡号必须是16位'
validates_uniqueness_of :card_no,:message => '卡号不能重复'
end
describe Card do
it 'is valid with card_no,name'do
card = Card.new(
card_no: '1054321239876456',
name: 'zhangsan',
)
expect(card).to be_valid
end
it 'is invalid without a card_no' do
card = Card.new(card_no:nil)
expect(card).to have(1).errors_on(:card_no)
end
end

The reason you are seeing 3 errors instead of 1 is because Card.card_no is blank. That means it is also not numeric and also not 16 characters long.
i.e. all three of
validates_presence_of :card_no,:message => '卡号不能为空'
validates_numericality_of :card_no,:message => '卡号必须是数字'
validates_length_of :card_no,:is => 16,:message => '卡号必须是16位'
are failing.
If you want numericality and length_of to only be validated when the card number is present, add allow_blank: true.
ie.
validates_presence_of :card_no,:message => '卡号不能为空'
validates_numericality_of :card_no, :allow_blank => true, :message => '卡号必须是数字'
validates_length_of :card_no, :is => 16, :allow_blank => true, :message => '卡号必须是16位'

Related

How to validate numericality and inclusion while still allowing attribute to be nil in some cases?

In a Rails app I have several integer attributes on a model.
A user should be able to create a record and leave these attributes blank.
Or, if the user enters values for these attributes, they should be validated for numericality and within a certain range.
In the model I have something like this
validates_presence_of :name
validates_numericality_of :a, :only_integer => true, :message => "can only be whole number."
validates_inclusion_of :a, :in => 1..999, :message => "can only be between 1 and 999."
If I now test with the minimum required attributes to save:
factory :model do
sequence(:name) { |n| "model#{n}" }
end
it "should save with minium attributes" do
#model = FactoryGirl.build(:model)
#model.save.should == false
end
I get
Validation failed: a can only be whole number., a can only be between 1 and 999.
How can I validate numericality and inclusion only if a value is given for :a, while still allowing :a to be nil in some cases?
Thanks
You can add an :allow_nil => true to your validates_numericality_of.
validates_numericality_of :a, :only_integer => true, :allow_nil => true,
:message => "can only be whole number."
You can also use greater_than_or_equal_to and less_than_or_equal_to options if you just want to use one validation:
validates_numericality_of :a, :only_integer => true, :allow_nil => true,
:greater_than_or_equal_to => 1,
:less_than_or_equal_to => 999,
:message => "can only be whole number between 1 and 999."
should be simply:
validates_numericality_of :a, :only_integer => true, :message => "can only be whole number.", :allow_nil => true
same for the second validation

Rails validation if a conditon is met

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

Conditional validation on model dependent on value of model attribute

I'm struggling with validating a postal_code and using a different regex depending on what the country_code attribute is. I tried the following, but it does not work:
class Venue < ActiveRecord::Base
...
attr_accessible :postal_code
attr_accessible :country_code
...
validates_presence_of :country_code
validates_length_of :country_code, :maximum => 2, :allow_nil => true
validates_inclusion_of :country_code, :in => %w( US CA AU GB )
validates_presence_of :postal_code
validates_length_of :postal_code, :maximum => 10, :allow_nil => true
validates_format_of :postal_code, :with => %r{^\d{4}$},
:message => "should be in the format 1111",
:allow_nil => true,
:if => :country_code == 'AU'
validates_format_of :postal_code, :with => %r{^\d{5}([\-]\d{4})?$},
:message => "should be in the format 11111 or 11111-1111",
:allow_nil => true,
:if => :country_code == 'US'
validates_format_of :postal_code, :with =>%r{^[ABCEGHJKLMNPRSTVXY]{1}\d{1}[A-Z]{1} *\d{1}[A-Z]{1}\d{1}$},
:message => "should be in the format A1A 1A1 (try making letters capitalized)",
:allow_nil => true,
:if => :country_code == 'CA'
# ridculously long (but thorough) regex courtesy of Wikipedia (http://en.wikipedia.org/wiki/Postcodes_in_the_United_Kingdom)
validates_format_of :postal_code, :with => %r{^(GIR 0AA)|(((A[BL]|B[ABDHLNRSTX]?|C[ABFHMORTVW]|D[ADEGHLNTY]|E[HNX]?|F[KY]|G[LUY]?|H[ADGPRSUX]|I[GMPV]|JE|K[ATWY]|L[ADELNSU]?|M[EKL]?|N[EGNPRW]?|O[LX]|P[AEHLOR]|R[GHM]|S[AEGKLMNOPRSTY]?|T[ADFNQRSW]|UB|W[ADFNRSV]|YO|ZE)[1-9]?[0-9]|((E|N|NW|SE|SW|W)1|EC[1-4]|WC[12])[A-HJKMNPR-Y]|(SW|W)([2-9]|[1-9][0-9])|EC[1-9][0-9]) [0-9][ABD-HJLNP-UW-Z]{2})$},
:message => "invalid postal code (try making letters capitalized)",
:allow_nil => true,
:if => :country_code == 'GB'
Am I totally off the reservation on this, or is it just the format of the :if options that are hosed up?
For reference, the :if => parameter accepts a method rather than a condition, you can for example do
validate_presence_of :foo, :if => :country_is_gb?
def country_is_gb?
country_code == 'GB'
end
But as mentioned, lengthy or complex validations are best done in a seperate custom validator.
Take a look at this screencast http://asciicasts.com/episodes/211-validations-in-rails-3
I think its just about time to write a custom validator function for your postal code.

why does rails not accept the :message symbol outside of a nested hash option?

Here is my code. The first commented line works fine; the second doesn't. The documentation is sketchy here - no mention of the :message option under the "validates" method, but "validates_format_of" says :message is fine. (http://apidock.com/rails/ActiveModel/Validations/ClassMethods/validates) What is going on here?
class Product < ActiveRecord::Base
validates :title, :description, :image_url, :presence => true
#validates :title, :length => {:minimum => 10, :message => "help!"}
#validates :title, :length => {:minimum => 10}, :message => "help!"
validates :price, :numericality => {:greater_than_or_equal_to => 0.01}
validates :image_url, :format => {
:with => %r{\.(gif|jpg|png)$}i,
:message => 'must be a URL for GIF, JPG or PNG image.'
}
end
In the first commented line, the message value is an option to the :length parameter and it appears its being ignored. To test try
validates :title, :length => {:minimum => 10, :foobar => "help!"}
and you will find it to works without creating an error.
validates is just a shortcut to default validators. You seem to want to set the validates_format_of :message options, so I think you want something like:
validates :title, length => {:minimum => 10}, :format => { :message => "help!" }
Length validations are baked in:
   validates_length_of :title, :minimum => 10, :message => "help!"
Active Record Validations

Rails: Keeping all the ActiveRecord "validates" lines in a separate file?

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

Resources