# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Break Versioning](https://www.taoensso.com/break-versioning).

## [Unreleased]

### Added

### Changed

### Deprecated

### Removed

### Fixed

### Security

[Unreleased]: https://github.com/dry-rb/dry-struct/compare/v1.8.1...main

## [1.8.1] - 2026-03-03

### Changed

- Performance improvements for building a hash from `Dry::Struct` when it contains nested structs. (@katafrakt in #202, identified by @rpeng in #200)

[1.8.1]: https://github.com/dry-rb/dry-struct/compare/v1.8.0...v1.8.1

## [1.8.0] - 2025-03-09

### Added

- Added super_diff extension for improved struct diffing in RSpec tests (@flash-gordon in #197)

  Add this to your Gemfile:
  ```ruby
  gem 'super_diff', group: :test
  ```

  Then activate the extension in your spec_helper:
  ```ruby
  Dry::Struct.load_extensions(:super_diff)
  ```

  Now this

  ```ruby
    expected: #<Test::User name="Jane" age=22>
          got: #<Test::User name="Jane" age=21>

    (compared using eql?)

    Diff:
    @@ -1 +1 @@
    -#<Test::User name="Jane" age=22>
    +#<Test::User name="Jane" age=21>
  ```

  will become this:

  ```ruby
    expected: #<Test::User name: "Jane", age: 22>
          got: #<Test::User name: "Jane", age: 21>

    (compared using eql?)

      #<Test::User {
        name: "Jane",
    -   age: 22
    +   age: 21
      }>
  ```

[1.8.0]: https://github.com/dry-rb/dry-struct/compare/v1.7.1...v1.8.0

## [1.7.1] - 2025-01-31

### Fixed

- Syntax errors on 3.3.0 (@flash-gordon, see https://github.com/dry-rb/dry-types/issues/478)

[1.7.1]: https://github.com/dry-rb/dry-struct/compare/v1.7.0...v1.7.1

## [1.7.0] - 2025-01-06

### Fixed

- Fixed coercion errors for structs (issue #192 via #193) (@flash-gordon)
- Invalid method names are now allowed as struct attributes (issue #169 via #195) (@flash-gordon)

### Changed

- Missing attribute error now includes the name of the class (issue #170 via #191) (@phillipoertel + @cllns)
- 3.1 is now the minimum Ruby version (@flash-gordon)
- `Dry::Struct::Error` is now a subclass of `Dry::Types::CoercionError` (in #193) (@flash-gordon)
- `Dry::Struct#[]` now returns `nil` if an optional attribute is not set. This is consistent with calling accessor methods for optional attributes. (issue #171 via #194) (@ivleonov + @flash-gordon)

[1.7.0]: https://github.com/dry-rb/dry-struct/compare/v1.6.0...v1.7.0

## [1.6.0] - 2022-11-04

### Changed

- This version uses dry-core 1.0 (@flash-gordon + @solnic)

[1.6.0]: https://github.com/dry-rb/dry-struct/compare/v1.5.2...v1.6.0

## [1.5.2] - 2022-10-19

### Fixed

- Coercion failures keep the original error instead of just having a string (@flash-gordon + @newx)

[1.5.2]: https://github.com/dry-rb/dry-struct/compare/v1.5.1...v1.5.2

## [1.5.1] - 2022-10-17

### Fixed

- Fixed issues with auto-loading `Extensions` module (issue #183 fixed via #184) (@solnic)

[1.5.1]: https://github.com/dry-rb/dry-struct/compare/v1.5.0...v1.5.1

## [1.5.0] - 2022-10-15

### Changed

- Use zeitwerk for auto-loading (@flash-gordon)

[1.5.0]: https://github.com/dry-rb/dry-struct/compare/v1.4.0...v1.5.0

## [1.4.0] - 2021-01-21

### Added

- Support for wrapping constructors and fallbacks, see release notes for dry-types 1.5.0 (@flash-gordon)
- Improvements of the attribute DSL, now it's possible to use optional structs as a base class (@flash-gordon)
  ```ruby
  class User < Dry::Struct
    attribute :name, Types::String
    attribute :address, Dry::Struct.optional do
      attribute :city, Types::String
    end
  end

  User.new(name: "John", address: nil) # => #<User name="John" address=nil>
  ```

[1.4.0]: https://github.com/dry-rb/dry-struct/compare/v1.3.0...v1.4.0

## [1.3.0] - 2020-02-10

### Added

- Nested structures will reuse type and key transformations from the enclosing struct (@flash-gordon)

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

    attribute :name, Types::String
    attribute :address do
      # this struct will inherit transform_keys(&:to_sym)
      attribute :city, Types::String
    end

    # nested struct will _not_ transform keys because a parent
    # struct is given
    attribute :contacts, Dry::Struct do
      attribute :email, Types::String
    end
  end
  ```
- `Dry::Struct::Constructor` finally acts like a fully-featured type (@flash-gordon)
- `Dry::Struct.abstract` declares a struct class as abstract. An abstract class is used as a default superclass for nested structs (@flash-gordon)
- `Dry::Struct.to_ast` and struct compiler (@flash-gordon)
- Struct composition with `Dry::Struct.attributes_from`. It's more flexible than inheritance (@waiting-for-dev + @flash-gordon)

  ```ruby
  class Address < Dry::Struct
    attribute :city, Types::String
    attribute :zipcode, Types::String
  end

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

  class Seller < Dry::Struct
    attribute :name, Types::String
    attribute :email, Types::String
    attributes_from Address
  end
  ```

### Changed

- [internal] metadata is now stored inside schema (@flash-gordon)

[1.3.0]: https://github.com/dry-rb/dry-struct/compare/v1.2.0...v1.3.0

## [1.2.0] - 2019-12-20

### Changed

- `Dry::Struct::Value` is deprecated. `Dry::Struct` instances were never meant to be mutable, we have no support for this. The only difference between `Dry::Struct` and `Dry::Struct::Value` is that the latter is deeply frozen. Freezing objects slows the code down and gives you very little benefit in return. If you have a use case for `Value`, it won't be hard to roll your own solution using [ice_nine](https://github.com/dkubb/ice_nine) (flash-gordon)
- In the thread of the previous change, structs now use immutable equalizer. This means `Struct#hash` memoizes its value after the first invocation. Depending on the case, this may speed up your code significantly (flash-gordon)

[1.2.0]: https://github.com/dry-rb/dry-struct/compare/v1.1.1...v1.2.0

## [1.1.1] - 2019-10-13

### Changed

- Pattern matching syntax is simplified with `deconstruct_keys` (k-tsj)

  ```ruby
  User = Dry.Struct(name: 'string', email: 'string')

  user = User.new(name: 'John Doe', email: 'john@acme.org')

  case user
  in User(name: 'John Doe', email:)
    puts email
  else
    puts 'Not John'
  end
  ```

  See more examples in the [specs](https://github.com/dry-rb/dry-struct/blob/8112772eb08d22ff2cd3e6997514d79a9b124968/spec/dry/struct/pattern_matching_spec.rb).

[1.1.1]: https://github.com/dry-rb/dry-struct/compare/v1.1.0...v1.1.1

## [1.1.0] - 2019-10-07

### Added

- Experimental support for pattern matching :tada: (flash-gordon)

[1.1.0]: https://github.com/dry-rb/dry-struct/compare/v1.0.0...v1.1.0

## [1.0.0] - 2019-04-23

### Added

- `Struct.call` now accepts an optional block that will be called on failed coercion. This behavior is consistent with dry-types 1.0. Note that `.new` doesn't take a block (flash-gordon)
  ```ruby
  User = Dry::Struct(name: 'string')
  User.(1) { :oh_no }
  # => :oh_no
  ```

### Changed

- `valid?` and `===` behave differently, `===` works the same way `Class#===` does and `valid?` checks if the value _can be_ coerced to the struct (flash-gordon)

[1.0.0]: https://github.com/dry-rb/dry-struct/compare/v0.7.0...v1.0.0

## [0.7.0] - 2019-03-22

### Changed

- [BREAKING] `Struct.input` was renamed `Struct.schema`, hence `Struct.schema` returns an instance of `Dry::Types::Hash::Schema` rather than a `Hash`. Schemas are also implementing `Enumerable` but they iterate over key types.
  New API:
  ```ruby
  User.schema.each do |key|
    puts "Key name: #{ key.name }"
    puts "Key type: #{ key.type }"
  end
  ```
  To get a type by its name use `.key`:
  ```ruby
  User.schema.key(:id) # => #<Dry::Types::Hash::Key ...>
  ```
- [BREAKING] `transform_types` now passes one argument to the block, an instance of the `Key` type. Combined with the new API from dry-types it simplifies declaring omittable keys:
  ```ruby
  class StructWithOptionalKeys < Dry::Struct
    transform_types { |key| key.required(false) }
    # or simply
    transform_types(&:omittable)
  end
  ```
- `Dry::Stuct#new` is now more efficient for partial updates (flash-gordon)
- Ruby 2.3 is EOL and not officially supported. It may work but we don't test it.

[0.7.0]: https://github.com/dry-rb/dry-struct/compare/v0.6.0...v0.7.0

## [0.6.0] - 2018-10-24

### Added

- `Struct.attribute?` is an easy way to define omittable attributes (flash-gordon):

  ```ruby
  class User < Dry::Struct
    attribute  :name,  Types::Strict::String
    attribute? :email, Types::Strict::String
  end
  # User.new(name: 'John') # => #<User name="John">
  ```

### Fixed

- `Struct#to_h` recursively converts hash values to hashes, this was done to be consistent with current behavior for arrays (oeoeaio + ZimbiX)

### Changed

- [BREAKING] `Struct.attribute?` in the old sense is deprecated, use `has_attribute?` as a replacement

[0.6.0]: https://github.com/dry-rb/dry-struct/compare/v0.5.1...v0.6.0

## [0.5.1] - 2018-08-11

### Added

- Pretty print extension (ojab)
  ```ruby
  Dry::Struct.load_extensions(:pretty_print)
  PP.pp(user)
  #<Test::User
   name="Jane",
   age=21,
   address=#<Test::Address city="NYC", zipcode="123">>
  ```

### Fixed

- Constant resolution is now restricted to the current module when structs are automatically defined using the block syntax. This shouldn't break any existing code (piktur)

[0.5.1]: https://github.com/dry-rb/dry-struct/compare/v0.5.0...v0.5.1

## [0.5.0] - 2018-05-03

### Added

- `Dry::Struct.transform_types` accepts a block which is yielded on every type to add. Since types are `dry-types`' objects that come with a robust DSL it's rather simple to restore the behavior of `constructor_type`. See https://github.com/dry-rb/dry-struct/pull/64 for details (flash-gordon)

  Example: evaluate defaults on `nil` values

  ```ruby
  class User < Dry::Struct
    transform_types do |type|
      type.constructor { |value| value.nil? ? Undefined : value  }
    end
  end
  ```
- `Data::Struct.transform_keys` accepts a block/proc that transforms keys of input hashes. The most obvious usage is simbolization but arbitrary transformations are allowed (flash-gordon)
- `Dry.Struct` builds a struct by a hash of attribute names and types (citizen428)

  ```ruby
  User = Dry::Struct(name: 'strict.string') do
    attribute :email, 'strict.string'
  end
  ```
- Support for `Struct.meta`, note that `.meta` returns a _new class_ (flash-gordon)

  ```ruby
  class User < Dry::Struct
    attribute :name, Dry::Types['strict.string']
  end

  UserWithMeta = User.meta(foo: :bar)

  User.new(name: 'Jade').class == UserWithMeta.new(name: 'Jade').class # => false
  ```
- `Struct.attribute` yields a block with definition for nested structs. It defines a nested constant for the new struct and supports arrays (AMHOL + flash-gordon)

  ```ruby
    class User < Dry::Struct
      attribute :name, Types::Strict::String
      attribute :address do
        attribute :country, Types::Strict::String
        attribute :city, Types::Strict::String
      end
      attribute :accounts, Types::Strict::Array do
        attribute :currency, Types::Strict::String
        attribute :balance, Types::Strict::Decimal
      end
    end

    # ^This automatically defines User::Address and User::Account
  ```

### Fixed

- Adding a new attribute invalidates `attribute_names` (flash-gordon)
- Struct classes track subclasses and define attributes in them, now it doesn't matter whether you define attributes first and _then_ subclass or vice versa. Note this can lead to memory leaks in Rails environment when struct classes are reloaded (flash-gordon)

[0.5.0]: https://github.com/dry-rb/dry-struct/compare/v0.4.0...v0.5.0

## [0.4.0] - 2017-11-04

### Fixed

- `Struct#new` doesn't call `.to_hash` recursively (flash-gordon)

### Changed

- Attribute readers don't override existing instance methods (solnic)
- `Struct#new` uses raw attributes instead of method calls, this makes the behavior consistent with the change above (flash-gordon)
- `constructor_type` now actively rejects `:weak` and `:symbolized` values (GustavoCaso)

[0.4.0]: https://github.com/dry-rb/dry-struct/compare/v0.3.1...v0.4.0

## [0.3.1] - 2017-06-30

### Added

- `Struct.constructor` that makes dry-struct more aligned with dry-types; now you can have a struct with a custom constructor that will be called _before_ calling the `new` method (v-kolesnikov)
- `Struct.attribute?` and `Struct.attribute_names` for introspecting struct attributes (flash-gordon)
- `Struct#__new__` is a safe-to-use-in-gems alias for `Struct#new` (flash-gordon)

[0.3.1]: https://github.com/dry-rb/dry-struct/compare/v0.3.0...v0.3.1

## [0.3.0] - 2017-05-05

### Added

- `Dry::Struct#new` method to return new instance with applied changeset (Kukunin)

### Fixed

- `.[]` and `.call` does not coerce subclass to superclass anymore (Kukunin)
- Raise ArgumentError when attribute type is a string and no value provided is for `new` (GustavoCaso)

### Changed

- `.new` without arguments doesn't use nil as an input for non-default types anymore (flash-gordon)

[0.3.0]: https://github.com/dry-rb/dry-struct/compare/v0.2.1...v0.3.0

## [0.2.1] - 2017-02-27

### Fixed

- Fixed `Dry::Struct::Value` which appeared to be broken in the last release (flash-gordon)

[0.2.1]: https://github.com/dry-rb/dry-struct/compare/v0.2.0...v0.2.1

## [0.2.0] - 2016-02-26

### Changed

- Struct attributes can be overridden in a subclass (flash-gordon)

[0.2.0]: https://github.com/dry-rb/dry-struct/compare/v0.1.1...v0.2.0

## [0.1.1] - 2016-11-13

### Fixed

- Make `Dry::Struct` act as a constrained type. This fixes the behavior of sum types containing structs (flash-gordon)

[0.1.1]: https://github.com/dry-rb/dry-struct/compare/v0.1.0...v0.1.1

## [0.1.0] - 2016-09-21

### Added

- `:strict_with_defaults` constructor type (backus)

### Changed

- [BREAKING] `:strict` was renamed to `:permissive` as it ignores missing keys (backus)
- [BREAKING] `:strict` now raises on unexpected keys (backus)
- Structs no longer auto-register themselves in the types container as they implement `Type` interface and we don't have to wrap them in `Type::Definition` (flash-gordon)

[0.1.0]: https://github.com/dry-rb/dry-struct/compare/v0.0.1...v0.1.0

## [0.0.1] - 2016-07-17

Initial release of code imported from dry-types

[0.0.1]: https://github.com/dry-rb/dry-struct/releases/tag/v0.0.1
