How can I use a custom validation with shoulda matchers? - ruby-on-rails

I have been unable to wrap my head around how to get these tests back to green.
Model
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },
uniqueness: true
validates :zip, presence: true, format: { with: VALID_ZIP_REGEX }
validates_numericality_of :puzzle_pieces, only_integer: true
Spec
it { should validate_presence_of(:email) }
it { should validate_uniqueness_of(:email) }
it { should allow_value('john.doe#example.com', 'alice#yahoo.ca').for(:email) }
it { should_not allow_value('john2example.com', 'john#examplecom').for(:email) }
it { should validate_presence_of(:zip) }
it { should allow_value('35124', '35124-1234').for(:zip) }
it { should_not allow_value('5124', '35124-12345').for(:zip) }
it { should validate_numericality_of(:puzzle_pieces).only_integer }
The above tests pass until I add this custom validation.
Custom Validator
class PiecesValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value > 0 && value <= Puzzle.remaining
record.errors[attribute] << (options[:message] || "Puzzle pieces must be between 1 and #{Puzzle.remaining}")
end
end
end
Model
validates :puzzle_pieces, pieces: true
Spec
it "does not allow a negative number of puzzle pieces to be saved" do
order = build(:order, puzzle_pieces: -1)
expect(order).to be_invalid
end
That last test passes, but all of my shoulda tests then fail with the same error
NoMethodError:
undefined method `>' for nil:NilClass
I am not understanding how to fix this. It seems that the shoulda tests operate in isolation just fine. But then they all blow up when the custom validation is added in.
Any help pushing me towards understanding this would be greatly appreciated!

Your problem is that your validation is not expecting value to be nil. Change your method to:
class PiecesValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value && value > 0 && value <= Puzzle.remaining
record.errors[attribute] << (options[:message] || "Puzzle pieces must be between 1 and #{Puzzle.remaining}")
end
end
end
This will not add an error if validated field is blank. However, what you are trying to do can be achieved using standard rails validators:
validates :puzzle_pieces, numericality: { only_integer: true, less_then: Puzzle.remaining, greater_then: 0 }

Related

set custom message in each different error validation in ruby on rails

let say that I have set in model for validation like this
validates :tel, presence: true , length: { minimum: 10, maximum: 11 }, numericality: { only_integer: true }
how do I can display a custom message in view for each validate.
when I set this in views page.
<% if #diary.errors.include?(:tel) %>
<div class="err"><p><%= #diary.errors.full_messages_for(:tel).join("") %></p></div>
<% end %>
it directly displays all error message. I want to make a display in view like this
if(error_require)
echo "tel is needed"
else if(error_length)
echo "tel is to long"
else
echo "tel must numeric"
end
can I make like that?
You can pass message in separate hashes for each validator:
validates :tel,
presence: { message: 'is needed' },
length: { minimum: 10, maximum: 11, too_long: 'is too long' },
numericality: { only_integer: true, message: 'must be numeric' }
Read more about presence, length, and numericality validators.
One way to do this is to define methods for each type of validation (in your model) like this:
validate :chech_length
def chech_length
if tel.length < 10 || tel.length > 11
errors.add(:base, "tel is too long!")
end
end
validate :check_if_present
def check_if_present
if tel.blank?
errors.add(:base, "tel must be present!")
end
end
etc...
Hope this helps.

RSpec with website format validation fails

I'm using rails4, factory_ girl, rspec and shoulda matchers. If I run rspec with the code below I get this error:
Product should validate that :website cannot be empty/falsy, producing a custom validation error on failure
Failure/Error: self.website = "http://#{self.website}" unless self.website[/^https?/]
NoMethodError:
undefined method `[]' for nil:NilClass
If I delete unless self.website[/^https?/] from the format_website method I get this error:
Product did not properly validate that :website cannot be empty/falsy,
producing a custom validation error on failure.
After setting :website to ‹nil› -- which was read back as ‹"http://"›
-- the matcher expected the Product to be invalid and to produce a
validation error matching ‹/can't be blank/› on :website. The record
was indeed invalid, but it produced these validation errors instead:
* user: ["can't be blank"]
* name: ["can't be blank"]
* company: ["can't be blank"]
What should I do to make this work?
product model
belongs_to :user
validates :name, presence: { message: "can't be blank" }, length: { maximum: 140, message: "can't be longer than 140 characters" }, uniqueness: { message: "already exists" }
validates :company, presence: { message: "can't be blank" }, length: { maximum: 140, message: "can't be longer than 140 characters" }
validates :website, presence: { message: "can't be blank" }, length: { maximum: 140, message: "can't be longer than 140 characters" }
before_validation :format_website
validate :website_validator
def format_website
self.website = "http://#{self.website}" unless self.website[/^https?/]
end
def website_validator
self.errors.add :website, "format is invalid!" unless website_valid?
end
def website_valid?
!!website.match(/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-=\?]*)*\/?$/)
end
factory
FactoryGirl.define do
factory :product do
name { Faker::Commerce.product_name }
company { Faker::Company.name }
website { 'https://example.com' }
user
end
end
it { is_expected.to callback(:format_website).before(:validation) } #this one is not important, if I take out it still gives the same error
it { is_expected.to validate_presence_of(:name).with_message(/can't be blank/) }
it { is_expected.to validate_presence_of(:company).with_message(/can't be blank/) }
it { is_expected.to validate_presence_of(:website).with_message(/can't be blank/) }
it { is_expected.to belong_to(:user) }
You should ensure that website is not nil.
def format_website
return if website.blank?
self.website = "http://#{self.website}" unless self.website[/^https?/]
end
In that case, if self.website == nil, you'll try to call the method [] on a nil object, hence the first error.
For the second case, the answer is in the return you have from rspec:
After setting :website to ‹nil› -- which was read back as ‹"http://"›
Your method format_website returns "http://", which is because website is nil.

ActiveModel::Validation Any of 3 fields

I have 3 fields in my Model and i need at least one of them to be present - how can do this using inbuilt validations?
Create a custom validation:
validate :one_of_three
def one_of_three
errors.add(:base, 'Must have one of foo, bar or wee') unless foo || bar || wee
end
Try the following.
validate :attributes_presence
def attributes_presence
errors.add(:base, 'At least one attribute must be present') if attr1.blank? && attr2.blank? && attr3.blank?
end
There is a way to conditionally validate the presence of one attribute depending on presence of another:
validates :attr_1,
presence: true,
if: ->(model) { model.attr_2.blank? && model.attr_3.blank? }
validates :attr_2,
presence: true,
if: ->(model) { model.attr_1.blank? && model.attr_3.blank? }
validates :attr_3,
presence: true,
if: ->(model) { model.attr_1.blank? && model.attr_2.blank? }

Validate presence of one field or another or both(OR)

So I'm looking at this link but I'm wondering how to do an 'OR' instead of an 'XOR'. Does anyone know how to do this? I am trying to do this using Rails 3.
There is short option for Rails 4 (not sure about Rails 3) :
validates :email, presence: { if: -> { username.blank? } }
validates :username, presence: { if: -> { email.blank? } }
I figured it out. all you have to change is the ^ to &&
private
def time_or_money
if time.blank? && money.blank?
errors[:base] << "Specify Time, Money or Both."
end
end

Raising errors in attribute accessor overrides?

I am overriding an attribute accessor in ActiveRecord to convert a string in the format "hh:mm:ss" into seconds. Here is my code:
class Call < ActiveRecord::Base
attr_accessible :duration
def duration=(val)
begin
result = val.to_s.split(/:/)
.map { |t| Integer(t) }
.reverse
.zip([60**0, 60**1, 60**2])
.map { |i,j| i*j }
.inject(:+)
rescue ArgumentError
#TODO: How can I correctly report this error?
errors.add(:duration, "Duration #{val} is not valid.")
end
write_attribute(:duration, result)
end
validates :duration, :presence => true,
:numericality => { :greater_than_or_equal_to => 0 }
validate :duration_string_valid
def duration_string_valid
if !duration.is_valid? and duration_before_type_cast
errors.add(:duration, "Duration #{duration_before_type_cast} is not valid.")
end
end
end
I am trying to meaningfully report on this error during validation. The first two ideas that I have had are included in the code sample.
Adding to errors inside of the accessor override - works but I am not certain if it is a nice solution.
Using the validation method duration_string_valid. Check if the other validations failed and report on duration_before_type_cast. In this scenario duration.is_valid? is not a valid method and I am not certain how I can check that duration has passed the other validations.
I could set a instance variable inside of duration=(val) and report on it inside duration_string_valid.
I would love some feedback as to whether this is a good way to go about this operation, and how I could improve the error reporting.
First, clean up your code. Move string to duration converter to the service layer. Inside the lib/ directory create StringToDurationConverter:
# lib/string_to_duration_converter.rb
class StringToDurationConverter
class << self
def convert(value)
value.to_s.split(/:/)
.map { |t| Integer(t) }
.reverse
.zip([60**0, 60**1, 60**2])
.map { |i,j| i*j }
.inject(:+)
end
end
end
Second, add custom DurationValidator validator
# lib/duration_validator.rb
class DurationValidator < ActiveModel::EachValidator
# implement the method called during validation
def validate_each(record, attribute, value)
begin
StringToDurationConverter.convert(value)
resque ArgumentError
record.errors[attribute] << 'is not valid.'
end
end
end
And your model will be looking something like this:
class Call < ActiveRecord::Base
attr_accessible :duration
validates :duration, :presence => true,
:numericality => { :greater_than_or_equal_to => 0 },
:duration => true
def duration=(value)
result = StringToDurationConverter.convert(value)
write_attribute(:duration, result)
end
end

Resources