dry-validation: Case insensitive `included_in?` validation with Dry::Validation.Schema

691 views Asked by At

I'm trying to create a validation for a predetermined list of valid brands as part of an ETL pipeline. My validation requires case insensitivity, as some brands are compound words or abbreviations that are insignificant.

I created a custom predicate, but I cannot figure out how to generate the appropriate error message.

I read the error messages doc, but am having a hard time interpreting:

  • How to build the syntax for my custom predicate?
  • Can I apply the messages in my schema class directly, without referencing an external .yml file? I looked here and it seems like it's not as straightforward as I'd hoped.

Below I've given code that represents what I have tried using both built-in predicates, and a custom one, each with their own issues. If there is a better way to compose a rule that achieves the same goal, I'd love to learn it.

require 'dry/validation'

CaseSensitiveSchema = Dry::Validation.Schema do
  BRANDS = %w(several hundred valid brands)

  # :included_in? from https://dry-rb.org/gems/dry-validation/basics/built-in-predicates/
  required(:brand).value(included_in?: BRANDS)
end

CaseInsensitiveSchema = Dry::Validation.Schema do
  BRANDS = %w(several hundred valid brands)

  configure do
    def in_brand_list?(value)
      BRANDS.include? value.downcase
    end
  end

  required(:brand).value(:in_brand_list?)
end


# A valid string if case insensitive
valid_product = {brand: 'Valid'}

CaseSensitiveSchema.call(valid_product).errors
# => {:brand=>["must be one of: here, are, some, valid, brands"]} # This message will be ridiculous when the full brand list is applied

CaseInsensitiveSchema.call(valid_product).errors
# => {}   # Good!



invalid_product = {brand: 'Junk'}

CaseSensitiveSchema.call(invalid_product).errors
# => {:brand=>["must be one of: several, hundred, valid, brands"]}  # Good... (Except this error message will contain the entire brand list!!!)

CaseInsensitiveSchema.call(invalid_product).errors
# => Dry::Validation::MissingMessageError: message for in_brand_list? was not found
# => from .. /gems/2.5.0/gems/dry-validation-0.12.2/lib/dry/validation/message_compiler.rb:116:in `visit_predicate'
1

There are 1 answers

0
Mr. Tim On BEST ANSWER

The correct way to reference my error message was to reference the predicate method. No need to worry about arg, value, etc.

en:
  errors:
    in_brand_list?: "must be in the master brands list"

Additionally, I was able to load this error message without a separate .yml by doing this:

CaseInsensitiveSchema = Dry::Validation.Schema do
  BRANDS = %w(several hundred valid brands)

  configure do
    def in_brand_list?(value)
      BRANDS.include? value.downcase
    end

    def self.messages
      super.merge({en: {errors: {in_brand_list?: "must be in the master brand list"}}})
    end     
  end

  required(:brand).value(:in_brand_list?)
end

I'd still love to see other implementations, specifically for a generic case-insensitive predicate. Many people say dry-rb is fantastically organized, but I find it hard to follow.