How does this length validation work in Ruby on Rails? - ruby-on-rails

I am following the Rails tutorial by Michael Hartl. In chapter 6 we're creating a length validation test for a users name and email.
In the test/models/user_test.rb file he says to write
test "name should not be too long" do
#user.name = "a" * 51
assert_not #user.valid?
end
and then in app/models/user.rb we put
class User < ActiveRecord::Base
validates :name, presence: true, length: { maximum: 50 }
My question is, how does the test ensure that the name is not, for example, 60 characters long? I get that the validation says to make the max length 50, but the test says assert that the user is not valid if user.name EQUALS 51 characters...not greater than or equal to.
To be completely honest, I don't understand the relationship between why you need the validates in user.rb and then also the test file, so that could be why I'm confused.

In tests, you need to ensure that your code does everything as you expect. In this case that you can't save the user with name which is longer than 50 symbols. Thats why in test you are checking that user becomes invalid with name length == 51 symbol.
But you are also correct that this test doesn't guarantee that user with name length 60 will be invalid too. It also doesn't check that 50 is the maximum, because it will pass for
validates :name, presence: true, length: { maximum: 1 }
for example. But you probably don't want your app to behave like this. That's why I also encourage you to add another check for maximum allowed length:
#user.name = "a" * 50
assert #user.valid?
Usually, if you are doubt that something can be wrong in your code, you are free to add new tests. But in this case, you shouldn't actually test how the code behaves, because presence/length validations are well-tested in Rails itself. You should just check that you included such validations in your model with correct arguments passed. For example, in shoulda-matchers you have these helpers:
should validate_presence_of(:name)
should validate_length_of(:name).is_at_most(50)
I am not sure if unit-test have the analogs, most likely no, so you should test it yourself in the way you do this and assume this is enough.

The test is just asserting that a user name with 51 characters should not be a valid one.
The test doesn't 'ensure' that the user name can't be 60 characters long. That's what the actual validation code does.
For example, if you were to change the validation code to this:
class User < ActiveRecord::Base
validates :name, presence: true, length: { maximum: 60 }
then the test would fail because the code is validating a user name with 51 characters.

Related

Rails Model minitest: "Expected false to be truthy"

I'm having trouble getting an assertion to pass in a RoR minitest. As far as I'm aware, this object should be totally valid. I can add an object with these attributes on the products page, but using the same attributes in a test fails this assertion.
Model:
class Product < ApplicationRecord
validates :title, :description, presence: true
validates :title, uniqueness: true
validates :price, numericality: { greater_than_or_equal_to: 0.01 }
validates :image_url, allow_blank: true, format: {
with: %r{​​\.​​(gif|jpg|png)​​\z​​}i,
message: 'must be GIF, JPG, or PNG'
}
end
Test in question:
test "price must be greater than or equal to 0.01" do
product = Product.new(title: "My Book Title",
description: "the description",
image_url: "zzz.jpg")
product.price = -1
assert product.invalid?
product.price = 0
assert product.invalid?
product.price = 1
assert product.valid?
end
The other two assertions pass, but something about the object is still invalid, even after setting a valid price. I also commented out the validates :price part, but something is still invalid in the test.
This is also my first foray into Ruby in general, so I may be missing something totally obvious but I've really no idea.
Okay, so this is really bizarre. I downloaded the products.rb file straight from the book's website, and although the contents were identical, the regex worked and all of the assertions passed. I can only assume that there are some rules I was unaware of about whitespace in validates: calls? Because otherwise I have no idea, as the contents of my version and the website's version were exactly the same.

Is there something like .NET's data annotations in rails?

I was looking for something similar to .net's data annotations in rails, something like this.
What I want to achieve by this is: I have some fields (they could be nil also) for which I want to check the length and if the length exceeds I want to display an error message.
I want to club all the error messages related to, say all blog posts (which again have many separate fields) and then display them at once.
Rails uses ActiveRecord validations. In many cases the default validations are easy to set up. But if you want/or need customized validations that can all be done as well. Read the documentation here:
http://guides.rubyonrails.org/active_record_validations.html
In your case this type of validation is built in to rails so it's simple as adding 1 line to your model:
class MyModel
validates :my_field_name, length: { maximum: 3 }, allow_blank: true
end
This will validate the maximum length of your field. You can also customize the validation error message:
class MyModel
validates :name, presence: {message: "Title can't be blank." }, uniqueness: {message: "Title already exists."}, length: { maximum: 5, message: "Must be less than 5 characters"}
end

TDD in Rails: clarification on Red - Green testing

