Dependent types in Ruby with dry-rb
Dry-rb supports dependent types (i.e., it allows you to match types based on the value):
class HasA < Dry::Struct
attribute :a, Types::Symbol
end
class HasB < Dry::Struct
attribute :b, Types::Symbol
end
class ShortText < Dry::Struct
attribute :text, Types::String.constrained(max_size: 3)
end
class LongText < Dry::Struct
attribute :text, Types::String.constrained(min_size: 4)
end
class OurStruct < Dry::Struct
attribute :a_or_b, Types::Array.of(A | B)
attribute :texts, Types::Array.of(ShortText | LongText)
end
> struct = OurStruct.new(
> a_or_b: [{ a: :abc } , { b: :def }],
> texts: [{ text: 'abc' } , { text: 'defgh' }],
> )
=> #<OurStruct
=> # a_or_b=[#<A a=:abc>, #<B b=:def>]
=> # texts=[#<ShortText text="abc">, #<LongText text="defgh">]
=> #>
You can use it to, e.g., parse data and then attach specific behavior to it:
module Types
EventData = Types::Hash.schema(name: Types::String, value: Types::Integer)
end
class GroupCondition < Dry::Struct
attribute :event, Types::Value(:group_formed)
attribute :data, Types::EventData
def applicable?
Group.exists?(name: data.fetch(:name)) && data.fetch(:value) >= 123
end
end
class InvitationCondition < Dry::Struct
attribute :event, Types::Value(:invitation_sent)
attribute :data, Types::EventData
def applicable?
Invitation.exists?(value: data.fetch(:value))
end
end
class ConditionsSet < Dry::Struct
attribute :conditions, Types::Array.of(GroupCondition | InvitationCondition)
end
> set = ConditionsSet.new(
> conditions: [
> { event: :invitation_sent, data: { name: 'abc', value: 123 } },
> { event: :group_formed, data: { name: 'def', value: 456 } }
> ]
> )
=> #<ConditionsSet
=> # conditions=[
=> # <InvitationCondition event=:invitation_sent data={:name=>"abc", :value=>123}>,
=> # <GroupCondition event=:group_formed data={:name=>"def", :value=>456}>
=> # ]
=> #>
> set.conditions.all?(&:applicable?)
=> true
Tweet