Fully custom validation error message with Rails
Asked Answered
T

18

286

Using Rails I'm trying to get an error message like "The song field can't be empty" on save. Doing the following:

validates_presence_of :song_rep_xyz, :message => "can't be empty"

... only displays "Song Rep XYW can't be empty", which is not good because the title of the field is not user friendly. How can I change the title of the field itself ? I could change the actual name of the field in the database, but I have multiple "song" fields and I do need to have specific field names.

I don't want to hack around rails' validation process and I feel there should be a way of fixing that.

Tenderize answered 30/4, 2009 at 19:5 Comment(0)
C
471

Now, the accepted way to set the humanized names and custom error messages is to use locales.

# config/locales/en.yml
en:
  activerecord:
    attributes:
      user:
        email: "E-mail address"
    errors:
      models:
        user:
          attributes:
            email:
              blank: "is required"

Now the humanized name and the presence validation message for the "email" attribute have been changed.

Validation messages can be set for a specific model+attribute, model, attribute, or globally.

Conflict answered 18/5, 2010 at 16:43 Comment(14)
Setting field names by language is great, but how does one set a custom error messages by language?Twila
If you are using mongoid, replace activerecord: with mongoid:Halsy
@graywh: Where should questions about an answer be posted, if not in the comments? Here's the I18n guide: guides.rubyonrails.org/i18n.htmlLuce
By the way: if you pass a symbol in for the message parameter of your validator in Rails 3.1.3, it will tell you the scope it was looking for as it won't e found, so you know exactly what to put in your locales yml.Heinrik
Well, this is fine and all, but what if naively prepending the column name (no matter how human readable it is) would lead to completely f-uped grammar (especially in non-english languages)? Do I really need to use errors.add :base, msg? I'd like to know which column the error is about, so I can display it at the correct form field.Waste
@Waste What language did you have in mind? Here's some sample locales in various languages: github.com/svenfuchs/rails-i18n/tree/master/rails/locale (This URL is given in the generated config/locales/en.yml in a new application.)Conflict
@Conflict Maybe I'm missing something, but doesn't it always prepend the column name before the message? Even in English I'd like to have e.g. The password is wrong. or The email address is not valid. instead of Password is wrong. and Email is not valid..Waste
@Waste Ah, then you probably want to look at the implementation of ActiveModel::Errors#full_message and see section 5.2.2 of the Rails I18n guide guides.rubyonrails.org/…Conflict
Don't forget to call errors.full_messages to actually see the humanized name.Ossicle
And if you're using MongoMapper, replace activerecord: with mongo_mapper:Hectograph
How could we add validation for format : like greater_that_or_equal_toLashonda
What if the attribute is actually a belongs_to relation?. It ain't working if I treat it like an attribute.Mirabel
where can i get the full list of keywords? e.g. for "email already taken" what should i put in place of blank field??Ise
In Rails 6, to enable that custom format for specific models or attributes, you need to enable it by adding config.active_model.i18n_customize_full_message = true in config/application.rb That guide helped me: bigbinary.com/blog/…Gluten
A
72

In your model:

validates_presence_of :address1, message: 'Put some address please' 

In your view

<% m.errors.each do |attr, msg|  %>
 <%= msg %>
<% end %>

If you do instead

<%= attr %> <%= msg %>

you get this error message with the attribute name

address1 Put some address please

if you want to get the error message for one single attribute

<%= @model.errors[:address1] %>
Amboina answered 5/3, 2012 at 16:42 Comment(5)
That's not an acceptable solution. What if I want the default behaviour for all the other attributes (attr + msg)?Tver
There you go.. you can play with those 2 things and make it workAmboina
You have to use a symbol so it will look up in your yml files, like validates_presence_of :address1, :message => :put_some_address_please Amboina
This is not acceptable, as the field name gets includedHaustellum
I think that I have improved this answer to be acceptable, please check it out.Deland
I
69

Try this.

class User < ActiveRecord::Base
  validate do |user|
    user.errors.add_to_base("Country can't be blank") if user.country_iso.blank?
  end
end

I found this here.

Update for Rails 3 to 6:

validate do |user|
  user.errors.add(:base, "Country can't be blank") if user.country_iso.blank?
