[Dry::Struct] Reusing a set of common attributes


#1

Hi there,

I’m trying to factorize and reuse a list of common attributes in a Dry::Struct.
As a simplified example, consider the following StructA and StructB:

require 'dry-struct'

module Types
  include Dry::Types.module
end

class StructA < Dry::Struct
  attribute :attr_a, Types::String
  attribute :uuid, Types::String
end

class StructB < Dry::Struct
  attribute :attr_b, Types::String
  attribute :uuid, Types::String
end

The uuid attribute is common to both structures. I’d like to make its definition reusable.
Inheritance works, ie. :

require 'dry-struct'

module Types
  include Dry::Types.module
end

class CommonStruct < Dry::Struct
  attribute :uuid, Types::String
end

class StructA < CommonStruct
  attribute :attr_a, Types::String
end

class StructB < CommonStruct
  attribute :attr_b, Types::String
end

But I’d like to mixin the CommonStruct attributes instead of relying on inheritance, ie. something like this:

class StructA < Dry::Struct
  include CommonStruct
  attribute :attr_a, Types::String
end

class StructB < Dry::Struct
  include CommonStruct
  attribute :attr_b, Types::String
end

How should I write the CommonStruct to do so?
Thanks for your help!

Vincent


#2

I found a solution with an eval:

require 'dry-struct'

module Types
  include Dry::Types.module
end

COMMON_STRUCT = "
attribute :uuid, Types::String
"
class StructA < Dry::Struct
  attribute :attr_a, Types::String
  eval COMMON_STRUCT
end

class StructB < Dry::Struct
  attribute :attr_b, Types::String
  eval COMMON_STRUCT
end

Don’t hesitate to post a more elegant solution!


#3

I ended up with the following version:

require ‘dry-struct’

module Types
  include Dry::Types.module
  
  Uuid = Types::String
                  .constructor(&:upcase)
                  .constrained(format: /^[0-9A-F]{8}-[0-9A-F]{4}-[1-5][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/)
end

module CommonAttributes
  def self.included(mod)
    mod::attribute :uuid, Types::Uuid
  end
end

class StructA < Dry::Struct::Value
  include CommonAttributes
  attribute :attr_a, Types::String
end

class StructB < Dry::Struct
  include CommonAttributes
  attribute :attr_b, Types::String
end

Cheers!


#4

nice !

Could be great to have some examples uses case like this one at the end of the documentation site.

Sometimes, some hints like this would give better understanding (cognitive help) when considering usage of dry gems.