Module: Dry::Struct::ClassInterface

Includes:
Core::ClassAttributes, Types::Builder, Types::Type
Included in:
Dry::Struct
Defined in:
lib/dry/struct/class_interface.rb

Overview

Class-level interface of Dry::Struct and Value

Instance Method Summary collapse

Instance Method Details

#===(other) ⇒ Boolean Also known as: primitive?

Parameters:

Returns:

  • (Boolean)


331
# File 'lib/dry/struct/class_interface.rb', line 331

def ===(other) = other.is_a?(self)

#abstractObject

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



387
388
389
# File 'lib/dry/struct/class_interface.rb', line 387

def abstract
  abstract_class self
end

#attribute(name, type = Undefined) { ... } ⇒ Dry::Struct

Adds an attribute for this Dry::Struct with given ‘name` and `type` and modifies Dry::Struct#schema accordingly.

Examples:

with nested structs

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

Language.schema # new lines for readability
# => #<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'

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 # new lines for readability
=> #<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'

Parameters:

  • name (Symbol)

    name of the defined attribute

  • type (Dry::Types::Type, nil) (defaults to: Undefined)

    or superclass of nested type

Yields:

  • 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.

Returns:

Raises:



86
87
88
# File 'lib/dry/struct/class_interface.rb', line 86

def attribute(name, type = Undefined, &)
  attributes(name => build_type(name, type, &))
end

#attribute?(*args) ⇒ Dry::Struct

Adds an omittable (key is not required on initialization) attribute for this Dry::Struct

Examples:

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

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

Parameters:

  • name (Symbol)

    name of the defined attribute

  • type (Dry::Types::Type, nil)

    or superclass of nested type

Returns:



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/dry/struct/class_interface.rb', line 139

def attribute?(*args, &)
  if args.size == 1 && !block_given?
    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, &))
  end
end

#attribute_namesArray<Symbol>

Gets the list of attribute names

Returns:

  • (Array<Symbol>)


357
358
359
# File 'lib/dry/struct/class_interface.rb', line 357

def attribute_names
  @attribute_names ||= schema.map(&:name)
end

#attributes(new_schema) ⇒ Dry::Struct

Examples:

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>]>

Parameters:

  • new_schema (Hash{Symbol => Dry::Types::Type})

Returns:

Raises:

See Also:



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/dry/struct/class_interface.rb', line 173

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

  subclasses.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) ⇒ Object

Add atributes from another struct

Examples:

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')

with nested structs

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

Parameters:



114
115
116
117
118
119
120
121
122
123
# File 'lib/dry/struct/class_interface.rb', line 114

def attributes_from(struct)
  extracted_schema = struct.schema.keys.to_h do |key|
    if key.required?
      [key.name, key.type]
    else
      [:"#{key.name}?", key.type]
    end
  end
  attributes(extracted_schema)
end

#call_safe(input) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



261
262
263
264
265
266
267
# File 'lib/dry/struct/class_interface.rb', line 261

def call_safe(input, &)
  if input.is_a?(self)
    input
  else
    new(input, true, &)
  end
end

#call_unsafe(input) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



270
271
272
273
274
275
276
# File 'lib/dry/struct/class_interface.rb', line 270

def call_unsafe(input)
  if input.is_a?(self)
    input
  else
    new(input)
  end
end

#constrained?true

Returns:

  • (true)


335
# File 'lib/dry/struct/class_interface.rb', line 335

def constrained? = true

#constructor(constructor = nil, &block) ⇒ Dry::Struct::Constructor

Parameters:

  • constructor (#call, nil) (defaults to: nil)
  • block (#call, nil)

Returns:



288
289
290
# File 'lib/dry/struct/class_interface.rb', line 288

def constructor(constructor = nil, **, &block)
  Constructor[self, fn: constructor || block]
end

#default?false

Returns:

  • (false)


327
# File 'lib/dry/struct/class_interface.rb', line 327

def default? = false

#failure(*args) ⇒ Dry::Types::Result::Failure

Parameters:

  • args (({Symbol => Object}))

Returns:

  • (Dry::Types::Result::Failure)


320
# File 'lib/dry/struct/class_interface.rb', line 320

def failure(*args) = result(Types::Result::Failure, *args)

#has_attribute?(key) ⇒ Boolean

Checks if this Dry::Struct has the given attribute

Parameters:

  • key (Symbol)

    Attribute name

Returns:

  • (Boolean)


352
# File 'lib/dry/struct/class_interface.rb', line 352

def has_attribute?(key) = schema.key?(key)

#load(attributes) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



279
280
281
282
283
# File 'lib/dry/struct/class_interface.rb', line 279

def load(attributes)
  struct = allocate
  struct.__send__(:initialize, attributes)
  struct
end

#meta(meta = Undefined) ⇒ {Symbol => Object}

Returns:

  • ({Symbol => Object})


362
363
364
365
366
367
368
369
370
371
372
# File 'lib/dry/struct/class_interface.rb', line 362

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) ⇒ Object

Parameters:

  • attributes (Hash{Symbol => Object}, Dry::Struct) (defaults to: default_attributes)

Raises:



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/dry/struct/class_interface.rb', line 239

def new(attributes = default_attributes, safe = false, &) # rubocop:disable Style/OptionalBooleanParameter
  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, &)
    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?false

Returns:

  • (false)


341
# File 'lib/dry/struct/class_interface.rb', line 341

def optional? = false

#primitiveself

Returns:

  • (self)


338
# File 'lib/dry/struct/class_interface.rb', line 338

def primitive = self

#result(klass, *args) ⇒ Object

Parameters:

  • klass (Class)
  • args (({Symbol => Object}))


324
# File 'lib/dry/struct/class_interface.rb', line 324

def result(klass, *args) = klass.new(*args)

#success(*args) ⇒ Dry::Types::Result::Success

Parameters:

  • args (({Symbol => Object}))

Returns:

  • (Dry::Types::Result::Success)


316
# File 'lib/dry/struct/class_interface.rb', line 316

def success(*args) = result(Types::Result::Success, *args)

#to_ast(meta: true) ⇒ Array

Dump to the AST

Returns:

  • (Array)


396
397
398
# File 'lib/dry/struct/class_interface.rb', line 396

def to_ast(meta: true)
  [:struct, [::WeakRef.new(self), schema.to_ast(meta: meta)]]
end

#to_procProc

Returns:

  • (Proc)


344
345
346
# File 'lib/dry/struct/class_interface.rb', line 344

def to_proc
  @to_proc ||= proc { |input| call(input) }
end

#transform_keys(proc = nil, &block) ⇒ Object

Add an arbitrary transformation for input hash keys.

Examples:

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">

Parameters:

  • proc (#call, nil) (defaults to: nil)
  • block (#call, nil)


221
222
223
# File 'lib/dry/struct/class_interface.rb', line 221

def transform_keys(proc = nil, &block)
  schema schema.with_key_transform(proc || block)
end

#transform_types(proc = nil, &block) ⇒ Object

Add an arbitrary transformation for new attribute types.

Examples:

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

  attribute :title, Types::String
end

Book.schema.key(:title).meta # => { struct: :Book }

Parameters:

  • proc (#call, nil) (defaults to: nil)
  • block (#call, nil)


204
205
206
# File 'lib/dry/struct/class_interface.rb', line 204

def transform_types(proc = nil, &block)
  schema schema.with_type_transform(proc || block)
end

#try(input) {|failure| ... } ⇒ Dry::Types::Result

Parameters:

Yield Parameters:

  • failure (Dry::Types::Result::Failure)

Yield Returns:

  • (Dry::Types::Result)

Returns:

  • (Dry::Types::Result)


296
297
298
299
300
301
# File 'lib/dry/struct/class_interface.rb', line 296

def try(input)
  success(self[input])
rescue Error => e
  failure_result = failure(input, e)
  block_given? ? yield(failure_result) : failure_result
end

#try_struct(input) ⇒ Dry::Types::Result

Parameters:

Returns:

  • (Dry::Types::Result)


306
307
308
309
310
311
312
# File 'lib/dry/struct/class_interface.rb', line 306

def try_struct(input)
  if input.is_a?(self)
    input
  else
    yield
  end
end

#|(type) ⇒ Dry::Types::Sum

Build a sum type

Parameters:

  • type (Dry::Types::Type)

Returns:

  • (Dry::Types::Sum)


377
378
379
380
381
382
383
# File 'lib/dry/struct/class_interface.rb', line 377

def |(type)
  if type.is_a?(::Class) && type <= Struct
    Sum.new(self, type)
  else
    super
  end
end