docbrown/app/validators/cross_model_slug_validator.rb
Joshua Wehner a20cf3e02b
Unique cross-model-slug check is likely misconfigured (#19263)
* Page unique_cross_model_slug seems misconfigured

* Update validator test

* More verbose invalid attribute message

* Try dynamic model/attribute registration

* Rubocop

* Use the cross-model-slug existence check

* Register with the dynamic cross-model-slug checker

* Cleanup

* Add missing keys to fr

* Update format regex

* Adjust validation: always presence, unique if changed

* Tests can create reserved-word pages when they need to

* Slugs can be mixed case?

* Case-sensitive is better, actually

* Refactor to use CrossModel check

* Refactor, rename for clarity

* Refactor, avoid mocking oneself

* Refactor, injectable everything

* Add reservedword check to extracted exists? checker

* Move to concerns

* Without dynamic registration

* extend when needed

* Cleanup comments, remove registration references

---------

Co-authored-by: Goran <gorang.pub@gmail.com>
2023-04-07 17:04:45 +02:00

39 lines
1.2 KiB
Ruby

##
# Validates if the give attribute is used across the reserved spaces.
class CrossModelSlugValidator < ActiveModel::EachValidator
FORMAT_REGEX = /\A[0-9a-z\-_]+\z/
def validate_each(record, attribute, value)
return if value.blank?
correct_format?(record, attribute, value)
not_on_reserved_list?(record, attribute, value)
unique_across_models?(record, attribute, value)
end
private
def not_on_reserved_list?(record, attribute, value)
return unless ReservedWords.all.include?(value)
record.errors.add(attribute, I18n.t("validators.cross_model_slug_validator.is_reserved"))
end
def correct_format?(record, attribute, value)
return if value.match?(FORMAT_REGEX)
record.errors.add(attribute, I18n.t("validators.cross_model_slug_validator.is_invalid"))
end
def unique_across_models?(record, attribute, value)
# attribute_changed? is likely redundant, but is much cheaper than the cross-model exists check
return unless record.public_send("#{attribute}_changed?")
return unless already_exists?(value)
record.errors.add(attribute, I18n.t("validators.cross_model_slug_validator.is_taken"))
end
def already_exists?(value)
CrossModelSlug.exists?(value.downcase)
end
end