dry-struct

v1.8
  1. Introduction
  2. Nested Structs
  3. Recipes

TOC

  1. Symbolize input keys
  2. Tolerance to extra keys
  3. Tolerance to missing keys
  4. Default values
  5. Resolving default values on nil
  6. Creating a custom struct class
  7. Set default value for a nested hash
  8. Composing structs

Recipes

Symbolize input keys

require 'dry-struct'

module Types
  include Dry.Types()
end

class User < Dry::Struct
  transform_keys(&:to_sym)

  attribute :name, Types::String
end

User.new('name' => 'Jane')
# => #<User name="Jane">

Tolerance to extra keys

Structs ignore extra keys by default. This can be changed by replacing the constructor.

class User < Dry::Struct
  # This does the trick
  schema schema.strict

  attribute :name, Types::String
end

User.new(name: 'Jane', age: 21)
# => Dry::Struct::Error ([User.new] unexpected keys [:age] in Hash input)

Tolerance to missing keys

You can mark certain keys as optional by calling attribute?.

class User < Dry::Struct
  attribute :name, Types::String
  attribute? :age, Types::Integer
end

user = User.new(name: 'Jane')
# => #<User name="Jane" age=nil>
user.age
# => nil

In the example above nil violates the type constraint so be careful with attribute?.

Default values

Instead of violating constraints you can assign default values to attributes:

class User < Dry::Struct
  attribute :name, Types::String
  attribute :age,  Types::Integer.default(18)
end

User.new(name: 'Jane')
# => #<User name="Jane" age=18>

Resolving default values on nil

nil as a value isn't replaced with a default value for default types. You may use transform_types to turn all types into constructors which map nil to Dry::Types::Undefined which in order triggers default values.

class User < Dry::Struct
  transform_types do |type|
    if type.default?
      type.constructor do |value|
        value.nil? ? Dry::Types::Undefined : value
      end
    else
      type
    end
  end

  attribute :name, Types::String
  attribute :age,  Types::Integer.default(18)
end

User.new(name: 'Jane')
# => #<User name="Jane" age=18>
User.new(name: 'Jane', age: nil)
# => #<User name="Jane" age=18>

Creating a custom struct class

You can combine examples from this page to create a custom-purposed base struct class and the reuse it your application or gem

class MyStruct < Dry::Struct
  # throw an error when unknown keys provided
  schema schema.strict

  # convert string keys to symbols
  transform_keys(&:to_sym)

  # resolve default types on nil
  transform_types do |type|
    if type.default?
      type.constructor do |value|
        value.nil? ? Dry::Types::Undefined : value
      end
    else
      type
    end
  end
end

Set default value for a nested hash

class Foo < Dry::Struct
  attribute :bar do
    attribute :nested, Types::Integer
  end
end
class Foo < Dry::Struct
  class Bar < Dry::Struct
    attribute :nested, Types::Integer
  end

  attribute :bar, Bar.default { Bar.new(nested: 1) }
end

Composing structs

You can compose other struct attributes as if they
had been defined in place.

class Address < Dry::Struct
  attribute :city, Types::String
  attribute :country, Types::String
end

class User < Dry::Struct
  attribute :name, Types::String
  attributes_from Address
end

User.new(name: 'Quispe', city: 'La Paz', country: 'Bolivia')

Composition can happen within a nested attribute:

class User < Dry::Struct
  attribute :name, Types::String
  attribute :address do
    attributes_from Address
  end
end