Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Suppress "base" in error text for custom validation of Rails nested attributes

I have the following models:

class Evaluation < ActiveRecord::Base
    attr_accessible :product_id, :description, :evaluation_institutions_attributes

    has_many :evaluation_institutions, :dependent => :destroy  
    accepts_nested_attributes_for :evaluation_institutions, :reject_if => lambda { |a| a[:token].blank? }, :allow_destroy => true       

    validate :requires_at_least_one_institution

    private

      def requires_at_least_one_institution
        if evaluation_institution_ids.nil? || evaluation_institution_ids.length == 0
          errors.add_to_base("Please select at least one institution")
        end
      end    
end

class EvaluationInstitution < ActiveRecord::Base

  attr_accessible :evaluation_institution_departments_attributes, :institution_id

  belongs_to :evaluation

  has_many :evaluation_institution_departments, :dependent => :destroy  
  accepts_nested_attributes_for :evaluation_institution_departments, :reject_if => lambda { |a| a[:department_id].blank? }, :allow_destroy => true

  validate :requires_at_least_one_department

  private

    def requires_at_least_one_department
       if evaluation_institution_departments.nil? || evaluation_institution_departments.length == 0
         errors.add_to_base("Please select at least one department")
       end
    end

end

class EvaluationInstitutionDepartment < ActiveRecord::Base
  belongs_to :evaluation_institution
  belongs_to :department
end

I have a form for Evaluation that includes nested attributes for EvaluationInstitution and EvaluationInstitutionDepartment, so my form is nested to 3 levels. The 3rd level is giving me a problem.

The errors are triggered as expected, but when the error triggers for requires_at_least_one_department, the text reads

Evaluation institutions base Please select at least one department

The message should read "Please select at least one department".

How do I remove "Evaluation institutions base"?

like image 356
Kevin Avatar asked May 09 '11 20:05

Kevin


4 Answers

In Rails 3.2, if you take a look at the implementation of method full_message, you will see that it displays error messages through I18n with format "%{attribute} %{message}".

It means that you can customize the displayed format in your I18n locales as follows:

activerecord:
  attributes:
    evaluation_institutions:
      base: ''

That would get rid of the prefix "Evaluation institutions base" as you wanted.

like image 196
Christophe Levand Avatar answered Nov 15 '22 04:11

Christophe Levand


If anyone is looking for a solution for this that doesn't involve monkey patching, here's what I did in my errors partial. I simply look for "base" in the name of the attribute with the error and if it exists, I only post the message, otherwise I build the full_message. Now this won't work if you have attributes that have base in the name, but I don't so this works for me. It's a little hacky but so are the other solutions to this issue.

<% if object.errors.any? %>
  <div id="error-explanation">
    <div class="alert alert-error">
      <ul>
        <% object.errors.each do |atr, msg| %>
          <li>
            <% if atr.to_s.include? "base" %>
              <%= msg %>
            <% else %>
              <%= object.errors.full_message(atr, msg) %>
            <% end %>
          </li>
        <% end %>
      </ul>
    </div>
  </div>
<% end %>
like image 44
dmastylo Avatar answered Nov 15 '22 03:11

dmastylo


Adding the following monkey patch to initializers did the job for me in 3.2.3 with dynamic_form:

class ActiveModel::Errors
  #exact copy of dynamic_form full_messages except 'attr_name = attr_name.sub(' base', ':')'
  def full_messages        
    full_messages = []

    each do |attribute, messages|
      messages = Array.wrap(messages)
      next if messages.empty?

      if attribute == :base
        messages.each {|m| full_messages << m }
      else
        attr_name = attribute.to_s.gsub('.', '_').humanize
        attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
        attr_name = attr_name.sub(' base', ':')
        options = { :default => "%{attribute} %{message}", :attribute => attr_name }

        messages.each do |m|
          if m =~ /^\^/
            options[:default] = "%{message}"
            full_messages << I18n.t(:"errors.dynamic_format", options.merge(:message => m[1..-1]))
          elsif m.is_a? Proc
            options[:default] = "%{message}"
            full_messages << I18n.t(:"errors.dynamic_format", options.merge(:message => m.call(@base)))
          else
            full_messages << I18n.t(:"errors.format", options.merge(:message => m))
          end            
        end
      end
    end

    full_messages
  end
end

If you aren't using dynamic_form, try the following instead (unless your form gem overrides errors.full_messages like dynamic_form does):

class ActiveModel::Errors        
    #exact copy of Rails 3.2.3 full_message except 'attr_name = attr_name.sub(' base', ':')'
    def full_message(attribute, message)
      return message if attribute == :base
      attr_name = attribute.to_s.gsub('.', '_').humanize
      attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
      attr_name = attr_name.sub(' base', ':')
      I18n.t(:"errors.format", {
        :default   => "%{attribute} %{message}",
        :attribute => attr_name,
        :message   => message
      })
    end   
end

The only change to original code is the following line:

attr_name = attr_name.sub(' base', ':')

Suggestions welcome.

like image 34
Ilia Gimelfarb Avatar answered Nov 15 '22 04:11

Ilia Gimelfarb


This is an older question, but this issue just bit me again in Rails 6, so posting my solution here, since this is the most relevant SO post that covered the issue.

Example: Saving a top level class: 'Parent' containing a collection of 'Child', where 'Child' has a custom validation method:

e.g.

class Parent < ActiveRecord::Base
  has_many :children
    accepts_nested_attributes_for :children, allow_destroy: true, reject_if: :all_blank
end

class Child < ActiveRecord::Base
  belongs_to :parent
  validate :custom_method

  def custom_method
    errors.add(:base, :error_symbol)
  end
end

The following is needed:

  1. Providing the locale entry for 'error_symbol'
  2. Preventing the error for children rendering as 'Child Base ...'

Solution

First, add config.active_model.i18n_customize_full_message = true to your application.rb file.

Then, the following locale file works to override both the message and prevent 'Base' being prepended to the collection.

# config/locales/en.yml
en:
  activerecord:
    errors:
      models:
        parent/children:
          format: 'Child: %{message}'
        child:
          error_symbol: "Error message goes here"

Interestingly, there seems to be some interaction here with accepts_nested_attributes_for, as I only was able to reproduce this issue when creating the parent and children with a single params object.

Should this not work for you, or you have a more complex issue, taking a look in ActiveModel/lib/active_model/errors.rb at the full_message method.

This should tell you whether:

  1. The class in question is correctly picking up the i18n formatting for that class
  2. What keys it is using to look in the locale files.
like image 36
jamiebuckley Avatar answered Nov 15 '22 03:11

jamiebuckley