ibanez270dx
8/16/2018 - 7:06 PM

Skippable Validations

Skip any attribute or block validations at runtime. Tested w/ Rails 5.2.0.

# Skip any attribute or block validations at runtime
#  https://github.com/rails/rails/blob/fc5dd0b85189811062c85520fd70de8389b55aeb/activemodel/lib/active_model/validations.rb
#  https://github.com/rails/rails/blob/91fd679710118482f718ea710710561f1cfb0b70/activesupport/lib/active_support/callbacks.rb
#
#  ex:
#    class User < ApplicationRecord
#      validates :name, presence: true
#      validate :fancy_email_validation
#     end
#
#  usage:
#    user = User.new
#    user.save! => false
#    user.skip_validations = [:name, :fancy_email_validation]
#    user.save! => true

module SkippableValidations
  extend ActiveSupport::Concern

  included do
    attr_accessor :skip_validations
    attr_internal :filters

    before_validation :skip_callbacks, if: proc{ skip_validations&.any? }
    after_validation :reset_callbacks, if: proc{ filters&.any? }
  end

private

  def skip_callbacks
    # determine validators to skip
    filters = skip_validations.flat_map do |attribute|
      _validators[attribute].presence || attribute
    end

    # skip any matching validators in the :validate CallbackChain
    # ActiveSupport::Callbacks::CallbackChain (nodoc) | https://github.com/rails/rails/blob/91fd679710118482f718ea710710561f1cfb0b70/activesupport/lib/active_support/callbacks.rb#L519
    _validate_callbacks.each do |callback|
      next unless callback.raw_filter.in?(filters)
      singleton_class.skip_callback :validate, :before, callback.raw_filter
    end
  end

  def reset_callbacks
    singleton_class.set_callback :validate, :before, *filters
    filters.clear
  end

end

# Add it to all models
ActiveRecord::Base.include SkippableValidations