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开发者_如何学C 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"?
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.
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 %>
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.
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:
- Providing the locale entry for 'error_symbol'
- 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:
- The class in question is correctly picking up the i18n formatting for that class
- What keys it is using to look in the locale files.
精彩评论