Эффективный способ сообщить предупреждения о валидации отчетов, а также ошибки?

У меня есть проект Rails, где, как и в большинстве приложений, у нас есть ряд жестких правил проверки, которым должны соответствовать все объекты до их сохранения. Естественно, ActiveModel Validations идеально подходят для этого - мы используем комбинацию значений по умолчанию Rails и наших собственных ручных проверок.

Все больше и больше, тем не менее, мы сталкиваемся с ситуациями, когда мы хотели бы предупредить пользователя о случаях, когда, хотя их данные не являются недействительными в строгом смысле, есть элементы, которые они должны рассмотреть, но которые сами по себе не должны препятствовать сохранению записи. Несколько примеров, с верхней части головы:

  • Заголовок публикации был отправлен во ВСЕХ CAPS, которые могут быть действительными, но, вероятно, не являются
  • Кусок текстового текста больше, чем x количество слов меньше или больше, чем предполагаемое количество слов

Модуль валидации является такой хорошей метафорой, что мы рассматриваем ошибки проверки - и так много уже доступных сокетов, - что в идеале я хотел бы использовать этот базовый код, но для создания коллекции warnings предметы вместе с errors. Это позволило бы нам осветить эти случаи по-разному нашим пользователям, вместо того, чтобы подразумевать, что возможные нарушения стиля дома эквивалентны более вопиющим, строго соблюдаемым правилам.

Я посмотрел на драгоценные камни, такие как activemodel-warnings, но они работают, изменяя, какие проверки проверяются, когда запись проверена, расширяется или уменьшается errors сбор соответственно. Точно так же я посмотрел на встроенный параметр :on для проверки, чтобы увидеть, могу ли я что-то вручную отбросить, но снова все нарушения попадут в коллекцию ошибок, а не будут выделены.

Кто-нибудь пробовал что-то подобное? Я не могу себе представить, что я единственный, кто хотел бы достичь этой цели, но сейчас я рисую пустоту...

Ответы

Ответ 1

Вот код, который я написал для проекта Rails 3, который делает именно то, о чем вы говорите здесь.

# Define a "warnings" validation bucket on ActiveRecord objects.
#
# @example
#
#   class MyObject < ActiveRecord::Base
#     warning do |vehicle_asset|
#       unless vehicle_asset.description == 'bob'
#         vehicle_asset.warnings.add(:description, "should be 'bob'")
#       end
#     end
#   end
#
# THEN:
#
#   my_object = MyObject.new
#   my_object.description = 'Fred'
#   my_object.sensible? # => false
#   my_object.warnings.full_messages # => ["Description should be 'bob'"]
module Warnings
  module Validations
    extend ActiveSupport::Concern
    include ActiveSupport::Callbacks

    included do
      define_callbacks :warning
    end

    module ClassMethods
      def warning(*args, &block)
        options = args.extract_options!
        if options.key?(:on)
          options = options.dup
          options[:if] = Array.wrap(options[:if])
          options[:if] << "validation_context == :#{options[:on]}"
        end
        args << options
        set_callback(:warning, *args, &block)
      end
    end

    # Similar to ActiveModel::Validations#valid? but for warnings
    def sensible?
      warnings.clear
      run_callbacks :warning
      warnings.empty?
    end

    # Similar to ActiveModel::Validations#errors but returns a warnings collection
    def warnings
      @warnings ||= ActiveModel::Errors.new(self)
    end

  end
end

ActiveRecord::Base.send(:include, Warnings::Validations)

Комментарии наверху показывают, как его использовать. Вы можете поместить этот код в инициализатор, а затем предупреждения должны быть доступны для всех ваших объектов ActiveRecord. А затем в основном просто добавьте блок warnings do в начало каждой модели, который может иметь предупреждения, и просто вручную добавить столько предупреждений, сколько захотите. Этот блок не будет выполнен, пока вы не назовете .sensible? на модели.

Также обратите внимание, что поскольку предупреждения не являются ошибками проверки, модель все равно будет технически обоснованной, даже если она не "разумна" (как я ее называл).

Ответ 2

Спустя годы, но в более новых версиях Rails есть немного более простой способ:

  attr_accessor :save_despite_warnings

  def warnings
    @warnings ||= ActiveModel::Errors.new(self)
  end

  before_save :check_for_warnings
  def check_for_warnings
    warnings.add(:notes, :too_long, count: 120) if notes.to_s.length > 120

    !!save_despite_warnings
  end

Тогда вы можете сделать: record.warnings.full_messages