Подавлять «base» в тексте ошибки для пользовательской проверки вложенных атрибутов Rails

#ruby-on-rails-3 #nested-attributes #custom-validators

#ruby-on-rails-3 #вложенные атрибуты #пользовательские валидаторы

Вопрос:

У меня есть следующие модели:

 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
  

У меня есть форма для оценки, которая включает вложенные атрибуты для EvaluationInstitution и EvaluationInstitutionDepartment, поэтому моя форма вложена в 3 уровня. 3-й уровень вызывает у меня проблему.

Ошибки срабатывают, как и ожидалось, но когда ошибка срабатывает для require_sat_least_one_department, текст гласит

База оценочных учреждений, пожалуйста, выберите хотя бы один отдел

Сообщение должно гласить «Пожалуйста, выберите хотя бы один отдел».

Как мне удалить «Базу оценочных учреждений»?

Комментарии:

1. Вы когда-нибудь находили ответ на этот @Kevin? У меня такая же проблема. Интересно, может ли помочь упрощение вопроса ..?

2. Так и не нашел ответа на это.

3. @paul, кстати, моя работа заключалась в том, чтобы перенести всю проверку на модель более высокого уровня, evaluation. Так, например, моя функция :requires_at_least_one_department перемещается в модель оценки. Не самое лучшее решение.

Ответ №1:

В Rails 3.2, если вы посмотрите на реализацию метода full_message, вы увидите, что он отображает сообщения об ошибках через I18n в формате «%{attribute} %{message}».

Это означает, что вы можете настроить отображаемый формат в ваших локализациях I18n следующим образом:

 activerecord:
  attributes:
    evaluation_institutions:
      base: ''
  

Это позволило бы избавиться от префикса «База оценочных учреждений», как вы хотели.

Комментарии:

1. Я не знаю, почему я поддержал это. Я не могу заставить это работать. Это выглядело как достойное решение.

2. Я заставил свой работать, удалив 2-ю строку в этом файле locales … другими словами, не включайте «ошибки» в файл yaml.

3. Для меня мне пришлось использовать имя модели в локальной в единственном числе, а не во множественном числе, т.е.: evaluation_institution

Ответ №2:

Если кто-то ищет решение для этого, которое не требует исправления обезьяной, вот что я сделал в разделе «Мои ошибки частично». Я просто ищу «base» в имени атрибута с ошибкой, и если он существует, я только отправляю сообщение, в противном случае я создаю full_message. Теперь это не сработает, если у вас есть атрибуты, в названии которых есть base, но у меня их нет, поэтому у меня это работает. Это немного халтурно, но таковы и другие решения этой проблемы.

 <% 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 %>
  

Комментарии:

1. Спасибо. Я сделал что-то очень похожее (Rails 4), но с этой менее сложной проверкой: if atr == :base || atr.to_s.ends_with?(".base")

Ответ №3:

Добавление следующего исправления monkey к инициализаторам выполнило работу за меня в 3.2.3 с помощью 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
  

Если вы не используете dynamic_form, попробуйте вместо этого следующее (если только ваш gem формы не переопределяет errors.full_messages, как это делает dynamic_form):

 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
  

Единственным изменением в исходном коде является следующая строка:

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

Предложения приветствуются.

Ответ №4:

Это старый вопрос, но эта проблема снова затронула меня в Rails 6, поэтому публикую свое решение здесь, поскольку это наиболее актуальный пост SO, в котором рассматривается проблема.

Пример: Сохранение класса верхнего уровня: ‘Parent’, содержащего коллекцию ‘Child’, где ‘Child’ имеет пользовательский метод проверки:

например

 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
  

Необходимо следующее:

  1. Предоставление записи локали для ‘error_symbol’
  2. Предотвращение ошибки для дочерних элементов, отображаемых как «Дочерняя база …»

Решение

Сначала добавьте config.active_model.i18n_customize_full_message = true в свой файл application.rb.

Затем следующий файл локали работает, чтобы переопределить оба сообщения и предотвратить добавление «Base» к коллекции.

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

Интересно, что, похоже, здесь есть некоторое взаимодействие с accepts_nested_attributes_for , поскольку я смог воспроизвести эту проблему только при создании родительского и дочерних элементов с одним объектом params.

Если у вас это не сработает или у вас более сложная проблема, взгляните на ActiveModel/lib/active_model/errors.rb метод full_message .

Это должно сказать вам, является ли:

  1. Рассматриваемый класс правильно подбирает форматирование i18n для этого класса
  2. Какие ключи он использует для поиска в файлах локали.