Following Michael Hartl's Rails tutorial, I'm unsure how the following validation test is supposed to work:
test "name should be present" do
#user.name = " "
assert_not #user.valid?
end
When this test is written, the testing suite should be Red. After adding the corresponding part in the User class as such:
class User < ActiveRecord::Base
validates :name, presence: true
end
The test becomes Green. I don't understand how the former part works. Is the test Red because the validates part is not implemented yet? After implementing it, #user.valid? should be False turning into a True due to assert_not. Thus, the test is Green?
You have the correct understanding of it. Before a model in Rails is saved to the database, it must be validated. The test here is checking whether the user model would be considered valid and saved with a blank name. As such, before you add validates :name, presence: true, #user.valid? evaluates to true and the test fails. Once you add the validation, the model is considered invalid.
In a Test driven development (TDD) approach we write test before writing codes. Thus, we make the test fail first and then write some codes to make the test pass.
Looks like you do have a correct understanding of whats going on.
assert_not #user.valid? is saying that the #user object is invalid. To start with #user.name is set to a blank value; so we expect it the assertion to pass. However, the code is not yet checking for a valid name to be present. So it fails.
Adding validates :name, presence: true makes the model check for a valid name to be present. Thus the test passes.

Validating presence with rspec

This is my first time testing in rails and I'm having trouble with what I think should be a pretty simple validation.
In group_spec.rb
it { should validate_presence_of(:enc_key) }
it { should validate_uniqueness_of(:enc_key).case_insensitive }
In my model
validates :enc_key, :presence => true, uniqueness: {:case_sensitive => false}
When I run rspec I'm getting
2) Group should require enc_key to be set
Failure/Error: it { should validate_presence_of(:enc_key) }
Expected errors to include "can't be blank" when enc_key is set to nil, got errors: ["owner There is no owner associated with this group. (nil)", "name can't be blank (nil)", "name is
too short (minimum is 4 characters) (nil)", "stripped_name can't be blank (nil)"]
The list of errors are generated from other validations and I've tried writing a custom validation but that didn't work either.
I'm guessing you are using shoulda matchers here and I can't see anything wrong with your test.
I think you should be using the validates method rather than the validate method in you model. validates is used to declare the kind of validation rules that you are specifying here. validate is used to declare custom validations.
So try this in your model:
validates :enc_key, presence: true, uniqueness: {case_sensitive: false}
See http://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html#method-i-validates
and http://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html#method-i-validate
The shoulda matchers are designed to work with otherwise valid records. They change various aspects of the record and test that the validation reports the appropriate error. If there are already errors in the record, this approach generally doesn't work.
You haven't shown the rest of your spec, but I gather than subject is nil. You need to set subject to be a valid instance of your record.
See https://github.com/thoughtbot/shoulda-matchers/issues/365 for a related discussion of this.

Testing password length validation with RSpec

I'm writing some unit tests to ensure a User model cannot have a password < 8 characters long.
I started with a User model:
class User < ActiveRecord::Base
...
validates :password, :length =>{
:minimum => 90,
:too_short => "password is too short, must be at least %{count} characters"
}, :on => :create
end
And a user_spec.rb test:
describe User do
subject { FactoryGirl.build :user }
its(:password) { should have_at_least(8).items }
end
However I realised that this doesn't actually test my validation, it just tests that my factory had a password >= 8 characters.
Is there a nice way to do this other than testing the valid? method for 0-7 character passwords?
My theory is that if I only test for 7 characters and someone accidentally hard codes that 4 characters passwords are OK this would pass validation but isn't really what was intended.
There could be some code else where that depends on a password being more than 8 characters (not likely but in other situations could be true) and so allowing a password of 4 is incorrect.
In this case the person who changed the password validation in the model won't know that that they've done anything wrong.
I'd just like to know how to properly test situations like this nicely with TDD.
Using the ensure_length_of matcher in thoughtbot's shoulda matchers, you can do this:
describe User do
it { should validate_length_of(:password).is_at_least(8)
.with_message(/password is too short/) }
end
See also: How can I test :inclusion validation in Rails using RSpec
I don't know if this answers your question but I think you are safe with something like this:
class User < ActiveRecord::Base
validates :password, :length => {:minimum => 8 }
end
describe User do
it "validates password length" do
FactoryGirl.build(:user, password: "1234567").should_not be_valid
FactoryGirl.build(:user, password: "12345678").should be_valid
end
end
The reason is that for this test to let through a 4 character password somebody would have to have set a validation rule that says 4 characters is ok, 7 isn't ok, but 8 is ok. Not something that is likely to happen by accident.

Resources