Customize validation error message depending on the context - ruby-on-rails

I need to have different error messages for the same model depending on the form's context and location.
For the User model which validates presence of first_name:
In the back-office page it is OK to have the validation message "First name can't be blank"
In the registration page the message should be "Please type your first name"
I am looking for a clean and best-practice oriented solution, because I would like not to hack with view helper and such.
Any hint appreciated, thanks

You can use validate method in User model . Something like this
validate do |user|
if user.first_name.blank? && user.id.blank?
# id blank means the user is in registration page as he is new user.
user.errors.add(:base, "Please type your first name")
elsif user.first_name.blank?
user.errors.add(:base, "First name can't be blank")
end
end

May be using hidden_field and attr_accessor, I hope you can achieve what you want,
Form 1:
<%= f.hidden_field :check_form, :value => true %>
Form 2:
<%= f.hidden_field :check_form, :value => false %>
You need to pass check_form value also to the model.
Model:
attr_accessor :check_form
validates_presence_of :first_name, :if => :check_form_is_true?, :message => "First Name can't be blank"
validates_presence_of :first_name, :unless => :check_form_is_true? //here you need to use i18n oriented translation to show the custom error message
private
def check_form_is_true?
check_form == true
end
config/locales/en.yml
en:
activerecord:
attributes:
user:
first_name: ""
errors:
models:
user:
attributes:
first_name:
blank: "Please type your first name"
Hope it helps :)

Related

How to delete attribute name from invalid feedback below simple form checkbox?

On the screenshot, you'll see that invalid feedback is specifying the attribute name from the input, here "Adulthood". I don't want it and i can't delete it. It's not in the acceptance message from the model, not in the view. Where does it come from ?
Code from the model :
validates :adulthood, acceptance: { message: "Only adults can signup on La Voyageuse" }, on: :create
Code from the view :
<%= f.input :adulthood, as: :boolean, label: t('.adulthood?'), class:"form-checkbox" %>
You have not inspected your view part which display error log correctly. Following will help you to inspect and handle your issue
u = User.new
u.valid? # => false
u.errors.messages # => {:email=>["This field is required."], :password=>["This field is required."]}
u.errors.full_messages # # => ["Email This field is required.", "Password This field is required."]
You was just subjected to show,
u.errors.messages[:email] # => "This field is required."
Inspect and edit your code in view.
I added a custom error message on the input to fix the problem :
<%= f.input :adulthood, as: :boolean, label: t('.adulthood?'), class:"form-checkbox", error: "You need to be an adult" %>
It will be internationalized so i'll call the same i18n tag than the model.

validates :terms, acceptance: true not showing error

In my model I have the following validator:
validates :terms, acceptance: true, on: :create, allow_nil: false
attr_accessor :terms
and in my form I have:
= simple_form_for #reservation do |f|
= f.error_notification
= f.input :terms, as: :boolean
The problem is that when user not accept the terms it not showing any error, why?
Try this:
validates :terms, :acceptance => {:accept => true} , on: :create, allow_nil: false
Problem may have terms as an actual column in the table. In general validates_acceptance_of is used without such a column, in which case it defines an attribute accessor and uses that for its validation.
In order for validates_acceptance_of to work when it maps to a real table column it is necessary to pass the :accept option, like:
validates :terms, :acceptance => {:accept => true} , :on => :create , allow_nil: false
The reason for this has to do with typecasting in Active Record. When the named attribute actually exists, AR performs typecasting based on the database column type. In most cases the acceptance column will be defined as a boolean and so model_object.terms will return true or false.
When there's no such column attr_accessor :terms simply returns the value passed in to the model object from the params hash which will normally be "1" from a checkbox.
Via noodl
I may have had a similar problem (Rails 4.2.0). I created a checkbox, but it would be ignored and never report and error if unchecked. I found that adding the parameter to the .permit part of my Strong Parameters allowed it to be present.
In my view template for my _form I have something like this:
<div class="field">
<%= label_tag :tos, 'I accepts the TOS' %><br>
<%= f.check_box :tos %>
</div>
I generated my model using scaffold, so my create method start like this
def create
#thing = Thing.new(thing_params)
then near the bottom I have the following for thing_params
def thing_params
params.require(:thing).permit(:field1, :field2, :tos)
end
in my model I used the following:
validates_acceptance_of :tos
If I leave out ':toslike thisparams.require(:thing).permit(:field1, :field2) it will not pop up an error and allows it to continue. This seems counter-intuitive because if Strong Parameters is removing the :tos field then I would think the validate_acceptance would fail.
I had initially just create a checkbox without using f.check_box. Now, if I even try to call the new route without :tos" being listed as permitted, rails throws an error. There also seems to be some rails magic going on because if I remove the validates_acceptance_of from my model, I receive an NoMethodError error when rendering my view saying undefined methodtos'` for the line
<%= f.check_box :tos %>
Would be great if someone else could explain what exactly is going on as I just hacked this together from googling and guessing.

