module Dry::Struct::ClassInterface

Class-level interface of {Struct} and {Value}

Public Instance Methods

===(other) click to toggle source

@param [Object, Dry::Struct] other @return [Boolean]

# File lib/dry/struct/class_interface.rb, line 350
def ===(other)
  other.is_a?(self)
end
Also aliased as: primitive?
abstract() click to toggle source

Make the struct abstract. This class will be used as a default parent class for nested structs

# File lib/dry/struct/class_interface.rb, line 416
def abstract
  abstract_class self
end
attribute(name, type = Undefined, &block) click to toggle source

Adds an attribute for this {Struct} with given `name` and `type` and modifies {.schema} accordingly.

@param [Symbol] name name of the defined attribute @param [Dry::Types::Type, nil] type or superclass of nested type @return [Dry::Struct] @yield

If a block is given, it will be evaluated in the context of
a new struct class, and set as a nested type for the given
attribute. A class with a matching name will also be defined for
the nested type.

@raise [RepeatedAttributeError] when trying to define attribute with the

same name as previously defined one

@example with nested structs

class Language < Dry::Struct
  attribute :name, Types::String
  attribute :details, Dry::Struct do
    attribute :type, Types::String
  end
end

Language.schema
# => #<Dry::Types[Constructor<Schema<keys={name: Constrained<Nominal<String> rule=[type?(String)]> details: Language::Details}> fn=Kernel.Hash>]>

ruby = Language.new(name: 'Ruby', details: { type: 'OO' })
ruby.name #=> 'Ruby'
ruby.details #=> #<Language::Details type="OO">
ruby.details.type #=> 'OO'

@example with a nested array of structs

class Language < Dry::Struct
  attribute :name, Types::String
  attribute :versions, Types::Array.of(Types::String)
  attribute :celebrities, Types::Array.of(Dry::Struct) do
    attribute :name, Types::String
    attribute :pseudonym, Types::String
  end
end

Language.schema
=> #<Dry::Types[Constructor<Schema<keys={
      name: Constrained<Nominal<String> rule=[type?(String)]>
      versions: Constrained<Array<Constrained<Nominal<String> rule=[type?(String)]>> rule=[type?(Array)]>
      celebrities: Constrained<Array<Language::Celebrity> rule=[type?(Array)]>
   }> fn=Kernel.Hash>]>

ruby = Language.new(
  name: 'Ruby',
  versions: %w(1.8.7 1.9.8 2.0.1),
  celebrities: [
    { name: 'Yukihiro Matsumoto', pseudonym: 'Matz' },
    { name: 'Aaron Patterson', pseudonym: 'tenderlove' }
  ]
)
ruby.name #=> 'Ruby'
ruby.versions #=> ['1.8.7', '1.9.8', '2.0.1']
ruby.celebrities
  #=> [
        #<Language::Celebrity name='Yukihiro Matsumoto' pseudonym='Matz'>,
        #<Language::Celebrity name='Aaron Patterson' pseudonym='tenderlove'>
      ]
ruby.celebrities[0].name #=> 'Yukihiro Matsumoto'
ruby.celebrities[0].pseudonym #=> 'Matz'
ruby.celebrities[1].name #=> 'Aaron Patterson'
ruby.celebrities[1].pseudonym #=> 'tenderlove'
# File lib/dry/struct/class_interface.rb, line 96
def attribute(name, type = Undefined, &block)
  attributes(name => build_type(name, type, &block))
end
attribute?(*args, &block) click to toggle source

Adds an omittable (key is not required on initialization) attribute for this {Struct}

@example

class User < Dry::Struct
  attribute  :name,  Types::String
  attribute? :email, Types::String
end

User.new(name: 'John') # => #<User name="John" email=nil>

@param [Symbol] name name of the defined attribute @param [Dry::Types::Type, nil] type or superclass of nested type @return [Dry::Struct]

# File lib/dry/struct/class_interface.rb, line 149
def attribute?(*args, &block)
  if args.size == 1 && block.nil?
    Core::Deprecations.warn(
      "Dry::Struct.attribute? is deprecated for checking attribute presence, "\
      "use has_attribute? instead",
      tag: :'dry-struct'
    )

    has_attribute?(args[0])
  else
    name, * = args

    attribute(:"#{name}?", build_type(*args, &block))
  end
end
attribute_names() click to toggle source

Gets the list of attribute names

@return [Array<Symbol>]

# File lib/dry/struct/class_interface.rb, line 386
def attribute_names
  @attribute_names ||= schema.map(&:name)
end
attributes(new_schema) click to toggle source

@param [Hash{Symbol => Dry::Types::Type}] new_schema @return [Dry::Struct] @raise [RepeatedAttributeError] when trying to define attribute with the

same name as previously defined one

@see attribute @example

class Book < Dry::Struct
  attributes(
    title: Types::String,
    author: Types::String
  )
end

