4 Commits

Author SHA1 Message Date
Kabiru Mwenja 0acda9d84b Cover historical reservation under :semantic_conversion context
Addresses Copilot review on PR 22982. The previous spec covered
historical reservation in the two mode contexts but lost the case
where another project's FriendlyId slug history blocks a semantic
identifier when the global mode is classic — the exact scenario
that drives the converter service's :semantic_conversion path.
2026-04-29 12:02:24 +03:00
Kabiru Mwenja 47fe076905 Move validator into Projects namespace
- Validator moves to app/validators/projects/identifier_validator.rb
  as Projects::IdentifierValidator. The path-style symbol on the
  validates declaration ('projects/identifier') resolves to it.
  Distinguishes domain-specific validators from globally reusable
  ones (UrlValidator, JsonValidator, etc).

- Spec moves to spec/validators/projects/identifier_validator_spec.rb.

- Trim comments that explained framework conventions (validator
  lookup, declaration order). Keep comments only where the WHY is
  non-obvious to a Rails-fluent reader.
2026-04-29 11:30:50 +03:00
Kabiru Mwenja 9b087691c0 Restructure validator spec around mode contexts
Top level no longer biases toward classic. Mode-agnostic behaviors
(blank short-circuit, reserved keyword, historical reservation) move
into shared examples that both 'in classic mode' and 'in semantic
mode' contexts include — so each branch of the validator's mode
dispatch exercises them. Format-specific tests stay inside their
respective mode context.

The :semantic_conversion validation context block stays its own
describe — its purpose is testing the override-from-classic, so
having a global classic setting there is the point, not a default.

Coverage grows from 31 to 48 examples; the additional 17 are
shared-example runs that previously executed only under classic
settings.
2026-04-29 09:13:22 +03:00
Kabiru Mwenja 97a266273c Extract project identifier validation into dedicated validator
Move five private validation methods out of the Projects::Identifier
concern into a top-level ActiveModel::EachValidator. The concern goes
from carrying ~50 lines of validation logic to declaring:

    validates :identifier, project_identifier: true,
                           if: :identifier_changed?

Format rules, reserved-keyword check, and historical-reservation check
all live in ProjectIdentifierValidator. Mode dispatch (classic vs
semantic vs :semantic_conversion context) is internal to the validator
and easy to swap for a registry if a third format ever appears.

Picks up the follow-up suggested in #22931 (comment 4326208630).

Notes:

- Validator is top-level (ProjectIdentifierValidator) not namespaced,
  matching the existing convention of UrlValidator / JsonValidator /
  SecureContextUriValidator and so Rails' validator lookup for
  `validates :identifier, project_identifier: true` resolves directly.

- Format constants (RESERVED_IDENTIFIERS, *_MAX_LENGTH) stay on the
  concern since they're shared with acts_as_url's blacklist/limit, the
  routing constraint, and suggesters — moving them to the validator
  would couple unrelated subsystems to a validator namespace.

- Validator declaration order in the concern is significant: presence
  + uniqueness must run before `project_identifier: true` so the
  historical-reservation check can short-circuit when uniqueness has
  already flagged :taken.

Pure-validator behaviour (format checks, reserved keyword,
:semantic_conversion context, historical reservation) moves to
spec/validators/project_identifier_validator_spec.rb. Integration
tests (FriendlyId :history wiring, :saving_custom_fields,
.suggest_identifier, .identifier_slugs scopes) stay in the model spec.
2026-04-29 08:54:30 +03:00