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.
Related
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.
I have been starting to learn testing in my Rails app and am using rSpec and Shoulda.
I have the following test which works:
it { should respond_to(:park_name) }
However, what I don't understand is, what is this being run on? Is this being run on the Model itself or an instance of the model and if it's an instance of the Model then is it automatically using my Factory Girl factory?
Any simple explanations on what is actually occurring here?
UPDATE:
Ok, So I have this:
describe 'validations' do
subject { FactoryGirl.build(:coaster) }
it { should validate_presence_of(:name) }
it { should validate_presence_of(:speed) }
it { should validate_presence_of(:height) }
end
But the tests are failing. Any ideas?
Coaster.rb:
class Coaster < ActiveRecord::Base
extend FriendlyId
friendly_id :slug, use: :slugged
belongs_to :park
belongs_to :manufacturer
attr_accessible :name,
:height,
:speed,
:length,
:inversions,
:material,
:lat,
:lng,
:park_id,
:notes,
:manufacturer_id,
:style,
:covering,
:ride_style,
:model,
:layout,
:dates_ridden,
:times_ridden,
:order,
:on_ride_photo
scope :by_name_asc, lambda {
order("name ASC")
}
scope :made_from, lambda { |material|
where("material = ?", material)
}
scope :wooden, lambda {
made_from "wood"
}
scope :steel, lambda {
made_from "steel"
}
delegate :name, :location_1, :location_2, :location_3, :location_4,
to: :park,
allow_nil: true,
prefix: true
delegate :name, :url,
to: :manufacturer,
prefix: true
validates :name,
:presence => true
validates :height,
allow_nil: true,
numericality: {greater_than: 0}
validates :speed,
allow_nil: true,
numericality: {greater_than: 0}
validates :length,
allow_nil: true,
numericality: {greater_than: 0}
Test Results:
1) Coaster validations should require speed to be set
Failure/Error: it { should validate_presence_of(:speed) }
Expected errors to include "can't be blank" when speed is set to nil, got no errors
# ./spec/models/coaster_spec.rb:75:in `block (3 levels) in '
2) Coaster validations should require height to be set
Failure/Error: it { should validate_presence_of(:height) }
Expected errors to include "can't be blank" when height is set to nil, got no errors
# ./spec/models/coaster_spec.rb:76:in `block (3 levels) in '
SIMILAR QUESTION:
I have this test:
describe 'methods' do
subject { FactoryGirl.build(:coaster) }
it "should return a formatted string of coaster name at park name" do
name_and_park.should eq('Nemesis at Alton Towers')
end
end
Coaster.rb:
def name_and_park
[name, park.name].join (' at ')
end
Error when running the test:
2) Coaster methods should return a formatted string of coaster name at
park name
Failure/Error: name_and_park.should eq('Nemesis at Alton Towers')
NameError:
undefined local variable or method name_and_park' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_6:0x007f84f4161798>
# ./spec/models/coaster_spec.rb:111:inblock (3 levels) in '
It says name_and_park cannot be called but surely that method should be being called on the instance of Coaster that is being made in the subject line? No?
It's being run on "subject", which is either explicitly defined through the subject method or implicitly defined by passing in a class as the argument to describe, in which case an instance of that class is instantiated and made the subject of the test. See https://www.relishapp.com/rspec/rspec-core/v/2-0/docs/subject/explicit-subject and https://www.relishapp.com/rspec/rspec-core/v/2-0/docs/subject/implicit-subject
As for the answer to the Update question, in your model validations for :speed and :length, you have allow_nil: true, which is why those two tests are failing. Part of the definition of validates_presence_of is that nil is not a settable value.
As for your latest question, I think you may be confused about the use of implicit subjects. If should is used by itself, it will indeed default to whatever the subject is, but if you include a subject yourself, as you have in this case with name_and_park, it won't treat that as a method of the default subject, it must have a definition within the current namespace. In your case, you would need to say subject.name_and_park.should ....
On a related aside, StackOverflow is best used when you ask a specific question or related set of questions and get an answer. For a variety of reasons, it's not intended for ongoing debugging sessions. One of those reasons is that it becomes tedious tracking substantial, sequential updates of the original question and answer.
My User model contains the following:
validates :password_digest, :presence => true, :message => "The password has to be 6 or more characters long"
def password=(password)
self.password_digest = BCrypt::Password.create(password) if password.length >= 6
end
The issue is that the message in the validates isn't working. I get a Unknown validator: 'MessageValidator' error. I assumed the way the presence validation worked was that it would just check if the password_digest was nil, which it would be had the password had a length less than 6. I want a solution that is elegant, like what I attempted. I have solved this one way, but I would really appreciate an understanding as to why what I'm trying isn't working, and is there a way to make it work.
What I got to work was:
validate do |user|
user.errors['password'] = "can't be less than 6 characters" if user.password_digest.nil?
end
This is due to how the validates method works. It assumes that you're looking for the MessageValidator when you specify :message as a key in the hash passed to validates.
This can be solved by reconstructing the query as follows:
validates :password_digest, :presence => { :message => "The password has to be 6 or more characters long" }
I'm learning rails with the book Agile Web development with Rails 4e. It uses the following so far as our product model (adapted from a scaffold):
class Product < ActiveRecord::Base
attr_accessible :description, :image_url, :price, :title
validates :description, :title, :image_url, presence: true
validates :price, numericality: {greater_than_or_equal_to: 0.01}
validates :title, uniqueness: true
validates :image_url, allow_blank: true, format: {
with: %r{\.(gif|jpg|png)$}i,
message: 'Must be a valid URL for a gif, png, or jpg..'
}
end
I'm wondering why it tests first for the presence of :image_url, but then in the tertiary validation to make sure the image url is valid, it allows for blank responses which contradicts the first validation. I don't understand why this is supposed to work as is.
As an additional question, if the image_url is empty, how can I test if it is empty in my code? (e.g. in the product view to display a default image.)
Model validations are tested in isolation. A model is valid if and only if it passes validation for each validates statement independently.
It's probably bad-form, and evidently confusing for that allow_blank: true to be in the 4th validation, but that only applies to that single statement. The model must pass all validations to be considered valid, so the 1st statement merely imposes a tighter restriction than the 4th.
A final point, note that presence tests for non-nilness, whereas blank is defined as nil or the empty string. It is therefore possible to be both present and blank; e.g. image_url = ''. However, it remains the case that validations are tested separately in isolation.
I think maybe you are confused about the validation code? I'm a noob, and this is probably not entirely accurate: the validates keyword doesn't test for presence, it starts a block that you use to specify your validations.
As is, your code will validate the :image_url according to your specifications if it exists. If you took out allow_blank: true, then a nonexistent or blank :image_url would fail the validations.
I'm new to Rails as well and am using the same book. My understanding is that in order to stop the validation returning two errors immediately against validation (i.e. one if the field is blank and one if it doesn't have a correct file extension) it must allow_blank for the file format.
The best way I can explain it is to suggest removing the allow_blank: true code and trying to submit the description form again.
You should then see that you get both the validation errors for the field being blank and the file format being wrong.
The allow_blank therefore tells the validation to only error on the file format once the field is no longer blank.
Confused me as well which is why I ended up here!
I have two different validations for the :website attribute on my Customer model. One is the build in length helper, with the maximum set to 255, while the other is a custom validation. They both work individually, and the appropriate tests pass, but for some reason, when I run my tests with both validations, RSpec crashes to the point I have to complete exit out of Guard and restart it.
Here is my code, any way they are some how conflicting with each other? I have never experienced this before:
class Customer < Active Record::Base
...
URL_REGEX = /(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*#)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|#)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|#)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|#)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|#)|\/|\?)*)?$/i
validates :website, length: { maximum: 255 }
validate :valid_urls
private
def valid_urls
["website", "blog", "contact"].each do |attribute|
errors.add(attribute, "needs to be a valid url") if send(attribute).present? && URL_REGEX.match(send(attribute)).nil?
end
end
end
UPDATE: Thanks for the help, turned out the whole issue was just a bad regex. I had copied the regex from a stackoverflow thread, which had escaped some of the ampersands, producing a bad regex. I just now copied it from the jQuery validate source and it worked, sorry for the trouble.
Mackshkatz, can you try removing custom validation to use those provided by rails? As such:
class Customer < ActiveRecord::Base
validates :website, format: { with: URL_REGEX }, allow_blank: true, length: { maximum: 255 }
validates :blog, format: { with: URL_REGEX }, allow_blank: true
validates :contact, format: { with: URL_REGEX }, allow_blank: true
end
And see if it passes? It seems like the problem may be in complex regexp you are using.