Dry-struct: Dependent attributes

Is there any known reason for dry-struct not having the ability to define dependent attributes like FactoryBot dependent attributes?

  class NewUser < Dry::Struct
    attribute :email, Types::String

    dependent_attribute :username, Types::String do
      email.split('@').first
    end

    # or

    attribute :username, Types::String, dependent: true do
      email.split('@').first
    end

    # or
    
    attribute! :username { Types::String.default(email.split('@').first) }
  end

Wouldn’t it have the same reason as default values?

No, and there won’t be such a feature in dry-struct. Mostly because it’s not aligned with how the gem operates, there’s no simple way to implement it. This would also make the DSL more complex, both internally and externally for a small gain.

That said, in some cases, I override the constructor:

class NewUser < Dry::Struct
  attribute :email, Types::String
  attribute :username, Types::String
 
  def self.new(values)
    super(values.merge(username: values[:email].split('@', 2).first))
  end
end

I don’t promise it’ll work in all cases but it does in simple ones.

If you do this, I would call it a code smell in dry-rb land. Structs are meant to be simple data capsules, they should not map data or construct values. Even coercion at a single value level is already a tiny, little code smell, but for now it’s fine since we’re not there yet with a full-stack, stand-alone data transformation solution.

In that case, defaults seem to be a similar code smell too🤔. What dry do you think is more appropiate to tackle this case? For now, I defined a method for that attribute and overrode #to_h to include it. I’m thinking also to a builder…

Yes you are absolutely correct.

For now I recommend doing literally whatever is simpler and works in your case. We’ll provide a nice, 1st-class solution eventually.

1 Like

For now I recommend doing literally whatever is simpler and works in your case. We’ll provide a nice, 1st-class solution eventually.

I think this could work. Not sure how nice but this could work :slight_smile: