Skip to content

class Athena::Validator::Constraints::Collection
inherits Athena::Validator::Constraints::Composite #

Can be used with any Enumerable({K, V}) to validate each key in a different way. For example validating the email key via AVD::Constraints::Email, and the inventory key with the AVD::Constraints::Range constraint. The collection constraint can also ensure that certain collection keys are present and that extra keys are not present.

Todo

Update it to be Mappable when/if https://github.com/crystal-lang/crystal/issues/10886 is implemented.

Usage#

data = {
  "email"           => "...",
  "email_signature" => "...",
}

For example, say you want to ensure the email field is a valid email, and that their email_signature is not blank nor over 100 characters long; without creating a dedicated class to represent the hash.

constraint = AVD::Constraints::Collection.new({
  "email"           => AVD::Constraints::Email.new,
  "email_signature" => [
    AVD::Constraints::NotBlank.new,
    AVD::Constraints::Size.new(..100, max_message: "Your signature is too long"),
  ],
})

validator.validate data, constraint

The collection constraint expects a hash representing the keys in the collection, with the value being which constraint(s) should be executed against its value. From there we can go ahead and validate our data hash against the constraint.

Presence and Absence of Fields#

This constraint also will return validation errors if any keys of a collection are missing, or if there are any unrecognized keys in the collection. This can be customized via the allow_extra_fields and allow_missing_fields configuration options respectively.

If the latter was set to true, then either email or email_signature could be missing from the data hash, and no validation errors would occur.

Required and Optional Constraints#

Each field in the collection is assumed to be required by default. While you could make everything optional via the setting allow_missing_fields to true, this is less than ideal in some cases when you only want to affect a single key, or a subset of keys.

In this case, a single constraint, or array of constraints, can be wrapped via the AVD::Constraints::Optional or AVD::Constraints::Required constraints. For example, if you wanted to require that the personal_email field is not blank and is a valid email, but also have an optional alternate_email field that must be a valid email if supplied, you could set things up like:

constraint = AVD::Constraints::Collection.new({
  "personal_email" => AVD::Constraints::Required.new([
    AVD::Constraints::NotBlank.new,
    AVD::Constraints::Email.new,
  ]),
  "alternate_email" => AVD::Constraints::Optional.new([
    AVD::Constraints::Email.new,
  ] of AVD::Constraint),
})

In this way, even if allow_missing_fields is true, you would be able to omit alternate_email since it is optional. However, since personal_email is required, the not blank assertion will still be applied and a violation will occur if it is missing.

Groups#

Any groups defined in nested constraints are automatically added to the collection constraint itself such that it can be traversed for all nested groups.

constraint = AVD::Constraints::Collection.new({
  "name"  => AVD::Constraints::NotBlank.new(groups: "basic"),
  "email" => AVD::Constraints::NotBlank.new(groups: "contact"),
})

constraint.groups # => ["basic", "contact"]

Tip

The collection constraint can be used to validate form data via a URI::Param instance.

Configuration#

Required Arguments#

fields#

Type: Hash(String, AVD::Constraint | Array(AVD::Constraint))

A hash defining the keys in the collection, and for which constraint(s) should be executed against them.

Optional Arguments#

allow_extra_fields#

Type: Bool Default: false

If extra fields in the collection other than those defined within fields are allowed. By default extra fields will result in a validation error.

allow_missing_fields#

Type: Bool Default: false

If the fields defined within fields are allowed to be missing. By default a validation error will be returned if one or more field is missing.

extra_fields_message#

Type: String Default: This field was not expected.

The message that will be shown if allow_extra_fields is false and a field in the collection was not defined within #fields.

Placeholders#

The following placeholders can be used in this message:

  • {{ field }} - The name of the extra field.

missing_fields_message#

Type: String Default: This field is missing.

The message that will be shown if allow_missing_fields is false and a field defined within #fields is missing from the collection.

Placeholders#

The following placeholders can be used in this message:

  • {{ field }} - The name of the missing field.

groups#

Type: Array(String) | String | Nil Default: nil

The validation groups this constraint belongs to. AVD::Constraint::DEFAULT_GROUP is assumed if nil.

payload#

Type: Hash(String, String)? Default: nil

Any arbitrary domain-specific data that should be stored with this constraint. The payload is not used by Athena::Validator, but its processing is completely up to you.

Constants#

MISSING_FIELD_ERROR = "af103ee5-3bcb-448e-98ad-b4ef76c05060"#

NO_SUCH_FIELD_ERROR = "70e60467-4078-4f92-acf9-d1e6683d0922"#

Constructors#

.new(fields : Hash(String, AVD::Constraint | Array(AVD::Constraint)), allow_extra_fields : Bool = false, allow_missing_fields : Bool = false, extra_fields_message : String = "This field was not expected.", missing_fields_message : String = "This field is missing.", groups : Array(String) | String | Nil = nil, payload : Hash(String, String) | Nil = nil)#

View source

Methods#

#allow_extra_fields? : Bool#

View source

#allow_missing_fields? : Bool#

View source

#extra_fields_message : String#

View source

#missing_fields_message : String#

View source

#validated_by : AVD::ConstraintValidator.class#

Returns the AVD::ConstraintValidator.class that should handle validating self.

View source