Rick Peyton
Rick Peyton

Reputation: 69

How can I use a custom validation with shoulda matchers?

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('[email protected]', '[email protected]').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!

Upvotes: 1

Views: 1198

Answers (1)

BroiSatse
BroiSatse

Reputation: 44685

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 }

Upvotes: 3

Related Questions