Rails Validations and custom error messages - ruby-on-rails

I'm having an issue with Validations, if I use the following syntax all is well (no failures).
validates :title, uniqueness: true
However if I change it to this, I get a failure.
validates :title, uniqueness: {message: 'Title must be unique'}
Here is the test for completeness:
test "product is not valid without a unique title " do
product = Product.new( title: products(:ruby).title,
description: "yyy",
price: 1,
image_url: "fred.gif" )
assert !product.save
assert_equal "has already been taken", product.errors['title'].join('; ')
end
I have a fixture that adds the book title for the Ruby product etc.
As I understand it the two validations should be the same, just that one gives a custom error message. This is the error I get when using the custom message.
1) Failure:
test_product_is_not_valid_without_a_unique_title_(ProductTest)
<"has already been taken"> expected but was
<"Title must be unique">.
Thanks in advance for your help.

Here:
assert_equal "has already been taken", product.errors['title'].join('; ')
you expect to see "has already been taken" in the errors hash, but you've overridden this message with your custom message. Test shows that your custom message appears as it should. Why do you still expect the default message? You have to expect Title must be unique.
TIP Don't supply the name of your field in your custom message. Rails will handle it automatically when generating errors by, say, product.errors.full_messages ("must be unique" would be enough).

Related

Rails validations - cutom messages are not applied

I have a Ride model with price float field and validation of the precision. I want to display my own custom error message when the validation fails but it doesn't work.
According to Rails Gudes "the :message option lets you specify the message that will be added to the errors collection when validation fails. When this option is not used, Active Record will use the respective default error message for each validation helper. The :message option accepts a String or Proc."
I do it exactly as in the example there and it does not work.
Rails guides
validates :age, numericality: { message: "%{value} seems wrong" }
My example
validates :price, numericality: { message: "Invalid price. Max 2 digits after period"}, format: { with: /\A\d{1,4}(.\d{0,2})?\z/ }
spec/models/ride_spec.rb
context 'with more than 2 digits after period' do
let(:price) { 29.6786745 }
it 'the price is invalid' do
expect(subject.save).to be_falsy
expect(subject).not_to be_persisted
puts subject.errors.full_messages.last # "Price is invalid"
end
end
What am I doing wrong?
Update
This is what I've learned so far.
I have set the price to be empty in the test and it now shows the error message that I want.
context 'with more than 2 digits after period' do
let(:price) { '' }
it 'the price is invalid' do
expect(subject.save).to be_falsy
expect(subject).not_to be_persisted
puts subject.errors.full_messages.last # "Price Invalid price. Max 2 digits after period"
end
end
Conslusion: it works for 'presence' validation, not for numericality validation, which is very confusing as the docs say clearly that you validate numericality, not presence. Am I right? Is this an error or deliberate?
I think where you are going wrong is expecting that numericality accepts a validation option format. Referring to the active record guides there is no option for format.
Seeing that you have called this price it seems you want to keep the precision to 2 decimal places so you can store the dollar value of something. The proper type for this is a decimal with scale: 2, or something I've had success with in the past is storing the price as an integer price_in_cents.
context 'with more than 2 digits after period' do
let(:price) { 123.333 }
it 'rounds to 2 decimal places' do
expect(subject.save).to eq true
expect(subject.reload.price).to eq 123.34
end
end
I figured this out, there are two validations: format validation and numericality validation. I did not add message to format validation, so when it fails I get the standard message
validates :price, format: { with: /\A\d{1,4}(.\d{0,2})?\z/, message: 'Invalid price. Max 2 digits after period'}, numericality: { message: 'is not a number' }

Having trouble testing Rails Model validations with shoulda-matchers and factory-girl-rails?

