module Ardm::Property::Validation

Public Class Methods

rules_for_property(property) click to toggle source

Infer validations for a given property. This will only occur if the option :auto_validation is either true or left undefined.

Triggers that generate validator creation

:required => true
    Setting the option :required to true causes a Rule::Presence
    to be created for the property

:length => 20
    Setting the option :length causes a Rule::Length to be created
    for the property.
    If the value is a Integer the Rule will have :maximum => value.
    If the value is a Range the Rule will have :within => value.

:format => :predefined / lambda / Proc
    Setting the :format option causes a Rule::Format to be created
    for the property

:set => ["foo", "bar", "baz"]
    Setting the :set option causes a Rule::Within to be created
    for the property

Integer type
    Using a Integer type causes a Rule::Numericalness to be created
    for the property.  The Rule's :integer_only option is set to true

BigDecimal or Float type
    Using a Integer type causes a Rule::Numericalness to be created
    for the property.  The Rule's :integer_only option will be set
    to false, and precision/scale will be set to match the Property

Messages

:messages => {..}
    Setting :messages hash replaces standard error messages
    with custom ones. For instance:
    :messages => {:presence => "Field is required",
                  :format => "Field has invalid format"}
    Hash keys are: :presence, :format, :length, :is_unique,
                   :is_number, :is_primitive

:message => "Some message"
    It is just shortcut if only one validation option is set

@api private

# File lib/ardm/property/validation.rb, line 62
def self.rules_for_property(property)
  rule_definitions = []

  # all inferred rules should not be skipped when the value is nil
  #   (aside from Rule::Presence/Rule::Absence)
  opts = { :allow_nil => true }

  if property.options.key?(:validates)
    opts[:context] = property.options[:validates]
  end

  rule_definitions << infer_presence(  property, opts.dup)
  rule_definitions << infer_length(    property, opts.dup)
  rule_definitions << infer_format(    property, opts.dup)
  rule_definitions << infer_uniqueness(property, opts.dup)
  rule_definitions << infer_within(    property, opts.dup)
  rule_definitions << infer_type(      property, opts.dup)

  rule_definitions.compact
end

Private Class Methods

infer_format(property, options) click to toggle source

@api private

# File lib/ardm/property/validation.rb, line 118
def self.infer_format(property, options)
  return unless property.options.key?(:format)

  options[:with] = property.options[:format]

  validation_options = options_with_message(options, property, :format)

  {format: validation_options}
end
infer_length(property, options) click to toggle source

@api private

# File lib/ardm/property/validation.rb, line 96
def self.infer_length(property, options)
  # TODO: return unless property.primitive <= String (?)
  return unless (property.kind_of?(Property::String) ||
                 property.kind_of?(Property::Text))
  length = property.options.fetch(:length, Property::String.length)


  if length.is_a?(Range)
    if length.last == Infinity
      raise ArgumentError, "Infinity is not a valid upper bound for a length range"
    end
    options[:in]  = length
  else
    options[:maximum] = length
  end

  validation_options = options_with_message(options, property, :length)

  {length: validation_options}
end
infer_presence(property, options) click to toggle source

@api private Skip TrueClass dump because presence is invalid for false, but boolean false is ok for a boolean property.

# File lib/ardm/property/validation.rb, line 87
def self.infer_presence(property, options)
  return if property.allow_blank? || property.serial? || property.dump_as == ::TrueClass

  validation_options = options_with_message(options, property, :presence)

  {presence: validation_options}
end
infer_type(property, options) click to toggle source

@api private

# File lib/ardm/property/validation.rb, line 157
def self.infer_type(property, options)
  return if property.respond_to?(:custom?) && property.custom?

  if property.kind_of?(Property::Numeric)
    options[:greater_than_or_equal_to] = property.min if property.min
    options[:less_than_or_equal_to] = property.max if property.max
  end

  if Integer == property.load_as
    options[:only_integer] = true

    validation_options = options_with_message(options, property, :is_number)
    {numericality: validation_options}
  elsif (BigDecimal == property.load_as ||
         Float == property.load_as)
    options[:precision] = property.precision
    options[:scale]     = property.scale

    validation_options = options_with_message(options, property, :is_number)
    {numericality: validation_options}
  else
    # We only need this in the case we don't already
    # have a numeric validator, because otherwise
    # it will cause duplicate validation errors
    validation_options = options_with_message(options, property, :is_primitive)
    # FIXME unsupported in Ardm::Property for now
    nil
  end
end
infer_uniqueness(property, options) click to toggle source

@api private

# File lib/ardm/property/validation.rb, line 129
def self.infer_uniqueness(property, options)
  return unless property.options.key?(:unique)

  case value = property.options[:unique]
  when Array, Symbol
    # TODO: fix this to behave like :unique_index
    options[:scope] = Array(value)

    validation_options = options_with_message(options, property, :is_unique)
    {uniqueness: validation_options}
  when TrueClass
    validation_options = options_with_message(options, property, :is_unique)
    {uniqueness: validation_options}
  end
end
infer_within(property, options) click to toggle source

@api private

# File lib/ardm/property/validation.rb, line 146
def self.infer_within(property, options)
  return unless property.options.key?(:set)

  options[:in] = property.options[:set]
  options[:message] ||= "must be one of #{options[:in].join(', ')}"

  validation_options = options_with_message(options, property, :within)
  {inclusion: validation_options}
end
options_with_message(base_options, property, validator_name) click to toggle source

TODO: eliminate this;

mutating one arg based on a non-obvious interaction of the other two...
well, it makes my skin crawl.

@api private

# File lib/ardm/property/validation.rb, line 192
def self.options_with_message(base_options, property, validator_name)
  options = base_options.clone
  opts    = property.options

  if opts.key?(:messages)
    options[:message] = opts[:messages][validator_name]
  elsif opts.key?(:message)
    options[:message] = opts[:message]
  end

  options
end