dry-schema

repository·main·Indexed 19 days ago

https://github.com/dry-rb/dry-schema

A Ruby library for defining data schemas and validating input data. Part of the dry-rb ecosystem, it allows developers to specify expected data types, structures, and constraints, providing detailed error messages and hints when validation fails. It includes specialized schemas for HTTP params and JSON data, as well as a DSL for defining predicates, nested hashes, and arrays.

Tokens
4.1K
Snippets
28
Records
30
Agent score
67%

What's inside dry-schema

  1. Overview of dry-schema

    main
    dry-schema is a library for defining data schemas and validating input data in Ruby. It allows you to define the expected structure, types, and constraints of your data, providing detailed error messages when validation fails. It is part of the dry-rb ecosystem.
  2. Use the dry-schema interactive console

    main

    The bin/console script provides an interactive Pry-based REPL for exploring the dry-schema API. When running the console, you have access to a Context object that provides shorthand methods for defining different types of schemas.

    Available shorthand methods in the console:

    • schema { ... }: Calls Dry::Schema.define.
    • params { ... }: Calls Dry::Schema.Params (optimized for HTTP params, handling string keys/coercion).
    • json { ... }: Calls Dry::Schema.JSON (optimized for JSON data, handling symbol keys).

    Additionally, Dry::Types are available via the Types module.

    # Start the console from your terminal
    ./bin/console
    
    # Inside the console, you can use shorthand methods:
    dry-schema> my_schema = params { required(:name).filled(:string) }
    dry-schema*> my_schema.call(name: 'Alice')
    => {:name=>"Alice"}
  3. Configure `Dry::Schema::Processor` settings

    main

    The Processor class supports several configuration settings via Dry::Configurable:

    • key_map_type: Configures how keys are mapped.
    • type_registry_namespace: Sets the namespace for the type registry (defaults to :strict).
    • filter_empty_string: A boolean setting to determine if empty strings should be filtered (defaults to false).
  4. Negate a predicate using the `!` operator

    main

    In dry-schema, you can negate a predicate within a schema block using the ! operator. This is useful when you want to ensure a value does not satisfy a specific condition (e.g., ensuring a string is not empty).

    Example usage:

    required(:name).value(:string) { !empty? }
  5. Define value constraints with `value()`

    main

    The value() macro sets predicates for a key. It can be used with simple predicates, predicates with arguments, or by passing a block for complex logic. You can also pass a type specification.

    # With a predicate
    required(:name).value(:filled?)
    
    # With a predicate with arguments
    required(:name).value(min_size?: 2)
    
    # With a predicate and arguments
    required(:name).value(:filled?, min_size?: 2)
    
    # With a block
    required(:name).value { filled? & min_size?(2) }
  6. Rebuild a target hash from a source hash using write

    main

    The #write method allows you to take a source hash and rebuild its structure into a target hash based on the keys defined in the KeyMap. This is a core step in schema processing for extracting or restructuring data.

    # source is the input hash, target is the output hash being built
    key_map.write(source, target)
  7. Configure custom message backends via Dry::Schema::Messages::Abstract

    main

    When implementing a custom message backend for dry-schema (e.g., for custom translation logic or error formatting), you should inherit from Dry::Schema::Messages::Abstract. This class provides a configuration DSL and a standardized lookup mechanism for error messages.

    Configuration Options

    You can configure the following settings within the build block or via the configuration object:

    SettingDefault ValueDescription
    default_locale(not specified)The fallback locale for translations
    load_paths[DEFAULT_MESSAGES_PATH]Paths to load message files
    top_namespaceDEFAULT_MESSAGES_ROOTThe top-level namespace for message lookups
    root"errors"The root key for messages
    lookup_options[:root, :predicate, :path, :val_type, :arg_type]Keys to exclude from interpolation options
    lookup_paths(array of templates)Templates used to resolve message keys based on predicate and options
    rule_lookup_paths["rules.%<name>s"]Templates used to resolve rule-specific messages
    arg_typesHash (with Range => "range")Mapping of argument classes to type strings
    val_typesHash (with Range => "range", String => "string")Mapping of value classes to type strings

    Implementation Requirements

    To create a functional backend, your subclass must implement:

    • key?(key, options): Returns true if the message key exists.
    • interpolatable_data(key, options, **data): (Private) Logic to prepare data for interpolation.
    • interpolate(key, options, **data): (Private) Logic to perform the actual string interpolation.
    class MyCustomMessages < Dry::Schema::Messages::Abstract
      def key?(key, options = {})
        # implementation
      end
    
      # ... other required methods
    end
    
    messages = MyCustomMessages.build do |config|
      config.default_locale = :en
      config.root = "my_errors"
    end
  8. Retrieve error texts for a specific key

    main

    If you need to find all error messages associated with a specific attribute, you can use the [] method or the fetch method on the MessageSet.

    • message_set[key]: Returns an Array<String> of error texts for the given key. Returns nil if the key does not exist.
    • message_set.fetch(key): Returns an Array<String> of error texts for the given key. Raises a KeyError if the key is not found.
    # Returns an array of strings, e.g., ["must be filled", "must be a string"]
    errors = result.errors[:email]
    
    # Raises KeyError if :email has no errors
    errors = result.errors.fetch(:email)