Book.schema
# => #<Dry::Types[Constructor<Schema<keys={
#      title: Constrained<Nominal<String> rule=[type?(String)]>
#      author: Constrained<Nominal<String> rule=[type?(String)]>
#    }> fn=Kernel.Hash>]>
# File lib/dry/struct/class_interface.rb, line 183
def attributes(new_schema)
  keys = new_schema.keys.map { |k| k.to_s.chomp("?").to_sym }
  check_schema_duplication(keys)

  schema schema.schema(new_schema)

  define_accessors(keys)

  @attribute_names = nil

  direct_descendants = descendants.select { |d| d.superclass == self }
  direct_descendants.each do |d|
    inherited_attrs = new_schema.reject { |k, _| d.has_attribute?(k.to_s.chomp("?").to_sym) }
    d.attributes(inherited_attrs)
  end

  self
end
attributes_from(struct) click to toggle source

Add atributes from another struct

@example

class Address < Dry::Struct
  attribute :city, Types::String
  attribute :country, Types::String
end

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

User.new(name: 'Quispe', city: 'La Paz', country: 'Bolivia')

@example with nested structs

class User < Dry::Struct
  attribute :name, Types::String
  attribute :address do
    attributes_from Address
  end
end

@param struct [Dry::Struct]

# File lib/dry/struct/class_interface.rb, line 124
def attributes_from(struct)
  extracted_schema = struct.schema.keys.map { |key|
    if key.required?
      [key.name, key.type]
    else
      [:"#{key.name}?", key.type]
    end
  }.to_h
  attributes(extracted_schema)
end
call_safe(input, &block) click to toggle source

@api private

# File lib/dry/struct/class_interface.rb, line 272
def call_safe(input, &block)
  if input.is_a?(self)
    input
  else
    new(input, true, &block)
  end
end
call_unsafe(input) click to toggle source

@api private

# File lib/dry/struct/class_interface.rb, line 281
def call_unsafe(input)
  if input.is_a?(self)
    input
  else
    new(input)
  end
end
constrained?() click to toggle source

@return [true]

# File lib/dry/struct/class_interface.rb, line 356
def constrained?
  true
end
constructor(constructor = nil, **, &block) click to toggle source

