Class: Servactory::ToolKit::DynamicOptions::MultipleOf

Inherits:
Must
  • Object
show all
Defined in:
lib/servactory/tool_kit/dynamic_options/multiple_of.rb

Overview

Validates that numeric value is a multiple of a specified number.

## Purpose

MultipleOf ensures that numeric values are evenly divisible by a specified divisor. This is useful for validating quantities, prices, or any values that must conform to specific increments.

## Usage

This option is **NOT included by default**. Register it for each attribute type where you want to use it:

“‘ruby configuration do

input_option_helpers([
  Servactory::ToolKit::DynamicOptions::MultipleOf.use
])

internal_option_helpers([
  Servactory::ToolKit::DynamicOptions::MultipleOf.use
])

output_option_helpers([
  Servactory::ToolKit::DynamicOptions::MultipleOf.use
])

end “‘

Use in your service definition:

“‘ruby class ProcessOrderService < ApplicationService::Base

input :quantity, type: Integer, multiple_of: 5
input :price, type: Float, multiple_of: 0.25
input :batch_size, type: Integer, multiple_of: 100

end “‘

## Simple Mode

Specify divisor directly:

“‘ruby class ProcessOrderService < ApplicationService::Base

input :quantity, type: Integer, multiple_of: 5
input :price, type: Float, multiple_of: 0.25
input :batch_size, type: Integer, multiple_of: 100

end “‘

## Advanced Mode

Specify divisor with custom error message using a hash:

With static message:

“‘ruby input :quantity, type: Integer, multiple_of:

is: 5,
message: "Input `quantity` must be a multiple of 5"

“‘

With dynamic lambda message:

“‘ruby input :quantity, type: Integer, multiple_of:

is: 5,
message: lambda do |input:, value:, option_value:, **|
  "Input `#{input.name` must be divisible by #option_value, got #value"
end

} “‘