end

Here is another way to do it. What you do is define a human_attribute_name method on the model class. The method is passed the column name as a string and returns the string to use in validation messages.

class User < ActiveRecord::Base

  HUMANIZED_ATTRIBUTES = {
    :email => "E-mail address"
  }

  def self.human_attribute_name(attr)
    HUMANIZED_ATTRIBUTES[attr.to_sym] || super
  end

end

The above code is from here

Icefall answered 30/4, 2009 at 20:3 Comment(3)
The problem is that my field is called :song_rep_xyz (well, something complicated), which is not user friendlyTenderize
for Rails 3, "def self.human_attribute_name(attr)" needs to be changed into "def self.human_attribute_name(attr, options={})", otherwise it returns an errorLandslide
Thanks for this. I needed something that worked for Rails 2. (Yes, poor me... :)Sieber
M
20

Yes, there's a way to do this without the plugin! But it is not as clean and elegant as using the mentioned plugin. Here it is.

Assuming it's Rails 3 (I don't know if it's different in previous versions),

keep this in your model:

validates_presence_of :song_rep_xyz, :message => "can't be empty"

and in the view, instead of leaving

@instance.errors.full_messages

as it would be when we use the scaffold generator, put:

@instance.errors.first[1]

And you will get just the message you specified in the model, without the attribute name.

Explanation:

#returns an hash of messages, one element foreach field error, in this particular case would be just one element in the hash:
@instance.errors  # => {:song_rep_xyz=>"can't be empty"}

#this returns the first element of the hash as an array like [:key,"value"]
@instance.errors.first # => [:song_rep_xyz, "can't be empty"]

#by doing the following, you are telling ruby to take just the second element of that array, which is the message.
@instance.errors.first[1]

So far we are just displaying only one message, always for the first error. If you wanna display all errors you can loop in the hash and show the values.

Hope that helped.

Mimosa answered 18/2, 2011 at 22:23 Comment(1)
Lovely. I was looking for getting a one-liner message for my API app and you have shown me how to manipulate the errors class. TYWomanhood
B
17

Rails3 Code with fully localized messages:

In the model user.rb define the validation

validates :email, :presence => true

In config/locales/en.yml

en:  
  activerecord:
    models: 
      user: "Customer"
    attributes:
      user:
        email: "Email address"
    errors:
      models:
        user:
          attributes:
            email:
              blank: "cannot be empty"
Brighton answered 18/8, 2011 at 13:34 Comment(0)
V
16

In the custom validation method use:

errors.add(:base, "Custom error message")

as add_to_base has been deprecated.

errors.add_to_base("Custom error message")

Vermilion answered 17/5, 2012 at 12:40 Comment(0)
D
14

One solution might be to change the i18n default error format:

en:
  errors:
    format: "%{message}"

Default is format: %{attribute} %{message}

Divergent answered 22/4, 2017 at 23:21 Comment(0)
G
13

Related to the accepted answer and another answer down the list:

I'm confirming that nanamkim's fork of custom-err-msg works with Rails 5, and with the locale setup.

You just need to start the locale message with a caret and it shouldn't display the attribute name in the message.

A model defined as:

class Item < ApplicationRecord
  validates :name, presence: true
end

with the following en.yml:

en:
  activerecord:
    errors:
      models:
        item:
          attributes:
            name:
              blank: "^You can't create an item without a name."

item.errors.full_messages will display:

You can't create an item without a name

instead of the usual Name You can't create an item without a name

Garnish answered 26/10, 2016 at 5:17 Comment(0)
E
12

I recommend installing the custom_error_message gem (or as a plugin) originally written by David Easley

It lets you do stuff like:

validates_presence_of :non_friendly_field_name, :message => "^Friendly field name is blank"
Elnaelnar answered 30/4, 2009 at 20:52 Comment(5)
I have used this plugin in the past with great success though it does not appear to be regularly maintained anymore.Twila
you can alse install it as a gem for rails 3. just add gem "custom_error_message" to your Gemfile - see github for more detailsDemetria
Exactly what I neededPenetrance
@DickieBoy I confirm that nanamkim's fork (github.com/nanamkim/custom-err-msg) works with Rails 5. It actually plays nice with the accepted answer. I'll write this up as a separate answer.Garnish
@Garnish For the life of me I can't remember the usecase around this, but thanks for the reply! I'll be sure to remember it for the future.Lorrainelorrayne
C
9

