Make it easier to customize validations

There are CONSUL installations where the validations CONSUL offers by
default don't make sense because they're using a different business
logic. Removing these validations in a custom model was hard, and that's
why in many cases modifying the original CONSUL models was an easier
solution.

Since modifying the original CONSUL models makes the code harder to
maintain, we're now providing a way to easily skip validations in a
custom model. For example, in order to skip the price presence
validation in the Budget::Heading model, we could write a model in
`app/models/custom/budget/heading.rb`:

```
require_dependency Rails.root.join("app", "models", "budget", "heading").to_s

class Budget::Heading
  skip_validation :price, :presence
end
```

In order to skip validation on translatable attributes (defined with
`validates_translation`), we have to use the
`skip_translation_validation` method; for example, to skip the proposal
title presence validation:

```
require_dependency Rails.root.join("app", "models", "proposal").to_s

class Proposal
  skip_translation_validation :title, :presence
end

```

Co-Authored-By: taitus <sebastia.roig@gmail.com>
This commit is contained in:
Javi Martín
2022-03-23 16:03:36 +01:00
parent 12460c2000
commit b5a4609b56
4 changed files with 114 additions and 0 deletions

View File

@@ -1,5 +1,6 @@
class ApplicationRecord < ActiveRecord::Base
include HumanName
include SkipValidation
self.abstract_class = true
def self.sample(count = 1)

View File

@@ -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

View File

@@ -9,6 +9,10 @@ module Globalize
end
end
end
class Translation
include SkipValidation
end
end
end

View File

@@ -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