@param [#call,nil] constructor @param [#call,nil] block @return [Dry::Struct::Constructor]

# File lib/dry/struct/class_interface.rb, line 299
def constructor(constructor = nil, **, &block)
  Constructor[self, fn: constructor || block]
end
default?() click to toggle source

@return [false]

# File lib/dry/struct/class_interface.rb, line 344
def default?
  false
end
failure(*args) click to toggle source

@param [({Symbol => Object})] args @return [Dry::Types::Result::Failure]

# File lib/dry/struct/class_interface.rb, line 333
def failure(*args)
  result(Types::Result::Failure, *args)
end
has_attribute?(key) click to toggle source

Checks if this {Struct} has the given attribute

@param [Symbol] key Attribute name @return [Boolean]

# File lib/dry/struct/class_interface.rb, line 379
def has_attribute?(key)
  schema.key?(key)
end
inherited(klass) click to toggle source

@param [Class] klass

Calls superclass method
# File lib/dry/struct/class_interface.rb, line 22
def inherited(klass)
  super

  unless klass.name.eql?("Dry::Struct::Value")
    klass.extend(Core::DescendantsTracker)
  end
end
load(attributes) click to toggle source

@api private

# File lib/dry/struct/class_interface.rb, line 290
def load(attributes)
  struct = allocate
  struct.__send__(:initialize, attributes)
  struct
end
meta(meta = Undefined) click to toggle source

@return [{Symbol => Object}]

# File lib/dry/struct/class_interface.rb, line 391
def meta(meta = Undefined)
  if meta.equal?(Undefined)
    schema.meta
  elsif meta.empty?
    self
  else
    ::Class.new(self) do
      schema schema.meta(meta) unless meta.empty?
    end
  end
end
new(attributes = default_attributes, safe = false) { |output| ... } click to toggle source

@param [Hash{Symbol => Object},Dry::Struct] attributes @raise [Struct::Error] if the given attributes don't conform {#schema}

# File lib/dry/struct/class_interface.rb, line 250
def new(attributes = default_attributes, safe = false, &block)
  if attributes.is_a?(Struct)
    if equal?(attributes.class)
      attributes
    else
      # This implicit coercion is arguable but makes sense overall
      # in cases there you pass child struct to the base struct constructor
      # User.new(super_user)
      #
      # We may deprecate this behavior in future forcing people to be explicit
      new(attributes.to_h, safe, &block)
    end
  elsif safe
    load(schema.call_safe(attributes) { |output = attributes| return yield output })
  else
    load(schema.call_unsafe(attributes))
  end
rescue Types::CoercionError => e
  raise Error, "[#{self}.new] #{e}", e.backtrace
end
optional?() click to toggle source

@return [false]

# File lib/dry/struct/class_interface.rb, line 366
def optional?
  false
end
primitive() click to toggle source

@return [self]

# File lib/dry/struct/class_interface.rb, line 361
def primitive
  self
end
primitive?(other)
Alias for: ===
result(klass, *args) click to toggle source

@param [Class] klass @param [({Symbol => Object})] args

# File lib/dry/struct/class_interface.rb, line 339
def result(klass, *args)
  klass.new(*args)
end
success(*args) click to toggle source

@param [({Symbol => Object})] args @return [Dry::Types::Result::Success]

# File lib/dry/struct/class_interface.rb, line 327
def success(*args)
  result(Types::Result::Success, *args)
end
to_ast(meta: true) click to toggle source

Dump to the AST

@return [Array]

@api public

# File lib/dry/struct/class_interface.rb, line 425
def to_ast(meta: true)
  [:struct, [::WeakRef.new(self), schema.to_ast(meta: meta)]]
end
to_proc() click to toggle source

@return [Proc]

# File lib/dry/struct/class_interface.rb, line 371
def to_proc
  @to_proc ||= proc { |input| call(input) }
end
transform_keys(proc = nil, &block) click to toggle source

Add an arbitrary transformation for input hash keys.

@param [#call,nil] proc @param [#call,nil] block @example

class Book < Dry::Struct
  transform_keys(&:to_sym)

  attribute :title, Types::String
end

Book.new('title' => "The Old Man and the Sea")
# => #<Book title="The Old Man and the Sea">
# File lib/dry/struct/class_interface.rb, line 232
def transform_keys(proc = nil, &block)
  schema schema.with_key_transform(proc || block)
end
transform_types(proc = nil, &block) click to toggle source

Add an arbitrary transformation for new attribute types.

@param [#call,nil] proc @param [#call,nil] block @example

class Book < Dry::Struct
  transform_types { |t| t.meta(struct: :Book) }

  attribute :title, Types::String
end

Book.schema.key(:title).meta # => { struct: :Book }
# File lib/dry/struct/class_interface.rb, line 215
def transform_types(proc = nil, &block)
  schema schema.with_type_transform(proc || block)
end
try(input) { |failure_result| ... } click to toggle source

@param [Hash{Symbol => Object},Dry::Struct] input @yieldparam [Dry::Types::Result::Failure] failure @yieldreturn [Dry::Types::ResultResult] @return [Dry::Types::Result]

# File lib/dry/struct/class_interface.rb, line 307
def try(input)
  success(self[input])
rescue Error => e
  failure_result = failure(input, e.message)
  block_given? ? yield(failure_result) : failure_result
end
try_struct(input) { || ... } click to toggle source

@param [Hash{Symbol => Object},Dry::Struct] input @return [Dry::Types::Result] @private

# File lib/dry/struct/class_interface.rb, line 317
def try_struct(input)
  if input.is_a?(self)
    input
  else
    yield
  end
end
|(type) click to toggle source

Build a sum type @param [Dry::Types::Type] type @return [Dry::Types::Sum]

Calls superclass method
# File lib/dry/struct/class_interface.rb, line 406
def |(type)
  if type.is_a?(::Class) && type <= Struct
    Sum.new(self, type)
  else
    super
  end
end

Private Instance Methods

build_type(name, type = Undefined, &block) click to toggle source

Constructs a type

@return [Dry::Types::Type, Dry::Struct]

# File lib/dry/struct/class_interface.rb, line 459
def build_type(name, type = Undefined, &block)
  type_object =
    if type.is_a?(::String)
      Types[type]
    elsif block.nil? && Undefined.equal?(type)
      raise(
        ::ArgumentError,
        "you must supply a type or a block to `Dry::Struct.attribute`"
      )
    else
      type
    end

  if block
    struct_builder.(name, type_object, &block)
  else
    type_object
  end
end
check_schema_duplication(new_keys) click to toggle source

@param [Hash{Symbol => Dry::Types::Type, Dry::Struct}] new_keys @raise [RepeatedAttributeError] when trying to define attribute with the

same name as previously defined one
# File lib/dry/struct/class_interface.rb, line 239
def check_schema_duplication(new_keys)
  overlapping_keys = new_keys & (attribute_names - superclass.attribute_names)

  if overlapping_keys.any?
    raise RepeatedAttributeError, overlapping_keys.first
  end
end
default_attributes(default_schema = schema) click to toggle source

Retrieves default attributes from defined {.schema}. Used in a {Struct} constructor if no attributes provided to {.new}

@return [Hash{Symbol => Object}]

# File lib/dry/struct/class_interface.rb, line 440
def default_attributes(default_schema = schema)
  default_schema.each_with_object({}) do |key, result|
    result[key.name] = default_attributes(key.schema) if struct?(key.type)
  end
end
define_accessors(keys) click to toggle source
# File lib/dry/struct/class_interface.rb, line 480
      def define_accessors(keys)
        keys.each do |key|
          next if instance_methods.include?(key)

          class_eval(<<-RUBY)
            def #{key}
              @attributes[#{key.inspect}]
            end
          RUBY
        end
      end
struct?(type) click to toggle source

Checks if the given type is a Dry::Struct

@param [Dry::Types::Type] type @return [Boolean]

# File lib/dry/struct/class_interface.rb, line 451
def struct?(type)
  type.is_a?(::Class) && type <= Struct
end
struct_builder() click to toggle source

Stores an object for building nested struct classes @return [StructBuilder]

# File lib/dry/struct/class_interface.rb, line 431
def struct_builder
  @struct_builder ||= StructBuilder.new(self).freeze
end