Allow blank on should validate_uniqueness_of - ruby-on-rails

Using shoulda, any ideas how to allow blank when having this test:
should validate_uniqueness_of(:email)
Thanks.

I have figured it out!! The validate_uniqueness_of relies on the value for the attribute you are currently testing on the first record in the database. So, if the first record in the database happens to be blank or nul for that attribute your test will always give an error like this: "Failure/Error: it { should validate_uniqueness_of(:customer_number).case_insensitive.scoped_to(:user_id) } Expected errors to include "has already been taken" when customer_number is set to nil, got no errors".
So how do we fix this? Make sure you delete all existing records and that the first record contains a filled in value for the attribute.
model:
validates_uniqueness_of :customer_number, :scope => :user_id, :case_sensitive => false, :allow_blank => true, :allow_nil => true
model spec:
describe 'Validation' do
it { should allow_value('').for(:customer_number) }
describe 'When a user exists with the same customer_number and user' do
before(:each) do
Customer.destroy_all
# Saving a single customer to validate uniqueness.
#existing = Factory(:customer, :customer_number => 'test') # If you leave customer_number blank here the matcher will check if it can save a new record with a blank customer_number which is possible since we allow blanks. So make sure your first record has a filled in value!!
end
subject do
Factory.build(:customer)
end
it { should validate_uniqueness_of(:customer_number).case_insensitive.scoped_to(:user_id) }
end
end
Hope this cleared up some people's issues :)

should allow_value(" ").for(:email)
should allow_value(nil).for(:email)

maybe
should validate_uniqueness_of(:email, :allow_blank => true)

Related

validation error messages are not set for associated record

Let's say we have the following context:
class Company
belongs_to :address, validate: true
end
class Address
validates :line1, presence: true
end
company = Company.new({ ... })
company.address = Address.new({ line1: '' })
company.save
puts company.errors[:address] # nothing
puts company.errors[:"address.line1"] # can't be blank
How can I make the validations errors to be set to the associated record and NOT to the owning record? This makes nested forms much more complicated because it's harder to reuse partials for these forms.
I actually need to have:
puts company.address.errors[:line1] # can't be blank
Apparently it does work as intended. Just a hitch in my code made me think it doesn't. Feel ashamed now...
custom validation methods
validate :check_address, :on => :create
def check_address
if self.address.line1.blank?
errors.add(:line1, "Please fill line 1.")
end
end

Validating a existence of a beta code before creating a User

model: User
has_one :beta_invite
before_save :beta_code_must_exist
def beta_code_must_exist
if beta_invite_id == beta_invite.find_by_name(beta_invite.id)
user
else
nil
end
end
model: BetaInvite
has_many :users
What I`m trying to do is check for the existence of a beta invite in DB, before allowing the user to be saved.
Since the User will be passing in the BetaInvite name into the field, I would like to check if it matches any existing Codes in the DB.
Hope I didn`t mix things up too much.
Would appreciate any help with this problem.
Add a text field to the form for :beta_code
Add an attr_accessor for that field: attr_accessor :beta_code
Then add the following line to the model (Assumes you only want to do this check on user creation):
validate :beta_code_must_exist, :on => :create
Change beta_code_must_exist to add an error to the form. Also be sure to properly cast :beta_code into the correct type.
Warning untested code below
def beta_code_must_exist
#invite = BetaInvite.find_by_name(beta_code)
if #invite.empty?
errors.add(:beta_code, "is not a valid invite code")
else
beta_invite_id = #invite.id
end
end
Use :inclusion with the :in option. You can supply :in with any enumerable:
validates :beta_invite, :inclusion => { :in => BetaInvite.all,
:message => "%{value} is not a valid beta invite code" }
Source: Rails Active Record Validation

validate and update single attribute rails