Validate field is unique compared to another field in same form

Say I have two fields in a new or edit form:
<%= f.text_field :email %>
<%= f.text_field :parent_email %>
How, in my model, can I validate that parent_email is different from email? The exclusion option seems like it might work, but I can't figure out how to access the email field's value within the model. Do I need to implement this in the controller instead?
validates :parent_email, exclusion: self.email # doesn't work, nor does :email
The following should work (but I guess there are cooler solutions out there):
class User
validate :email_differs_from_parent_email
private
def email_differs_from_parent_email
if email == parent_email
errors.add(:parent_email, "parent_email must differ from email")
end
end
end

Customize error message with simple_form

I'm using the simple_form gem. I want to customize the error message displayed when a user fails validations. How can I accomplish this?
You can declare the content of the
error message in your model:
validates_length_of :name, :minimum => 5, :message => "blah blah blah"
You can set id or class for your
error tag:
<%= f.input :name, :error_html => { :id => "name_error"} %>
Then you can use CSS for the styling.
And you can use
<%= f.error :name, :id => "name_error" %>
and you'll get
<span class="error" id="name_error">is too short (minimum is 5 characters)</span>
I dont know if it is any different for simple_form gem.
For content of error messages to be changed, you can use the :message attribute in the model.
class User < ActiveRecord::Base
validates :email, {:presence => true, :message => "is not filled up."}
end
Now the validation message will be Email is not filled up. If you want the field name also to be changed(Email to E-mail address something like that ), the approach now is to define it in locales.rb file like this
# config/locales/en.yml
en:
activerecord:
attributes:
user:
email: "E-mail address"
See link for details on locales. Another approach is to define in the model, humanized attributes like this:
class User < ActiveRecord::Base
validates :email, {:presence => true, :message => "is not filled up."}
HUMANIZED_ATTRIBUTES = {
:email => "E-mail address",
...(other fields and their humanized names)
...
}
def self.human_attribute_name(attr, options={})
HUMANIZED_ATTRIBUTES[attr.to_sym] || super
end
end
For customizing style of validation message we will have to edit the style for
#errorExplanation and .fieldWithErrors,in the scaffold.css stylesheet.
You can easily change the default error message comes in the translation file, which is found in config/locales/simple_form.en.yml.
In the specific initializer, config/initializers/simple_form.rb you can overrule the default options how the html is generated.
Hope this helps.
For completeness, I would like to add that formtastic is an easier choice to start with, because it has a default layout. I like simple_form a lot, but it does not offer any formatting out of the box, but that is their intention. With Formtastic it is very hard (impossible) to change the generated html, and with simple_form can you can completely mold the generated html to your liking. This is especially useful if you have a designer, and the forms you generate have to generate the same html. So if you are getting started, formtastic will give you nicer-looking results quicker. Also note that it is quite easy to switch, because the syntax is almost identical.
There is another solution explained here that wasn't mentioned in the answers. You can directly override the error messages from the views, in the form itself. For example:
<%= f.input :last_name,
placeholder: 'last_name',
error: 'This is a custom error message',
required: true,
class: 'form-field',
autofocus: true,
input_html: { autocomplete: "last_name" } %>
It is however not advised as it is not DRY, you would need to override it in every field.

Rails - update_attributes coming up against validations

