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)


349
350
351
# File 'lib/dry/struct/class_interface.rb', line 349

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

#abstractObject

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



415
416
417
# File 'lib/dry/struct/class_interface.rb', line 415

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:



95
96
97
# File 'lib/dry/struct/class_interface.rb', line 95

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

#attribute?(*args, &block) ⇒ 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:



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/dry/struct/class_interface.rb', line 148

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_namesArray<Symbol>

Gets the list of attribute names

Returns:

  • (Array<Symbol>)


385
386
387
# File 'lib/dry/struct/class_interface.rb', line 385

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:



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/dry/struct/class_interface.rb', line 182

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



123
124
125
126
127
128
129
130
131
132
# File 'lib/dry/struct/class_interface.rb', line 123

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



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

def call_safe(input, &block)
  if input.is_a?(self)
    input
  else
    new(input, true, &block)
  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.



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

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

#constrained?true

Returns:

  • (true)


355
356
357
# File 'lib/dry/struct/class_interface.rb', line 355

def constrained?
  true
end

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

Parameters:

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

Returns:



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

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

#default?false

Returns:

  • (false)


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

def default?
  false
end

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

Parameters:

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

Returns:

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


332
333
334
# File 'lib/dry/struct/class_interface.rb', line 332

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

#has_attribute?(key) ⇒ Boolean

Checks if this Dry::Struct has the given attribute

Parameters:

  • key (Symbol)

    Attribute name

Returns:

  • (Boolean)


378
379
380
# File 'lib/dry/struct/class_interface.rb', line 378

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

#inherited(klass) ⇒ Object

Parameters:

  • klass (Class)


15
16
17
18
19
20
21
# File 'lib/dry/struct/class_interface.rb', line 15

def inherited(klass)
  super

  unless klass.name.eql?("Dry::Struct::Value")
    klass.extend(Core::DescendantsTracker)
  end
end

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



289
290
291
292
293
# File 'lib/dry/struct/class_interface.rb', line 289

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

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

Returns:

  • ({Symbol => Object})


390
391
392
393
394
395
396
397
398
399
400
# File 'lib/dry/struct/class_interface.rb', line 390

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, &block) ⇒ Object

Parameters:

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

Raises:



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/dry/struct/class_interface.rb', line 249

def new(attributes = default_attributes, safe = false, &block) # 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, &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?false

Returns:

  • (false)


365
366
367
# File 'lib/dry/struct/class_interface.rb', line 365

def optional?
  false
end

#primitiveself

Returns:

  • (self)


360
361
362
# File 'lib/dry/struct/class_interface.rb', line 360

def primitive
  self
end

#result(klass, *args) ⇒ Object

Parameters:

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


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

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

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

Parameters:

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

Returns:

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


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

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

#to_ast(meta: true) ⇒ Array

Dump to the AST

Returns:

  • (Array)


424
425
426
# File 'lib/dry/struct/class_interface.rb', line 424

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

#to_procProc

Returns:

  • (Proc)


370
371
372
# File 'lib/dry/struct/class_interface.rb', line 370

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)


231
232
233
# File 'lib/dry/struct/class_interface.rb', line 231

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)


214
215
216
# File 'lib/dry/struct/class_interface.rb', line 214

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)


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

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)


316
317
318
319
320
321
322
# File 'lib/dry/struct/class_interface.rb', line 316

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)


405
406
407
408
409
410
411
# File 'lib/dry/struct/class_interface.rb', line 405

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