I have the following in my user model
attr_accessible :avatar, :email
validates_presence_of :email
has_attached_file :avatar # paperclip
validates_attachment_size :avatar,
:less_than => 1.megabyte,
:message => 'Image cannot be larger than 1MB in size',
:if => Proc.new { |imports| !imports.avatar_file_name.blank? }
in one of my controllers, I ONLY want to update and validate the avatar field without updating and validating email.
How can I do this?
for example (this won't work)
if #user.update_attributes(params[:user])
# do something...
end
I also tried with update_attribute('avatar', params[:user][:avatar]), but that would skip the validations for avatar field as well.
You could validate the attribute by hand and use update_attribute, that skips validation. If you add this to your User:
def self.valid_attribute?(attr, value)
mock = self.new(attr => value)
if mock.valid?
true
else
!mock.errors.has_key?(attr)
end
end
And then update the attribute thusly:
if(!User.valid_attribute?('avatar', params[:user][:avatar])
# Complain or whatever.
end
#user.update_attribute('avatar', params[:user][:avatar])
You should get your single attribute updated while only (manually) validating that attribute.
If you look at how Milan Novota's valid_attribute? works, you'll see that it performs the validations and then checks to see if the specific attr had issues; it doesn't matter if any of the other validations failed as valid_attribute? only looks at the validation failures for the attribute that you're interested in.
If you're going to be doing a lot of this stuff then you could add a method to User:
def update_just_this_one(attr, value)
raise "Bad #{attr}" if(!User.valid_attribute?(attr, value))
self.update_attribute(attr, value)
end
and use that to update your single attribute.
A condition?
validates_presence_of :email, :if => :email_changed?
Have you tried putting a condition on the validates_presence_of :email ?
http://ar.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#M000083
Configuration options:
if - Specifies a method, proc or string to call to determine if the validation should occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The method, proc or string should return or evaluate to a true or false value.
unless - Specifies a method, proc or string to call to determine if the validation should not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The method, proc or string should return or evaluate to a true or false value.
I am assuming you need this, because you have a multi-step wizard, where you first upload the avatar and the e-mail is filled in later.
To my knowledge, with your validations as they are, I see no good working solution. Either you validate all, or you update the avatar without validations. If it would be a simple attribute, you could check if the new value passes the validation seperately, and then update the model without validations (e.g. using update_attribute).
I can suggest two possible alternative approaches:
either you make sure that the e-mail is always entered first, which I believe is not a bad solution. And then, with each save, the validation is met.
otherwise, change the validation. Why would you declare a validation on a model, if there are records in the database that do not meet the validation? That is very counter-intuitive.
So I would propose something like this:
validate :presence_of_email_after_upload_avatar
def presence_of_email_after_upload_avatar
# write some test, when the email should be present
if avatar.present?
errors.add(:email, "Email is required") unless email.present?
end
end
Hope this helps.
Here is my solution.
It keeps the same behaviour than .valid? method, witch returns true or false, and add errors on the model on witch it was called.
class MyModel < ActiveRecord::Base
def valid_attributes?(attributes)
mock = self.class.new(self.attributes)
mock.valid?
mock.errors.to_hash.select { |attribute| attributes.include? attribute }.each do |error_key, error_messages|
error_messages.each do |error_message|
self.errors.add(error_key, error_message)
end
end
self.errors.to_hash.empty?
end
end
> my_model.valid_attributes? [:first_name, :email] # => returns true if first_name and email is valid, returns false if at least one is not valid
> my_modal.errors.messages # => now contain errors of the previous validation
{'first_name' => ["can't be blank"]}

Shoulda rspec matchers :on => :create

I am using some of the Shoulda rspec matchers to the test my model, one of them being:
describe Issue do
it { should_not allow_value("test").for(:priority) }
end
My problem with this is that my validation in my model looks like this:
validates_format_of :priority, :with => /^(Low|Normal|High|Urgent)$/, :on => :update
So when running this test I get:
1) 'Issue should not allow priority to be set to "test"' FAILED
Expected errors when priority is set to "test", got errors: category is invalid (nil)title can't be blank (nil)profile_id can't be blank (nil)
The validation isn't being triggered because it only runs on an update, how can I use these shoulda matchers on an update vs. a create?
I think shoulda should handle this better. I ran into this because I only want to run my uniqueness validation check for my User model when creating new users. It's a waste of a database query doing it on update, since I don't allow usernames to be changed:
validates :username, :uniqueness => { :case_sensitive => false, :on => :create },
Fortunately you can get around this by explicitly defining the "subject":
describe "validation of username" do
subject { User.new }
it { should validate_uniqueness_of(:username) }
end
This way it's only testing on a new instance. For your case, you can probably just change the subject to be something saved in the database already, with all the necessary fields set.

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