57 lines
2 KiB
Ruby
57 lines
2 KiB
Ruby
class CrossModelSlugValidator < ActiveModel::EachValidator
|
|
FORMAT_REGEX = /\A[0-9a-z\-_]+\z/
|
|
ORGANIZATION_FORMAT_REGEX = /\A(?![0-9]+\z)[0-9a-z\-_]+\z/
|
|
## allow / in page slugs
|
|
PAGE_FORMAT_REGEX = /\A[0-9a-z\-_\/]+\z/
|
|
PAGE_DIRECTORY_LIMIT = 6
|
|
|
|
def validate_each(record, attribute, value)
|
|
return if value.blank?
|
|
|
|
correct_format?(record, attribute, value)
|
|
allowed_subdirectory_count?(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 false if record.instance_of?(::Page) || ReservedWords.all.exclude?(value)
|
|
|
|
record.errors.add(attribute, I18n.t("validators.cross_model_slug_validator.is_reserved"))
|
|
end
|
|
|
|
def correct_format?(record, attribute, value)
|
|
format_regex = case record.class.name
|
|
when "Organization"
|
|
ORGANIZATION_FORMAT_REGEX
|
|
when "Page"
|
|
PAGE_FORMAT_REGEX
|
|
else
|
|
FORMAT_REGEX
|
|
end
|
|
return false 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 false unless record.public_send("#{attribute}_changed?")
|
|
return false unless already_exists?(value)
|
|
|
|
record.errors.add(attribute, I18n.t("validators.cross_model_slug_validator.is_taken"))
|
|
end
|
|
|
|
def allowed_subdirectory_count?(record, attribute, value)
|
|
return false unless record.instance_of?(::Page)
|
|
return false if value.split("/").count <= PAGE_DIRECTORY_LIMIT
|
|
|
|
record.errors.add(attribute, I18n.t("validators.cross_model_slug_validator.too_many_subdirectories"))
|
|
end
|
|
|
|
def already_exists?(value)
|
|
CrossModelSlug.exists?(value.downcase)
|
|
end
|
|
end
|