All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Break Versioning.
- Performance improvements for building a hash from Dry::Struct when it contains nested structs (found by @rpeng in #200)
-
Added super_diff extension for improved struct diffing in RSpec tests (@flash-gordon in #197)
Add this to your Gemfile:
gem 'super_diff', group: :test
Then activate the extension in your spec_helper:
Dry::Struct.load_extensions(:super_diff)
Now this
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:
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 }>
- Syntax errors on 3.3.0 (@flash-gordon, see dry-rb/dry-types#478)
- 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)
- 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::Erroris now a subclass ofDry::Types::CoercionError(in #193) (@flash-gordon)Dry::Struct#[]now returnsnilif an optional attribute is not set. This is consistent with calling accessor methods for optional attributes. (issue #171 via #194) (@ivleonov + @flash-gordon)
- This version uses dry-core 1.0 (@flash-gordon + @solnic)
- Coercion failures keep the original error instead of just having a string (@flash-gordon + @newx)
- Fixed issues with auto-loading
Extensionsmodule (issue #183 fixed via #184) (@solnic)
- Use zeitwerk for auto-loading (@flash-gordon)
- 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)
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>
-
Nested structures will reuse type and key transformations from the enclosing struct (@flash-gordon)
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::Constructorfinally acts like a fully-featured type (@flash-gordon) -
Dry::Struct.abstractdeclares a struct class as abstract. An abstract class is used as a default superclass for nested structs (@flash-gordon) -
Dry::Struct.to_astand struct compiler (@flash-gordon) -
Struct composition with
Dry::Struct.attributes_from. It's more flexible than inheritance (@waiting-for-dev + @flash-gordon)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
- [internal] metadata is now stored inside schema (@flash-gordon)
Dry::Struct::Valueis deprecated.Dry::Structinstances were never meant to be mutable, we have no support for this. The only difference betweenDry::StructandDry::Struct::Valueis 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 forValue, it won't be hard to roll your own solution using ice_nine (flash-gordon)- In the thread of the previous change, structs now use immutable equalizer. This means
Struct#hashmemoizes its value after the first invocation. Depending on the case, this may speed up your code significantly (flash-gordon)
-
Pattern matching syntax is simplified with
deconstruct_keys(k-tsj)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.
- Experimental support for pattern matching 🎉 (flash-gordon)
Struct.callnow accepts an optional block that will be called on failed coercion. This behavior is consistent with dry-types 1.0. Note that.newdoesn't take a block (flash-gordon)User = Dry::Struct(name: 'string') User.(1) { :oh_no } # => :oh_no
valid?and===behave differently,===works the same wayClass#===does andvalid?checks if the value can be coerced to the struct (flash-gordon)
- [BREAKING]
Struct.inputwas renamedStruct.schema, henceStruct.schemareturns an instance ofDry::Types::Hash::Schemarather than aHash. Schemas are also implementingEnumerablebut they iterate over key types. New API:To get a type by its name useUser.schema.each do |key| puts "Key name: #{ key.name }" puts "Key type: #{ key.type }" end
.key:User.schema.key(:id) # => #<Dry::Types::Hash::Key ...>
- [BREAKING]
transform_typesnow passes one argument to the block, an instance of theKeytype. Combined with the new API from dry-types it simplifies declaring omittable keys:class StructWithOptionalKeys < Dry::Struct transform_types { |key| key.required(false) } # or simply transform_types(&:omittable) end
Dry::Stuct#newis 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.
-
Struct.attribute?is an easy way to define omittable attributes (flash-gordon):class User < Dry::Struct attribute :name, Types::Strict::String attribute? :email, Types::Strict::String end # User.new(name: 'John') # => #<User name="John">
Struct#to_hrecursively converts hash values to hashes, this was done to be consistent with current behavior for arrays (oeoeaio + ZimbiX)
- [BREAKING]
Struct.attribute?in the old sense is deprecated, usehas_attribute?as a replacement
- Pretty print extension (ojab)
Dry::Struct.load_extensions(:pretty_print) PP.pp(user) #<Test::User name="Jane", age=21, address=#<Test::Address city="NYC", zipcode="123">>
- 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)
-
Dry::Struct.transform_typesaccepts a block which is yielded on every type to add. Since types aredry-types' objects that come with a robust DSL it's rather simple to restore the behavior ofconstructor_type. See #64 for details (flash-gordon)Example: evaluate defaults on
nilvaluesclass User < Dry::Struct transform_types do |type| type.constructor { |value| value.nil? ? Undefined : value } end end
-
Data::Struct.transform_keysaccepts a block/proc that transforms keys of input hashes. The most obvious usage is simbolization but arbitrary transformations are allowed (flash-gordon) -
Dry.Structbuilds a struct by a hash of attribute names and types (citizen428)User = Dry::Struct(name: 'strict.string') do attribute :email, 'strict.string' end
-
Support for
Struct.meta, note that.metareturns a new class (flash-gordon)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.attributeyields a block with definition for nested structs. It defines a nested constant for the new struct and supports arrays (AMHOL + flash-gordon)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
- 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)
Struct#newdoesn't call.to_hashrecursively (flash-gordon)
- Attribute readers don't override existing instance methods (solnic)
Struct#newuses raw attributes instead of method calls, this makes the behavior consistent with the change above (flash-gordon)constructor_typenow actively rejects:weakand:symbolizedvalues (GustavoCaso)
Struct.constructorthat makes dry-struct more aligned with dry-types; now you can have a struct with a custom constructor that will be called before calling thenewmethod (v-kolesnikov)Struct.attribute?andStruct.attribute_namesfor introspecting struct attributes (flash-gordon)Struct#__new__is a safe-to-use-in-gems alias forStruct#new(flash-gordon)
Dry::Struct#newmethod to return new instance with applied changeset (Kukunin)
.[]and.calldoes not coerce subclass to superclass anymore (Kukunin)- Raise ArgumentError when attribute type is a string and no value provided is for
new(GustavoCaso)
.newwithout arguments doesn't use nil as an input for non-default types anymore (flash-gordon)
- Fixed
Dry::Struct::Valuewhich appeared to be broken in the last release (flash-gordon)
- Struct attributes can be overridden in a subclass (flash-gordon)
- Make
Dry::Structact as a constrained type. This fixes the behavior of sum types containing structs (flash-gordon)
:strict_with_defaultsconstructor type (backus)
- [BREAKING]
:strictwas renamed to:permissiveas it ignores missing keys (backus) - [BREAKING]
:strictnow raises on unexpected keys (backus) - Structs no longer auto-register themselves in the types container as they implement
Typeinterface and we don't have to wrap them inType::Definition(flash-gordon)
Initial release of code imported from dry-types