diff --git a/app/models/application_record.rb b/app/models/application_record.rb index ebc177b0d..d145cec8a 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -1,5 +1,6 @@ class ApplicationRecord < ActiveRecord::Base include HumanName + include SkipValidation self.abstract_class = true def self.sample(count = 1) diff --git a/app/models/concerns/skip_validation.rb b/app/models/concerns/skip_validation.rb new file mode 100644 index 000000000..2a3dbcdc1 --- /dev/null +++ b/app/models/concerns/skip_validation.rb @@ -0,0 +1,26 @@ +module SkipValidation + extend ActiveSupport::Concern + + module ClassMethods + def skip_validation(field, validator) + validator_class = if validator.is_a?(Class) + validator + else + "ActiveModel::Validations::#{validator.to_s.camelize}Validator".constantize + end + + _validators[field].reject! { |existing_validator| existing_validator.is_a?(validator_class) } + + _validate_callbacks.each do |callback| + if callback.raw_filter.is_a?(validator_class) + callback.raw_filter.instance_variable_set("@attributes", callback.raw_filter.attributes - [field]) + end + end + end + + def skip_translation_validation(field, validator) + skip_validation(field, validator) + translation_class.skip_validation(field, validator) + end + end +end diff --git a/config/initializers/globalize.rb b/config/initializers/globalize.rb index d5834f217..7d93e9fda 100644 --- a/config/initializers/globalize.rb +++ b/config/initializers/globalize.rb @@ -9,6 +9,10 @@ module Globalize end end end + + class Translation + include SkipValidation + end end end diff --git a/spec/models/skip_validation_spec.rb b/spec/models/skip_validation_spec.rb new file mode 100644 index 000000000..553432757 --- /dev/null +++ b/spec/models/skip_validation_spec.rb @@ -0,0 +1,83 @@ +require "rails_helper" + +describe SkipValidation do + describe ".skip_validation" do + before do + dummy_model = Class.new do + include ActiveModel::Model + include SkipValidation + attr_accessor :title, :description + + validates :title, presence: true, length: { in: 10..60, allow_nil: true } + validates :description, presence: true + end + + stub_const("DummyModel", dummy_model) + end + + it "accepts validator classes as parameters" do + DummyModel.skip_validation :title, ActiveModel::Validations::PresenceValidator + + expect(DummyModel.new(title: nil, description: "Something")).to be_valid + end + + it "accepts symbols as parameters" do + DummyModel.skip_validation :title, :presence + + expect(DummyModel.new(title: nil, description: "Something")).to be_valid + end + + it "does not affect other attributes" do + DummyModel.skip_validation :title, :presence + + expect(DummyModel.new(title: nil, description: nil)).not_to be_valid + end + + it "does not affect other validations" do + DummyModel.skip_validation :title, :presence + + expect(DummyModel.new(title: "Short", description: "Something")).not_to be_valid + end + + it "works with validators other than presence" do + DummyModel.skip_validation :title, :length + + expect(DummyModel.new(title: "Short", description: "Something")).to be_valid + expect(DummyModel.new(title: nil, description: "Something")).not_to be_valid + end + end + + describe ".skip_translation_validation" do + before do + dummy_banner = Class.new(Banner) do + def self.translation_class + @translation_class ||= Class.new(Banner::Translation) { clear_validators! } + end + reflect_on_association(:translations).options[:class_name] = "DummyBanner::Translation" + + clear_validators! + validates_translation :title, presence: true + validates_translation :description, presence: true + end + + stub_const("DummyBanner", dummy_banner) + stub_const("DummyBanner::Translation", dummy_banner.translation_class) + end + + it "removes the validation from the translatable attribute" do + DummyBanner.skip_translation_validation :title, :presence + + custom_banner = DummyBanner.new(build(:banner).attributes.merge(title: nil)) + + expect { custom_banner.save! }.not_to raise_exception + end + + it "does not affect other validations" do + DummyBanner.skip_translation_validation :title, :presence + + custom_banner = DummyBanner.new(build(:banner).attributes.merge(description: nil)) + + expect { custom_banner.save! }.to raise_exception(ActiveRecord::RecordInvalid) + end + end +end