Model:
validates :email,
uniqueness: {
message: "has been taken."
},
presence: {
message: "cannot be blank."
},
length: {
minimum: 3,
message: "is too short, must be a minimum of 3 characters.",
allow_blank: true
},
format: {
with: /\A[A-Z0-9_\.&%\+\-']+#(?:[A-Z0-9\-]+\.)+(?:[A-Z]{2,13})\z/i,
message: "is invalid.",
if: Proc.new { |u| u.email && u.email.length >= 3 }
}
RSpec:
before(:each) do
#user = FactoryGirl.build(:user)
end
it { should validate_length_of(:email).is_at_least(3) }
Error:
Failure/Error: should validate_length_of(:email).is_at_least(3)
Expected errors to include "is too short (minimum is 3 characters)" when email is set to "xx",
got errors:
* "is too short (minimum is 4 characters)" (attribute: password, value: nil)
* "is too short, must be a minimum of 3 characters." (attribute: email, value: "xx")
* "is not included in the list" (attribute: state, value: "passive")
Factory:
factory :user, class: User do
email FFaker::Internet.email
password FFaker::Internet.password
username FFaker::Internet.user_name
end
I am using factory_girl_rails with shoulda_matchers. Every time I try to validate my email, I keep getting an error like above. It says the email value is "xx", but the length of the factory email is greater than that. How can I write an rspec that will pass?
Shoulda-matchers tests validations by testing the messages in the errors object.
The valdation matchers only work with the rails default error messages unless you specify otherwise:
it { should validate_length_of(:email).with_message("is too short, must be a minimum of 3 characters.") }
The with_message method is detailed in this comment.
You are understanding the error (and it's cause) wrong. The error says that it expects "is too short (minimum is 3 characters)" but in the errors it can't find that string (rspec finds "is too short, must be a minimum of 3 characters." which is what you defined on your validation).
When you use shoulda matchers and say
it { should validate_length_of(:email).is_at_least(3) }
I'm guessing it creates a test with an email shorter than 3 and checks if it fails, that's why it's ignoring your factory, internally should matcher is setting a fixed length string just to make that test pass.
When you see the errors in user, that test should actually work since the error is actually there, only the string is different. So, you have two options: remove the custom message when length is minimum: 3; or tell the matcher what message you are expecting:
it { should validate_length_of(:email).is_at_least(3).with_message("is too short, must be a minimum of 3 characters.") }

(Rails) update_attributes throws error (needing a password) during integration test that doesn't use password

I'm writing a Rails integration test that checks that the user's title saves. The title has one validation: it has to be no more than 255 characters. But #user.update_attributes!(title: params[:title]) is throwing the error "Password must have at least 6 characters." But...I'm not updating the password or anything other than the title. So how do I save this attribute with its own validation and not worry about the password?
Test:
test "profile submits new title and description successfully" do
log_in_as(#non_admin)
get user_path(#non_admin)
assert_nil #non_admin.title
post "/users/#{#non_admin.id}/update_description",
{ title: "I am a man of constant sorrow." }
user = assigns(:user)
user.reload.title
assert user.title == "I am a man of constant sorrow."
assert_template 'users/show'
assert flash[:success]
end
Controller method (not finished, but you'll get the idea). Note, it's the update_attributes! call that throws the password validation error.
# Handles user's posted title and description.
def update_description
#user = User.find(params[:id])
# Check if title is present. If so, attempt to save, load flash, and reload.
if #user.update_attributes!(title: params[:title])
flash[:success] = "Saved title. "
# if unable, set error flash and reload.
else
flash[:warning] = "Unable to save."
end
# Same logic as before, now for description.
# Make sure two different [:success] flashes work! Probably not!
redirect_to user_path(#user)
end
Validations:
validates :password, length: { minimum: 6,
message: "must have at least 6 characters" }
validates :title, length: { maximum: 255 }
Here's the test error:
23:08:51 - INFO - Running: test/integration/users_show_test.rb
Started
ERROR["test_profile_submits_new_title_and_description_successfully", UsersShowTest, 2017-10-23 01:06:11 -0400]
test_profile_submits_new_title_and_description_successfully#UsersShowTest (1508735171.57s)
ActiveRecord::RecordInvalid: ActiveRecord::RecordInvalid: Validation failed: Password must have at least 6 characters
app/controllers/users_controller.rb:71:in `update_description'
test/integration/users_show_test.rb:22:in `block in <class:UsersShowTest>'
app/controllers/users_controller.rb:71:in `update_description'
test/integration/users_show_test.rb:22:in `block in <class:UsersShowTest>'
In case it's relevant, here's the fixture that is loaded as #non_admin:
archer:
name: Sterling Archer
email: duchess#example.gov
password_digest: <%= User.digest('Jsdfuisd8f') %>
activated: true
activated_at: <%= Time.zone.now %>
I'm a Rails noob so it's probably something basic. Thanks in advance...
UPDATE: See discussion with kasperite below. I simply needed to add on: create to my password validation.
Calling update_attributes! will triggers save!, which in turn triggers validation on the model. And since you don't provide password, it will throw the exception.
You can either do update_attribute(:title, params[:title]) which bypass validation
or this:
#user.title = params[:title]
#user.save!(validation: false)
See: http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-update-21

How do I put a custom validation message for a "greater_than" property from my model?

I'm using Rails 5. In my model, I have this validation rule
validates :price, :numericality => { :greater_than => 0 }
for one of my fields. I want to create a custom validation error message but this isn't displaying for my ./config/locales/en.yml file
en:
activerecord:
errors:
models:
my_record:
attributes:
...
price:
greater_than: "Please etner a valid number for price."
When I try and load my app, I get the error below
can not load translations from /Users/davea/Documents/workspace/cindex/config/locales/en.yml: #<Psych::SyntaxError: (/Users/davea/Documents/workspace/cindex/config/locales/en.yml): found character that cannot start any token while scanning for the next token at line 30 column 1>
What's the right way to set up the custom error message in my locales file?
Definitely a YAML parsing issue caused by bad formatting or syntax. Check that you don't have any single quotes anywhere and your indentation is correct at line 29, 30 and 31

Rails test fails with fixture but passes with direct object creation

I have a test:
test 'gift should not be saved with a non-numeric price' do
#gift = gifts(:gift_without_numeric_price)
assert_not #gift.save, 'gift was saved with non-numeric name'
end
It uses this fixture:
gift_without_numeric_price:
name: "pinecones"
description: "these pinecones smell really good"
estimated_price: "object"
link: "www.google.com"
My Gift model looks like this:
validates :estimated_price,
presence: true,
numericality: true
So I was expecting the test to pass since 'object' is not numeric, causing the #gift.save to return false and causing the assert_not to ultimately return true. However, the test fails for some reason. When I use the the direct way of creating a gift object, the test looks like this and the test passes:
test 'gift should not be saved with a non-numeric price' do
#gift = Gift.new(name: 'aa', description: 'description', link: 'www.google.com', estimated_price: 'object')
assert_not #gift.save, 'gift was saved with non-numeric name'
end
What am I doing wrong with the fixture?
You can try and change your validation:
validates :estimated_price, presence: true,numericality: { only_integer: true }

Resources