So I've got a user model, with login, email address, password, password confirmation, name, avatar (picture), etc. There are validations on the first 5, basically stating that all 5 need to exist in order to create a new model.
However, this causes problems for me where updates are concerned.
I've got an edit page, where the user can only edit their name and avatar. I'm not currently intending to let them change their login, and I wish to do an email and password change from a different page.
So the edit form looks like this:
<% form_for #user, :html => { :multipart => true } do |u| %>
<p>
<label>Name:</label>
<%= u.text_field :name %>
</p>
<p>
<label>Avatar:</label>
<%= display_user_avatar %>
<%= u.file_field :avatar%>
</p>
<p>
<%= submit_tag %>
</p>
<% end %>
If I attempt to do a #user.update_attributes(params[:user]), then because the only 2 params are name and avatar, the update fails, since stuff like password, password confirmation, email, etc are required to validate the entry, and they simply don't exist in that form.
I can get around this by doing #user.update_attribute(:name, params[:user][:name]), but then I worry about whether avoiding validations is a Good Thing™ or not. Especially with regards to something like password updates, where I do need to validate the new password.
Is there another way?
And if I were to do this simply using update_attribute for :name and :avatar, how would I go about doing it?
Would this work?
params[:user].each do |attribute|
#user.update_attribute(attribute, params[:user][attribute])
end
Is this an acceptable way to do this...?
--edit as follow up --
Okie, I tried as you suggested and did
def update
#user = User.find_by_login(params[:id])
if #user.update_attributes!(params[:user])
redirect_to edit_user_path(#user)
else
flash[:notice] = #user.errors
redirect_to edit_user_path(#user)
end
end
So it's doing the ! version, and the exception caught & displayed in the browser is:
Validation failed: Password is too short (minimum is 5 characters)
The info in the server log is:
Processing UsersController#update (for 127.0.0.1 at 2010-07-18 11:56:59) [PUT]
Parameters: {"user"=>{"name"=>"testeeeeee"}, "commit"=>"Save changes", "action"=>"update", "_method"=>"put", "authenticity_token"=>"BMEGRW/pmIJVs1zlVH2TtZX2TQW8soeCXmMx4kquzMA=", "id"=>"tester", "controller"=>"users"}
Urm. Looking at this, I just realised that it is submitting "id"=>"tester". Now, I have my routes set up so that it is showing the users login name, instead of the user_id... Could that be why? It is attempting to find a update a user with user_id == tester, but since it doesn't exist, it attempts to create one instead?
Is it actually something I'm doing wrong due to the route?
Hmmm... rake routes tells me that the route is:
edit_user GET /users/:id/edit(.:format) {:action=>"edit", :controller=>"users"}
PUT /users/:id(.:format) {:action=>"update", :controller=>"users"}
And I set up the route like that in the user.rb file:
def to_param
"#{login}"
end
but it's definitely been displaying login instead of id all this time. But I'm also doing right at the beginning of the update action, a #user = User.find_by_login(params[:id]), and then updating that #user.
I'm very confused. >.<
Second update:
My User.rb validation stuff are as follows:
validates_length_of :login, :within => 3..20
validates_length_of :password, :within => 5..20
validates_presence_of :login, :email, :password, :password_confirmation, :salt, :name, :on => :create
validates_uniqueness_of :login, :case_sensitive => false
validates_confirmation_of :password
validates_format_of :email, :with => /^([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :message => "format is invalid."
attr_accessor :password, :password_confirmation
And the hashed_password section is here:
def password=(pass)
#password = pass
self.salt = User.random_string(10) if !self.salt?
self.hashed_password = User.encrypt(#password, self.salt)
end
u.attributes gives me
>> u.attributes
=> {"salt"=>"NHpH5glxsU", "name"=>"test er", "avatar_updated_at"=>nil, "updated_at"=>Sat Jul 17 07:04:24 UTC 2010, "avatar_file_size"=>nil, "avatar_file_name"=>nil, "hashed_password"=>"84f8675c1ed43ef7f8645a375ea9f867c9a25c83", "id"=>1, "avatar_content_type"=>nil, "login"=>"tester", "email"=>"tester#tester.com", "created_at"=>Fri May 07 10:09:37 UTC 2010}
Urmmm... Ok, so it's what you said, about the virtual attribute password being actually nonexistent...
So how do I get around that?
Bugger, here I thought I was being smart fiddling with my own authentication code...
How easy is it to change to one of those authentication plugins? Will I need to create a new User model? Or should the plugin be able to work with my current one?
Thanks for all the help so far, btw! :D
I've checked this and a partial update of just 2 attributes via update_attributes works fine. All the other attributes are left with their previous values, meaning that the validation shouldn't fail. A couple of things to try:
In your controller action are you loading the user via User.find? i.e. are you starting from a valid model.
Are you sure the update is failing due to validation errors? Try replacing the update_attributes with update_attributes!. The latter will throw an exception if the update fails due to validation. Or check #user.errors after the attempted update to confirm which validation has failed.
Update
If User.find_by_login doesn't find a matching record it will return nil and won't create a new record for you. Is it possible that the tester user in the database has a password that is too short? Maybe that user was created before you put the validations in your code? Are you using any kind of plugin or callback to encrypt user passwords before saving the records? Is password actually a virtual attribute that isn't saved and the actual password is in a field like encrypted_password?
Try this from script/console (use the same environment as you are testing the app with - development or production)
> user = User.find_by_login 'tester'
> user.valid?
> user.attributes
The user.valid? will return true of false and will tell you whether the user is valid to start with, before you even try an update.
Update 2 (fixing the validation)
In terms of fixing your own code, you could add a method like the following to your User model:
def password_validation_required?
hashed_password.blank? || !#password.blank?
end
and then update all your password related validation rules so that they only apply if this method returns true e.g.
validates_length_of :password, :within => 5..20,
:if => :password_validation_required?
What this is saying is only do the password validation rule if we don't yet have a hashed_password (on a new user for example) or if a new plain text password has been specified via password=. If the user already has a password and it is being left unchanged then skip the password validation.
You are right to be considering using a plugin though. Writing your own authentication code can be an interesting excercise and can be required if you have some unusual requirements. The down side is that there can be security issues that you haven't thought of. Retrofitting something like restful_authentication to your app shouldn't be too bad. You might just need to rename one or two fields on your User model.

Resources