Here is another way:

If you use this template:

<% if @thing.errors.any? %>
  <ul>
    <% @thing.errors.full_messages.each do |message| %>
      <li><%= message %></li>
    <% end %>
  </ul>
<% end %>

You can write you own custom message like this:

class Thing < ActiveRecord::Base

  validate :custom_validation_method_with_message

  def custom_validation_method_with_message
    if some_model_attribute.blank?
      errors.add(:_, "My custom message")
    end
  end

This way, because of the underscore, the full message becomes " My custom message", but the extra space in the beginning is unnoticeable. If you really don't want that extra space at the beginning just add the .lstrip method.

<% if @thing.errors.any? %>
  <ul>
    <% @thing.errors.full_messages.each do |message| %>
      <li><%= message.lstrip %></li>
    <% end %>
  </ul>
<% end %>

The String.lstrip method will get rid of the extra space created by ':_' and will leave any other error messages unchanged.

Or even better, use the first word of your custom message as the key:

  def custom_validation_method_with_message
    if some_model_attribute.blank?
      errors.add(:my, "custom message")
    end
  end

Now the full message will be "My custom message" with no extra space.

If you want the full message to start with a word capitalized like "URL can't be blank" it cannot be done. Instead try adding some other word as the key:

  def custom_validation_method_with_message
    if some_model_attribute.blank?
      errors.add(:the, "URL can't be blank")
    end
  end

Now the full message will be "The URL can't be blank"

Cruller answered 22/10, 2015 at 23:35 Comment(2)
ooo, you can even do errors.add(:_, 'foobar') and get ' foobar' as the messageDasha
Good approach, I build my own implementation based on the examples here. Thank you!Chadbourne
U
6

Just do it the normal way:

validates_presence_of :email, :message => "Email is required."

But display it like this instead

<% if @user.errors.any? %>
  <% @user.errors.messages.each do |message| %>
    <div class="message"><%= message.last.last.html_safe %></div>
  <% end %>
<% end %>

Returns

"Email is required."

The localization method is definitely the "proper" way to do this, but if you're doing a little, non-global project and want to just get going fast - this is definitely easier than file hopping.

I like it for the ability to put the field name somewhere other than the beginning of the string:

validates_uniqueness_of :email, :message => "There is already an account with that email."
Unreligious answered 24/11, 2011 at 14:26 Comment(0)
S
3

Here is my code that can be useful for you in case you still need it: My model:

validates :director, acceptance: {message: "^Please confirm that you are a director of the company."}, on: :create, if: :is_director?

Then I have created a helper to show messages:

module ErrorHelper
  def error_messages!
    return "" unless error_messages?
    messages = resource.errors.full_messages.map { |msg|
       if msg.present? && !msg.index("^").nil?
         content_tag(:p, msg.slice((msg.index("^")+1)..-1))
       else
         content_tag(:p, msg)
       end
    }.join

    html = <<-HTML
      <div class="general-error alert show">
        #{messages}
      </div>
    HTML

    html.html_safe
  end

  def error_messages?
    !resource.errors.empty?
  end
end
Slovenly answered 2/8, 2017 at 11:1 Comment(1)
+1 for helping handle the case of checkbox acceptance messages ... I wonder if there's a way to do it without the custom error helper that examines the messages looking for the "^" string?Marismarisa
D
2

If you want to list them all in a nice list but without using the cruddy non human friendly name, you can do this...

object.errors.each do |attr,message|
  puts "<li>"+message+"</li>"
end
Dunaville answered 23/6, 2011 at 15:5 Comment(0)
G
1

In your view

object.errors.each do |attr,msg|
  if msg.is_a? String
    if attr == :base
      content_tag :li, msg
    elsif msg[0] == "^"
      content_tag :li, msg[1..-1]
    else
      content_tag :li, "#{object.class.human_attribute_name(attr)} #{msg}"
    end
  end
end

