Excess errors on model from somewhere - ruby-on-rails

I have a User model, and use an acts_as_authentic (from authlogic) on it. My User model have 3 validations on username and looks as following:
User < ActiveRecord::Base
acts_as_authentic
validates_presence_of :username
validates_length_of :username, :within => 4..40
validates_uniqueness_of :username
end
I'm writing a test to see my validations in action. Somehow, I get 2 errors instead of one when validating a uniqueness of a name. To see excess error, I do the following test:
describe User do
before(:each) do
#user = Factory.build(:user)
end
it "should have a username longer then 3 symbols" do
#user2 = Factory(:user)
#user.username = #user2.username
#user.save
puts #user.errors.inspect
end
end
I got 2 errors on username: #errors={"username"=>["has already been taken", "has already been taken"]}.
Another case of problem is when I set username to nil. Somehow I get four validation errors instead of three: #errors={"username"=>["is too short (minimum is 3 characters)", "should use only letters, numbers, spaces, and .-_# please.", "can't be blank", "is too short (minimum is 4 characters)"]}
I think authlogic is one that causes this strange behaviour. But I can't even imagine on how to solve that. Any ideas?

I think this is because authlogic has some build in validations and both them and your validations are run.
Google seems to give some answers to this topic. This one is for example for password field.

Related

Validate on Update only if conditions are met

I am working on adding some new validation to an app. The entire idea is to make sure that when a user updates their username it does not violate our username policy.
This is the current incantation of the validator:
validates_format_of :username, with: /[0-9a-zA-Z_]+/,
on: :update,
if: lambda { |u| u.username_changed? }
Even with this validation bad characters make it through.
Here is my spec that I am using:
it "validates and does not update a user with an invalid username" do
user.update_attributes(username: "k~!tten")
expect(user.username).not_to eq "k~!tten"
end
Any help with this is greatly appreciated.
Username is "k~!tten" only on the model. It has not been saved to the database due to validation failure. Instead of:
expect(user.username).not_to eq "k~!tten"
use the below to assert that the username does not pass validation:
expect(user.username).not_to be_valid

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.

How can I override the default validations that come with Authlogic?

I have my own password/login rules that I want to implement such as:
Password: At least one number, one lower case character and one upper case character
Login: Alphanumeric and between 4 and 15 characters long
etc...
Currently when I do not enter a password and login in my form, I get the following errors (from Authlogic's default validations):
Login is too short (minimum is 3 characters)
Password is too short (minimum is 4 characters)
Password confirmation is too short (minimum is 4 characters)
These validation rules are not in my model, they come from the Authlogic gem. I know there are configurations that I can add using:
acts_as_authentic do |config|
config.validate_password_field = false
end
The problem is that I can't find good documentation for these configurations and when I try the one above so I can use my own, it complains:
undefined method 'password_confirmation' for #<User:0x7f5fac8fe7c0>
Doing this:
acts_as_authentic do |config|
config.validate_password_field = false
# added this so it might stop complaining
config.require_password_confirmation = true
end
Does nothing.
Is there a way to have Authlogic require password confirmation while ignore all other validation so that I can control this?
The undefined method 'password_confirmation' can be avoided simply by adding this line in your model:
attr_accessor :password_confirmation
If you want to handle the confirmation yourself, then just add:
validates_confirmation_of :password
If you dive into the code, you will find this in password.rb:
if require_password_confirmation
validates_confirmation_of :password, validates_confirmation_of_password_field_options
validates_length_of :password_confirmation, validates_length_of_password_confirmation_field_options
end

Rails 2.3 uniqueness validation - how can I capture the value causing the error

I'm trying to capture the value that's throwing a uniqueness error (or for that matter, any other type of built-in validation) to display it in the :message option. Here's what I tried (didn't work)
# inside the model
validate_uniqueness_of :name, :message => "#{name} has already been taken" # also tried using #{:name}
I could use a custom validation, but this beats the point of using something that's already integrated into AR. Any thoughts? thanks.
Try this interpolation technique:
validate_uniqueness_of :name, :message => "%{value} has already been taken"
The RailsGuide for Active Record Validations and Callbacks shows an example where %{value} is interpolated in a custom error message:
:message => "%{value} is not a valid size"
I looked at the validates_each documentation and can see the validate block is passed three properties: |record, attr, value|. All three can be accessed with %{model}, %{attribute} and %{value}.
While this is limited, since it only gives you access to three properties, thankfully that is all you need.
Try self.name
validates_uniqueness_of :name, :message => "#{self.name} has already been taken" # also tried using #{:name}
Also validate_uniqueness_of is wrong it should be validates_uniqueness_of
If this not works use validate method and comment line validates_uniqueness_of
def validate
name= User.find_by_name(self.name) #Assuming User is your Model Name
unless name.blank?
self.errors.add :base, "#{self.name} has already been taken"
end
end

How to test custom messages thrown back by a models validation in Rails

I have this validation in my user model.
validates_uniqueness_of :email, :case_sensitive => false,
:message => "Some funky message that ive cleverly written"
In my tests I want to ensure that when a user enters a dupe email address that my message definately gets shown but without having to duplicate the error string from above in my test. I dont like that because im sure the message will change as i start thinking about copy. Does rails store these error messages - something which i can call in my tests?
Ive done a general test of
assert #error_messages[:taken] , user.errors.on(:email)
but that would pass on any of the other email related errors ive set validations up to catch i.e. incorrect formating, blank etc.
I made a quick test, and it looks like the error messages are sorted in the order you wrote your validation statements in your model class (top-down).
That means, you can find the error message for the first validation on an attribute at the first place in the errors array:
user.errors.on(:email)[0]
So, if your user model class contains something like this:
validates_presence_of :email
validates_uniqueness_of :email, :case_sensitive => false, :message => "Some funky message that ive cleverly written"
validates_length_of :email
...you'll find your 'funky message' at user.errors.on(:email)[1], but only if at least validates_presence_of triggers an error, too.
Concerning your specific problem:
The only way I could think of to not repeat your error message in the test, is to define a constant in your user model and use this instead of directly typing a message for that validation:
EMAIL_UNIQUENESS_ERROR_MESSAGE = "Some funky message that ive cleverly written"
...
validates_uniqueness_of :email, :case_sensitive => false, :message => EMAIL_UNIQUENESS_ERROR_MESSAGE
In your test, you could use this constant, too:
assert_equal User::EMAIL_UNIQUENESS_ERROR_MESSAGE, user.errors.on(:email)[1]
In rspec,
it "should validate uniqueness of email" do
existing_user = User.create!(:email => email)
new_user = User.create!(:email => existing_user.email)
new_user.should_not be_valid
new_user.errors.on(:email).should include("Some funky message that ive cleverly written")
end

Resources