Module: Plumb::Attributes

Included in:
Types::Data
Defined in:
lib/plumb/attributes.rb

Defined Under Namespace

Modules: ClassMethods

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#attributesObject (readonly)

Returns the value of attribute attributes.



135
136
137
# File 'lib/plumb/attributes.rb', line 135

def attributes
  @attributes
end

#errorsObject (readonly)

Returns the value of attribute errors.



135
136
137
# File 'lib/plumb/attributes.rb', line 135

def errors
  @errors
end

Class Method Details

.included(base) ⇒ Object

A module that provides a simple way to define a struct-like class with attributes that are type-checked on initialization.

It supports nested attributes:

Or arrays of nested attributes:

Or use struct classes defined separately:

Arrays and other types support composition and helpers. Ex. #default.

attribute :companies, Types::Array[Company].default([].freeze)

Passing a named struct class AND a block will subclass the struct and extend it with new attributes:

attribute :company, Company do
attribute :address, String
end

The same works with arrays:

attribute :companies, Types::Array[Company] do
attribute :address, String
end

Note that this does NOT work with union'd or piped structs.

attribute :company, Company | Person do

Optional Attributes

Using attribute? allows for optional attributes. If the attribute is not present, it will be set to Undefined.

attribute? :company, Company

Struct Inheritance

Structs can inherit from other structs. This is useful for defining a base struct with common attributes.

class BasePerson
include Plumb::Attributes

attribute :name, String
end

class Person < BasePerson
attribute :age, Integer
end

[] Syntax

The [] syntax can be used to define a struct in a single line. Like Plumb::Types::Hash, suffixing a key with ? makes it optional.

Person = Data[name: String, age?: Integer]
person = Person.new(name: 'Jane')

Examples:

class Person
  include Plumb::Attributes

  attribute :name, Types::String
  attribute :age, Types::Integer[18..]
end

person = Person.new(name: 'Jane', age: 20)
person.valid? # => true
person.errors # => {}
person.name # => 'Jane'
class Person
  include Plumb::Attributes

  attribute :friend do
    attribute :name, String
  end
end

person = Person.new(friend: { name: 'John' })
class Person
  include Plumb::Attributes

  attribute :friends, Types::Array do
    atrribute :name, String
  end
end

person = Person.new(friends: [{ name: 'John' }])
class Company
  include Plumb::Attributes
  attribute :name, String
end

class Person
  include Plumb::Attributes

  # Single nested struct
  attribute :company, Company

  # Array of nested structs
  attribute :companies, Types::Array[Company]
end


111
112
113
114
# File 'lib/plumb/attributes.rb', line 111

def self.included(base)
  base.send(:extend, ClassMethods)
  base.define_singleton_method(:__plumb_struct_class__) { base }
end

.struct_class(node) ⇒ Object

The struct class behind node, or nil. Structs appear in two shapes: a Plumb::Attributes class itself (Types::Data subclasses are Composable classes), or the Function that Composable.wrap builds around a plain include Plumb::Attributes class — the class is the wrapped callable, so it is reached via #fn (a Function's #children are its types). This is the one owner of that representation fact — used by #build_nested and Codec's rewriter.

Keyed on Function#wraps_callable? rather than on the types the node declares: boundary absorption can move a neighbouring type into a wrapper's slot (see Function#absorb_input), and it wraps the same class either way.



127
128
129
130
131
132
133
# File 'lib/plumb/attributes.rb', line 127

def self.struct_class(node)
  return node if node.is_a?(::Class) && node <= Attributes
  return nil unless node.is_a?(Plumb::Function) && node.wraps_callable?

  callable = node.fn
  callable.is_a?(::Class) && callable <= Attributes ? callable : nil
end

Instance Method Details

#==(other) ⇒ Object



142
143
144
# File 'lib/plumb/attributes.rb', line 142

def ==(other)
  other.is_a?(self.class) && other.attributes == attributes
end

#deconstructObject



180
# File 'lib/plumb/attributes.rb', line 180

def deconstruct(...) = to_h.values.deconstruct(...)

#deconstruct_keysObject



181
# File 'lib/plumb/attributes.rb', line 181

def deconstruct_keys(...) = to_h.deconstruct_keys(...)

#initialize(attrs = {}) ⇒ Object



137
138
139
140
# File 'lib/plumb/attributes.rb', line 137

def initialize(attrs = {})
  assign_attributes(self.class._pipeline.parse(attrs))
  freeze
end

#inspectObject



155
156
157
158
159
# File 'lib/plumb/attributes.rb', line 155

def inspect
  %(#<#{self.class}:#{object_id} [#{valid? ? 'valid' : 'invalid'}] #{attributes.map do |k, v|
    [k, v.inspect].join(':')
  end.join(' ')}>)
end

#to_hHash

Returns:

  • (Hash)


162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/plumb/attributes.rb', line 162

def to_h
  self.class._schema._schema.keys.each.with_object({}) do |key, memo|
    key = key.to_sym
    value = attributes[key]
    val = case value
          when ::Array
            value.map { |v| v.respond_to?(:to_h) ? v.to_h : v }
          when ::NilClass
            nil
          else
            value.respond_to?(:to_h) ? value.to_h : value
          end
    memo[key] = val
  end
end

#to_hashObject



178
# File 'lib/plumb/attributes.rb', line 178

def to_hash = to_h

#valid?Boolean

Returns:

  • (Boolean)


147
# File 'lib/plumb/attributes.rb', line 147

def valid? = !errors || errors.none?

#with(attrs = BLANK_HASH) ⇒ Plumb::Attributes

Parameters:

  • attrs (Hash) (defaults to: BLANK_HASH)

Returns:



151
152
153
# File 'lib/plumb/attributes.rb', line 151

def with(attrs = BLANK_HASH)
  self.class.new(attributes.merge(attrs))
end