Home > OS >  Skip validation from include element
Skip validation from include element

Time:12-02

I am having an issue, I have the next class

class Question < ApplicationRecord
  include Mappable

 ...

end

so my problem is at the time to create a Question, I need to keep the Question validations but skip the ones that are incoming from Mappable

because by now I am using question.save(validate: false) and I have to update that to something like question.save(mappable_validate: false), just to skip the validations from Mappable

EDIT Mappable:

module Mappable
  extend ActiveSupport::Concern

  included do
    attr_accessor :skip_map

    has_one :map_location, dependent: :destroy
    accepts_nested_attributes_for :map_location, allow_destroy: true, reject_if: :all_blank

    validate :map_must_be_valid, on: :create, if: :feature_maps?

    def map_must_be_valid
      return true if skip_map?

      unless map_location.try(:available?)
        skip_map_error = "Map error"
        errors.add(:skip_map, skip_map_error)
      end
    end

    def feature_maps?
      Setting["feature.map"].present?
    end

    def skip_map?
      skip_map == "1"
    end

  end

end

CodePudding user response:

There are quite a few ways to solve this. But no reliable ones that don't involve modifying the module.

One would be simply to use composition and move the validations to its own module:

module Mappable
  module Validations
    extend ActiveSupport::Concern
    included do
      validate :map_must_be_valid, on: :create, if: :feature_maps?
    end
  end
end
class Question < ApplicationRecord
  include Mappable
  include Mappable
end

class Foo < ApplicationRecord
  include Mappable
  include Mappable::Validations
end

Another very common way to make the behavior provided by a module customizeable is to not just cram all your code into the Module#included hook which doesn't let you pass options.

Instead create a class method:

module Mappable
  extend ActiveSupport::Concern
   
  def map_must_be_valid
    return true if skip_map?

    unless map_location.try(:available?)
      skip_map_error = "Map error"
      errors.add(:skip_map, skip_map_error)
    end
  end

  def feature_maps?
    Setting["feature.map"].present?
  end

  def skip_map?
    skip_map == "1"
  end

  module ClassMethods
    def make_mappable(validate: true)
      attr_accessor :skip_map
      has_one :map_location, dependent: :destroy
      accepts_nested_attributes_for :map_location, 
         allow_destroy: true, reject_if: :all_blank
      if validate
        validate :map_must_be_valid, on: :create, if: :feature_maps?
      end
    end
  end
end

And then just call the class method in the class you want to modify.

class Question < ApplicationRecord
  include Mappable
  make_mappable(validate: false)
end

This pattern can be found everywhere in Rails and Ruby in general and lets you make the functionality you're providing much flexible.

I understand that this might not seem to be immediately helpful as the code is coming from a gem. But it can help you understand what to do to fix the gem or evaluate if its actually worthwhile/needed.

  • Related