When you want to override the error message without the attribute name, simply prepend the message with ^ like so:

validates :last_name,
  uniqueness: {
    scope: [:first_name, :course_id, :user_id],
    case_sensitive: false,
    message: "^This student has already been registered."
  }
Gracious answered 2/12, 2016 at 9:59 Comment(3)
does not work with rails 5.1 / ruby 2.4 ? getting the model name in that scopeAnkus
@Ankus Works for me on Rails 5.1.2, Ruby 2.4.1p111. Can you share your code?Gracious
i guess i had to look further, you can check the code and his answer there https://mcmap.net/q/109916/-comparing-rspec-custom-activerecord-recordinvalid-errors-messages/102133Ankus
E
0

I tried following, worked for me :)

1 job.rb

class Job < ApplicationRecord
    validates :description, presence: true
    validates :title, 
              :presence => true, 
              :length => { :minimum => 5, :message => "Must be at least 5 characters"}
end

2 jobs_controller.rb

def create
      @job = Job.create(job_params)
      if @job.valid?
        redirect_to jobs_path
      else
        render new_job_path
      end     
    end

3 _form.html.erb

<%= form_for @job do |f| %>
  <% if @job.errors.any? %>
    <h2>Errors</h2>
    <ul>
      <% @job.errors.full_messages.each do |message|%>
        <li><%= message %></li>
      <% end %>  
    </ul>
  <% end %>
  <div>
    <%= f.label :title %>
    <%= f.text_field :title %>
  </div>
  <div>
    <%= f.label :description %>
    <%= f.text_area :description, size: '60x6' %>

  </div>
  <div>
    <%= f.submit %>
  </div>
<% end %> 
Electrolyse answered 3/12, 2016 at 23:8 Comment(0)
E
0

A unique approach I haven't seen anyone mention!

The only way I was able to get all the customisation I wanted was to use an after_validation callback to allow me to manipulate the error message.

  1. Allow the validation message to be created as normal, you don't need to try and change it in the validation helper.

  2. create an after_validation callback that will replace that validation message in the back-end before it gets to the view.

  3. In the after_validation method you can do anything you want with the validation message, just like a normal string! You can even use dynamic values and insert them into the validation message.


#this could be any validation
validates_presence_of :song_rep_xyz, :message => "whatever you want - who cares - we will replace you later"

after_validation :replace_validation_message

def replace_validation_message
    custom_value = #any value you would like
    errors.messages[:name_of_the_attribute] = ["^This is the replacement message where 
    you can now add your own dynamic values!!! #{custom_value}"]
end

The after_validation method will have far greater scope than the built in rails validation helper, so you will be able to access the object you are validating like you are trying to do with object.file_name. Which does not work in the validation helper where you are trying to call it.

Note: we use the ^ to get rid of the attribute name at the beginning of the validation as @Rystraum pointed out referencing this gem

Enceladus answered 22/11, 2018 at 10:20 Comment(0)
C
0

graywh's answer is the best if it actually locales different in displaying the field name. In the case of a dynamic field name (based on other fields to display), I would do something like this

<% object.errors.each do |attr, msg| %>
<li>
  <% case attr.to_sym %>
  <% when :song_rep_xyz %>
    <%= #display error how you want here %>
  <% else %>
    <%= object.errors.full_message(attr, msg) %>
  <% end %>
</li>
<% end %>

the full_message method on the else is what rails use inside of full_messages method, so it will give out the normal Rails errors for other cases (Rails 3.2 and up)

Celluloid answered 16/1, 2020 at 20:21 Comment(0)
D
0

Upgraded @Federico's answer to be universal for all field errors.

In your controller.rb:

flash[:alert] = @model.errors.messages.values 
# [["field1_err1", "field1_err2"], ["field2_err1"], ["field3_err1"]]

messages method "returns a Hash of attributes with an array of their error messages" as in rails docs.

Then, to display those errors in a form:

<% flash.each do |type, type_arr| %>
  <% type_arr.each do |msg| %>
    <ul>
      <li>
        <%= msg.to_sentence %>
      </li>
    </ul>
  <% end %>
<% end %>
Deland answered 1/2, 2023 at 13:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.