AVD::Constraints::GroupSequences can be a good way to create efficient validations.
However, since the sequence is static, it is not a very flexible solution.
Group sequence providers allow the sequence to be dynamically determined at runtime. This allows running specific validations only when the object is in a specific state, such as validating a "registered" user differently than a non-registered user.
class User include AVD::Validatable # Include the interface that informs the validator this object will provide its sequence. include AVD::Constraints::GroupSequence::Provider @[Assert::NotBlank] property name : String # Only validate the `email` property if the `#group_sequence` method includes "registered" # Which can be determined using the current state of the object. @[Assert::Email(groups: "registered")] @[Assert::NotBlank(groups: "registered")] property email : String? def initialize(@name : String, @email : String); end # Define a method that returns the sequence. def group_sequence : Array(String | Array(String)) | AVD::Constraints::GroupSequence # When returning a 1D array, if there is a vaiolation in any group # the rest of the groups are not validated. E.g. if `User` fails, # `registered` and `api` are not validated: return ["User", "registered", "api"] # When returning a nested array, all groups included in each array are validated. # E.g. if `User` fails, `Premium` is also validated (and you'll get its violations), # but `api` will not be validated return [["User", "registered"], "api"] end end
AVD::Constraints::Sequentially for a more straightforward method of applying constraints sequentially on a single property.