class ActiveRecord::Import::Validator

Public Class Methods

new(klass, options = {}) click to toggle source
# File lib/activerecord-import/import.rb, line 29
def initialize(klass, options = {})
  @options = options
  @validator_class = klass
  init_validations(klass)
end

Public Instance Methods

init_validations(klass) click to toggle source
# File lib/activerecord-import/import.rb, line 35
def init_validations(klass)
  @validate_callbacks = klass._validate_callbacks.dup

  @validate_callbacks.each_with_index do |callback, i|
    filter = callback.respond_to?(:raw_filter) ? callback.raw_filter : callback.filter
    next unless filter.class.name =~ /Validations::PresenceValidator/ ||
                (!@options[:validate_uniqueness] &&
                 filter.is_a?(ActiveRecord::Validations::UniquenessValidator))

    callback = callback.dup
    filter = filter.dup
    attrs = filter.instance_variable_get(:@attributes).dup

    if filter.is_a?(ActiveRecord::Validations::UniquenessValidator)
      attrs = []
    else
      associations = klass.reflect_on_all_associations(:belongs_to)
      associations.each do |assoc|
        if (index = attrs.index(assoc.name))
          key = assoc.foreign_key.is_a?(Array) ? assoc.foreign_key.map(&:to_sym) : assoc.foreign_key.to_sym
          attrs[index] = key unless attrs.include?(key)
        end
      end
    end

    filter.instance_variable_set(:@attributes, attrs.flatten)

    if @validate_callbacks.respond_to?(:chain, true)
      @validate_callbacks.send(:chain).tap do |chain|
        callback.instance_variable_set(:@filter, filter)
        callback.instance_variable_set(:@compiled, nil)
        chain[i] = callback
      end
    else
      callback.raw_filter = filter
      callback.filter = callback.send(:_compile_filter, filter)
      @validate_callbacks[i] = callback
    end
  end
end
valid_model?(model) click to toggle source
# File lib/activerecord-import/import.rb, line 76
def valid_model?(model)
  init_validations(model.class) unless model.instance_of?(@validator_class)

  validation_context = @options[:validate_with_context]
  validation_context ||= (model.new_record? ? :create : :update)
  current_context = model.send(:validation_context)

  begin
    model.send(:validation_context=, validation_context)
    model.errors.clear

    model.run_callbacks(:validation) do
      if defined?(ActiveSupport::Callbacks::Filters::Environment) # ActiveRecord >= 4.1
        runner = if @validate_callbacks.method(:compile).arity == 0
          @validate_callbacks.compile
        else # ActiveRecord >= 7.1
          @validate_callbacks.compile(nil)
        end
        env = ActiveSupport::Callbacks::Filters::Environment.new(model, false, nil)
        if runner.respond_to?(:call) # ActiveRecord < 5.1
          runner.call(env)
        else # ActiveRecord >= 5.1
          # Note that this is a gross simplification of ActiveSupport::Callbacks#run_callbacks.
          # It's technically possible for there to exist an "around" callback in the
          # :validate chain, but this would be an aberration, since Rails doesn't define
          # "around_validate". Still, rather than silently ignoring such callbacks, we
          # explicitly raise a RuntimeError, since activerecord-import was asked to perform
          # validations and it's unable to do so.
          #
          # The alternative here would be to copy-and-paste the bulk of the
          # ActiveSupport::Callbacks#run_callbacks method, which is undesirable if there's
          # no real-world use case for it.
          raise "The :validate callback chain contains an 'around' callback, which is unsupported" unless runner.final?
          runner.invoke_before(env)
          # Ensure a truthy value is returned. ActiveRecord < 7.2 always returned an array.
          runner.invoke_after(env) || []
        end
      elsif @validate_callbacks.method(:compile).arity == 0 # ActiveRecord = 4.0
        model.instance_eval @validate_callbacks.compile
      else # ActiveRecord 3.x
        model.instance_eval @validate_callbacks.compile(nil, model)
      end
    end

    model.errors.empty?
  ensure
    model.send(:validation_context=, current_context)
  end
end