Lambda receives the following parameters:

  • For inputs: ‘input:, option_value:, value:, **`

  • For internals: ‘internal:, option_value:, value:, **`

  • For outputs: ‘output:, option_value:, value:, **`

## Supported Types

  • Integer

  • Float

  • Rational

  • BigDecimal

## Important Notes

  • Divisor must be a non-zero Numeric

  • Uses epsilon comparison for floating point precision

  • Returns false for non-numeric values

  • Provides specific error messages for blank and zero divisors

Class Method Summary collapse

Instance Method Summary collapse

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 = :multiple_of) ⇒ Servactory::Maintenance::Attributes::OptionHelper

Creates a MultipleOf validator instance.

Parameters:

  • option_name (Symbol) (defaults to: :multiple_of)

    The option name (default: :multiple_of)

Returns:



104
105
106
# File 'lib/servactory/tool_kit/dynamic_options/multiple_of.rb', line 104

def self.use(option_name = :multiple_of)
  new(option_name).must(:be_multiple_of)
end

Instance Method Details

#common_condition_with(value:, option:) ⇒ Boolean

Common validation logic for all attribute types.

Parameters:

  • value (Object)

    Value to validate

  • option (WorkOption)

    Multiple of configuration

Returns:

  • (Boolean)

    true if value is multiple of divisor



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/servactory/tool_kit/dynamic_options/multiple_of.rb', line 143

def common_condition_with(value:, option:, **) # rubocop:disable Naming/PredicateMethod
  case value
  when Integer, Float, Rational, BigDecimal
    # Validate divisor is present and valid.
    return false if option.value.blank?
    return false unless option.value.is_a?(Numeric)
    return false if option.value.zero?

    # Calculate remainder with epsilon tolerance for floats.
    remainder = value % option.value
    remainder.zero? || remainder.abs < Float::EPSILON * [value.abs, option.value.abs].max
  else
    false
  end
end

#condition_for_input_withBoolean

Validates multiple_of condition for input attribute.

Parameters:

  • input (Object)

    Input attribute object

  • value (Object)

    Value to validate

  • option (WorkOption)

    Multiple of configuration

Returns:

  • (Boolean)

    true if valid



114
115
116
# File 'lib/servactory/tool_kit/dynamic_options/multiple_of.rb', line 114

def condition_for_input_with(...)
  common_condition_with(...)
end

#condition_for_internal_withBoolean

Validates multiple_of condition for internal attribute.

Parameters:

  • internal (Object)

    Internal attribute object

  • value (Object)

    Value to validate

  • option (WorkOption)

    Multiple of configuration

Returns:

  • (Boolean)

    true if valid



124
125
126
# File 'lib/servactory/tool_kit/dynamic_options/multiple_of.rb', line 124

def condition_for_internal_with(...)
  common_condition_with(...)
end

#condition_for_output_withBoolean

Validates multiple_of condition for output attribute.

Parameters:

  • output (Object)

    Output attribute object

  • value (Object)

    Value to validate

  • option (WorkOption)

    Multiple of configuration

Returns:

  • (Boolean)

    true if valid



134
135
136
# File 'lib/servactory/tool_kit/dynamic_options/multiple_of.rb', line 134

def condition_for_output_with(...)
  common_condition_with(...)
end

#message_for_input_with(service:, input:, value:, option_name:, option_value:) ⇒ String

Generates error message for input validation failure.

Selects appropriate message based on divisor state:

  • blank: divisor is nil or empty

  • divided_by_0: divisor is zero

  • default: standard not-multiple-of message

Parameters:

  • service (Object)

    Service context

  • input (Object)

    Input attribute

  • value (Object)

    Failed value

  • option_name (Symbol)

    Option name

  • option_value (Object)

    Divisor value

Returns:

  • (String)

    Localized error message



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/servactory/tool_kit/dynamic_options/multiple_of.rb', line 174

def message_for_input_with(service:, input:, value:, option_name:, option_value:, **) # rubocop:disable Metrics/MethodLength
  i18n_key = "inputs.validations.must.dynamic_options.multiple_of"

  i18n_key += if option_value.blank?
                ".blank"
              elsif option_value.is_a?(Numeric) && option_value.zero?
                ".divided_by_0"
              else
                ".default"
              end

  service.translate(
    i18n_key,
    input_name: input.name,
    value:,
    option_name:,
    option_value: option_value.inspect
  )
end

#message_for_internal_with(service:, internal:, value:, option_name:, option_value:) ⇒ String

Generates error message for internal validation failure.

Parameters:

  • service (Object)

    Service context

  • internal (Object)

    Internal attribute

  • value (Object)

    Failed value

  • option_name (Symbol)

    Option name

  • option_value (Object)

    Divisor value

Returns:

  • (String)

    Localized error message



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/servactory/tool_kit/dynamic_options/multiple_of.rb', line 202

def message_for_internal_with(service:, internal:, value:, option_name:, option_value:, **) # rubocop:disable Metrics/MethodLength
  i18n_key = "internals.validations.must.dynamic_options.multiple_of"

  i18n_key += if option_value.blank?
                ".blank"
              elsif option_value.is_a?(Numeric) && option_value.zero?
                ".divided_by_0"
              else
                ".default"
              end

  service.translate(
    i18n_key,
    internal_name: internal.name,
    value:,
    option_name:,
    option_value: option_value.inspect
  )
end

#message_for_output_with(service:, output:, value:, option_name:, option_value:) ⇒ String

Generates error message for output validation failure.

Parameters:

  • service (Object)

    Service context

  • output (Object)

    Output attribute

  • value (Object)

    Failed value

  • option_name (Symbol)

    Option name

  • option_value (Object)

    Divisor value

Returns:

  • (String)

    Localized error message



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/servactory/tool_kit/dynamic_options/multiple_of.rb', line 230

def message_for_output_with(service:, output:, value:, option_name:, option_value:, **) # rubocop:disable Metrics/MethodLength
  i18n_key = "outputs.validations.must.dynamic_options.multiple_of"

  i18n_key += if option_value.blank?
                ".blank"
              elsif option_value.is_a?(Numeric) && option_value.zero?
                ".divided_by_0"
              else
                ".default"
              end

  service.translate(
    i18n_key,
    output_name: output.name,
    value:,
    option_name:,
    option_value: option_value.inspect
  )
end