Class: Servactory::ToolKit::DynamicOptions::ConsistsOf
- Defined in:
- lib/servactory/tool_kit/dynamic_options/consists_of.rb
Overview
Validates that collection elements are of specified types.
## Purpose
ConsistsOf ensures that all elements within an Array or collection attribute match one of the specified types. This is essential for validating homogeneous collections where each element must conform to expected types.
## Usage
This option is **included by default** for inputs, internals, and outputs. No registration required for basic usage.
To extend supported collection types (e.g., add ‘ActiveRecord::Relation`), use the `collection_mode_class_names` configuration:
“‘ruby configuration do
collection_mode_class_names([ActiveRecord::Relation])
end “‘
## Simple Mode
Specify type directly as the option value:
“‘ruby class ProcessUsersService < ApplicationService::Base
input :user_ids, type: Array, consists_of: Integer
input :tags, type: Array, consists_of: [String, Symbol]
input :scores, type: Array, consists_of: Float
end “‘
## Advanced Mode
Specify type with custom error message using a hash. Note: Advanced mode uses ‘:type` key (not `:is` like other options).
With static message:
“‘ruby input :ids, type: Array, consists_of:
type: String,
message: "Input `ids` must be an array of `String`"
“‘
With dynamic lambda message:
“‘ruby input :ids, type: Array, consists_of:
type: String,
message: lambda do |input:, option_value:, **|
"Input `#{input.name` must be an array of `#')`"
end
} “‘
Lambda receives the following parameters:
-
For inputs: ‘input:, value:, option_value:, reason:, **`
-
For internals: ‘internal:, value:, option_value:, reason:, **`
-
For outputs: ‘output:, value:, option_value:, reason:, **`
## Validation Rules
-
Collection must be of a registered collection type (Array, Set, etc.)
-
All elements are flattened before validation (nested arrays supported)
-
Empty collections pass validation for optional input attributes only
-
For internal/output attributes, presence check is always performed
-
For optional inputs with non-empty collections, presence check is performed
-
Multiple types can be specified as an array
## Important Notes
-
Use ‘consists_of: false` to disable validation
-
NilClass in types allows nil elements in the collection
-
Nested arrays are automatically flattened for validation
-
Advanced mode uses ‘:type` key (not `:is` like other options)
-
Numeric types use exact class matching (Integer != Float)
Class Method Summary collapse
-
.use(option_name = :consists_of, collection_mode_class_names:) ⇒ Servactory::Maintenance::Attributes::OptionHelper
Creates a ConsistsOf validator instance.
Instance Method Summary collapse
-
#assign(collection_mode_class_names) ⇒ void
Assigns the list of valid collection class names.
-
#common_condition_with(attribute:, value:, option:) ⇒ Boolean, Array
Common validation logic for all attribute types.
-
#condition_for_input_with(input:, value:, option:) ⇒ Boolean, Array
Validates element types for input attribute.
-
#condition_for_internal_with(internal:, value:, option:) ⇒ Boolean, Array
Validates element types for internal attribute.
-
#condition_for_output_with(output:, value:, option:) ⇒ Boolean, Array
Validates element types for output attribute.
-
#fails_presence_validation?(attribute:, values:, consists_of_types:) ⇒ Boolean
Checks if collection fails presence validation.
-
#given_type_for(values:, option_value:) ⇒ String
Extracts type names of elements that don’t match expected types.
-
#message_for_input_with(service:, input:, value:, option_name:, option_value:, reason:) ⇒ String
Generates error message for input validation failure.
-
#message_for_internal_with(service:, internal:, value:, option_name:, option_value:, reason:) ⇒ String
Generates error message for internal validation failure.
-
#message_for_output_with(service:, output:, value:, option_name:, option_value:, reason:) ⇒ String
Generates error message for output validation failure.
-
#validate_for!(attribute:, values:, option:) ⇒ Boolean, Array
Validates all elements against allowed types.
Methods inherited from Must
#equivalent_with, #initialize, #must, #must_content_message_with, #must_content_value_with, #must_content_with
Constructor Details
This class inherits a constructor from Servactory::ToolKit::DynamicOptions::Must
Class Method Details
.use(option_name = :consists_of, collection_mode_class_names:) ⇒ Servactory::Maintenance::Attributes::OptionHelper
Creates a ConsistsOf validator instance.
93 94 95 96 97 |
# File 'lib/servactory/tool_kit/dynamic_options/consists_of.rb', line 93 def self.use(option_name = :consists_of, collection_mode_class_names:) instance = new(option_name, :type, false) instance.assign(collection_mode_class_names) instance.must(:consists_of) end |
Instance Method Details
#assign(collection_mode_class_names) ⇒ void
This method returns an undefined value.
Assigns the list of valid collection class names.
103 104 105 |
# File 'lib/servactory/tool_kit/dynamic_options/consists_of.rb', line 103 def assign(collection_mode_class_names) @collection_mode_class_names = collection_mode_class_names end |
#common_condition_with(attribute:, value:, option:) ⇒ Boolean, Array
Common validation logic for all attribute types.
143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/servactory/tool_kit/dynamic_options/consists_of.rb', line 143 def common_condition_with(attribute:, value:, option:) # Validation disabled. return true if option.value == false # Attribute must be a collection type. return [false, :wrong_type] unless @collection_mode_class_names.intersect?(attribute.types) # Flatten nested arrays for uniform validation. values = value.respond_to?(:flatten) ? value&.flatten : value validate_for!(attribute:, values:, option:) end |
#condition_for_input_with(input:, value:, option:) ⇒ Boolean, Array
Validates element types for input attribute.
113 114 115 |
# File 'lib/servactory/tool_kit/dynamic_options/consists_of.rb', line 113 def condition_for_input_with(input:, value:, option:) common_condition_with(attribute: input, value:, option:) end |
#condition_for_internal_with(internal:, value:, option:) ⇒ Boolean, Array
Validates element types for internal attribute.
123 124 125 |
# File 'lib/servactory/tool_kit/dynamic_options/consists_of.rb', line 123 def condition_for_internal_with(internal:, value:, option:) common_condition_with(attribute: internal, value:, option:) end |
#condition_for_output_with(output:, value:, option:) ⇒ Boolean, Array
Validates element types for output attribute.
133 134 135 |
# File 'lib/servactory/tool_kit/dynamic_options/consists_of.rb', line 133 def condition_for_output_with(output:, value:, option:) common_condition_with(attribute: output, value:, option:) end |
#fails_presence_validation?(attribute:, values:, consists_of_types:) ⇒ Boolean
Checks if collection fails presence validation.
185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/servactory/tool_kit/dynamic_options/consists_of.rb', line 185 def fails_presence_validation?(attribute:, values:, consists_of_types:) # NilClass in types allows nil elements. return false if consists_of_types.include?(NilClass) check_present = proc { _1 && !values.all?(&:present?) } [ check_present[attribute.input? && (attribute.required? || (attribute.optional? && values.present?))], check_present[attribute.internal?], check_present[attribute.output?] ].any? end |
#given_type_for(values:, option_value:) ⇒ String
Extracts type names of elements that don’t match expected types.
274 275 276 277 278 279 280 |
# File 'lib/servactory/tool_kit/dynamic_options/consists_of.rb', line 274 def given_type_for(values:, option_value:) return "NilClass" if values.nil? values = values&.flatten if values.respond_to?(:flatten) values.filter { |value| Array(option_value).uniq.exclude?(value.class) }.map(&:class).uniq.join(", ") end |
#message_for_input_with(service:, input:, value:, option_name:, option_value:, reason:) ⇒ String
Generates error message for input validation failure.
209 210 211 212 213 214 215 216 217 218 219 220 221 |
# File 'lib/servactory/tool_kit/dynamic_options/consists_of.rb', line 209 def (service:, input:, value:, option_name:, option_value:, reason:, **) i18n_key = "inputs.validations.must.dynamic_options.consists_of" i18n_key += reason.present? ? ".#{reason}" : ".default" service.translate( i18n_key, service_class_name: service.class_name, input_name: input.name, option_name:, expected_type: Array(option_value).uniq.join(", "), given_type: given_type_for(values: value, option_value:) ) end |
#message_for_internal_with(service:, internal:, value:, option_name:, option_value:, reason:) ⇒ String
Generates error message for internal validation failure.
232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/servactory/tool_kit/dynamic_options/consists_of.rb', line 232 def (service:, internal:, value:, option_name:, option_value:, reason:, **) i18n_key = "internals.validations.must.dynamic_options.consists_of" i18n_key += reason.present? ? ".#{reason}" : ".default" service.translate( i18n_key, service_class_name: service.class_name, internal_name: internal.name, option_name:, expected_type: Array(option_value).uniq.join(", "), given_type: given_type_for(values: value, option_value:) ) end |
#message_for_output_with(service:, output:, value:, option_name:, option_value:, reason:) ⇒ String
Generates error message for output validation failure.
255 256 257 258 259 260 261 262 263 264 265 266 267 |
# File 'lib/servactory/tool_kit/dynamic_options/consists_of.rb', line 255 def (service:, output:, value:, option_name:, option_value:, reason:, **) i18n_key = "outputs.validations.must.dynamic_options.consists_of" i18n_key += reason.present? ? ".#{reason}" : ".default" service.translate( i18n_key, service_class_name: service.class_name, output_name: output.name, option_name:, expected_type: Array(option_value).uniq.join(", "), given_type: given_type_for(values: value, option_value:) ) end |
#validate_for!(attribute:, values:, option:) ⇒ Boolean, Array
Validates all elements against allowed types.
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/servactory/tool_kit/dynamic_options/consists_of.rb', line 162 def validate_for!(attribute:, values:, option:) consists_of_types = Array(option.value).uniq # Check presence requirements. return [false, :required] if fails_presence_validation?(attribute:, values:, consists_of_types:) # Empty optional collections are valid. return true if values.blank? && attribute.input? && attribute.optional? # Verify each element matches allowed types. return true if values.all? do |value| consists_of_types.include?(value.class) end [false, :wrong_element_type] end |