Module: Plumb::Attributes::ClassMethods

Defined in:
lib/plumb/attributes.rb

Instance Method Summary collapse

Instance Method Details

#[](type_specs) ⇒ Object

Person = Data[:name => String, :age => Integer, title?: String]



243
244
245
246
247
248
249
250
# File 'lib/plumb/attributes.rb', line 243

def [](type_specs)
  type_specs = type_specs._schema if type_specs.is_a?(Plumb::HashClass)
  klass = Class.new(self)
  type_specs.each do |key, type|
    klass.attribute(key, type)
  end
  klass
end

#__plumb_define_attribute_reader_method__(name) ⇒ Object



295
296
297
# File 'lib/plumb/attributes.rb', line 295

def __plumb_define_attribute_reader_method__(name)
  define_method(name) { @attributes[name] }
end

#__plumb_define_attribute_writer_method__(name) ⇒ Object



299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/plumb/attributes.rb', line 299

def __plumb_define_attribute_writer_method__(name)
  define_method("#{name}=") do |value|
    type = self.class._schema.at_key(name)
    result = type.resolve(value)
    @attributes[name] = result.value
    if result.valid?
      @errors.delete(name)
    else
      @errors.merge!(name => result.errors)
    end
    result.value
  end
end

#__set_nested_class__(name, klass) ⇒ Object



336
337
338
339
# File 'lib/plumb/attributes.rb', line 336

def __set_nested_class__(name, klass)
  name = name.to_s.split('_').map(&:capitalize).join.sub(/s$/, '')
  const_set(name, klass) unless const_defined?(name)
end

#_pipelineObject



182
183
184
# File 'lib/plumb/attributes.rb', line 182

def _pipeline
  @_pipeline || Plumb::Types::Any
end

#_schemaObject



219
220
221
# File 'lib/plumb/attributes.rb', line 219

def _schema
  @_schema ||= HashClass.new
end

#_set_pipeline(pl) ⇒ Object



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

def _set_pipeline(pl)
  @_pipeline = pl
end

#attribute(name, type = Types::Any, writer: false, &block) ⇒ Object

attribute(:friend) { attribute(:name, String) } attribute(:friend, MyStruct) { attribute(:name, String) } attribute(:name, String) attribute(:friends, Types::Array) { attribute(:name, String) } attribute(:friends, Types::Array) # same as Types::Array attribute(:friends, []) # same as Types::Array attribute(:friends, Types::Array) attribute(:friends, [Person])



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# File 'lib/plumb/attributes.rb', line 264

def attribute(name, type = Types::Any, writer: false, &block)
  # Key accepts String or Symbol, with optional '?' suffix for optional keys
  # for Data structs, we always convert to Symbol keys
  key = Key.wrap(name, symbolize: true)
  name = key.to_sym
  type = Composable.wrap(type)

  if block_given? # :foo, Array[Data] or :foo, Struct
    type = __plumb_struct_class__ if type == Types::Any
    type = Plumb.decorate(type) do |node|
      if node.is_a?(Plumb::ArrayClass)
        child = node.children.first
        child = __plumb_struct_class__ if child == Types::Any
        Types::Array[build_nested(name, child, &block)]
      elsif node.is_a?(Plumb::Step)
        build_nested(name, node, &block)
      elsif node.is_a?(Class) && node <= Plumb::Attributes
        build_nested(name, node, &block)
      else
        node
      end
    end
  end

  @_schema = _schema + { key => type }
  __plumb_define_attribute_reader_method__(name)
  return name unless writer

  __plumb_define_attribute_writer_method__(name)
end

#attribute?(name, *args, &block) ⇒ Boolean

Returns:

  • (Boolean)


313
314
315
# File 'lib/plumb/attributes.rb', line 313

def attribute?(name, *args, &block)
  attribute(Key.new(name, optional: true), *args, &block)
end

#build_nested(name, node, &block) ⇒ Object



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/plumb/attributes.rb', line 317

def build_nested(name, node, &block)
  if node.is_a?(Class) && node <= Plumb::Attributes
    sub = Class.new(node)
    sub.instance_exec(&block)
    __set_nested_class__(name, sub)
    return Composable.wrap(sub)
  end

  return node unless node.is_a?(Plumb::Step)

  child = node.children.first
  return node unless child <= Plumb::Attributes

  sub = Class.new(child)
  sub.instance_exec(&block)
  __set_nested_class__(name, sub)
  Composable.wrap(sub)
end

#call(result) ⇒ Plumb::Result::Valid, Plumb::Result::Invalid

The Plumb::Step interface



234
235
236
237
238
239
240
# File 'lib/plumb/attributes.rb', line 234

def call(result)
  return result if result.value.is_a?(self)
  return result.invalid(errors: ['Must be a Hash of attributes']) unless result.value.respond_to?(:to_h)

  instance = new(result.value.to_h)
  instance.valid? ? result.valid(instance) : result.invalid(instance, errors: instance.errors.to_h)
end

#inherited(subclass) ⇒ Object



223
224
225
226
227
228
229
# File 'lib/plumb/attributes.rb', line 223

def inherited(subclass)
  subclass._set_pipeline _pipeline
  _schema._schema.each do |key, type|
    subclass.attribute(key, type)
  end
  super
end

#node_nameObject

node name for visitors



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

def node_name = :data

#step(st = nil, &block) ⇒ Class

Add a step to the processing pipeline that runs before attribute validation. This allows you to transform or validate the input data before it’s assigned to attributes.

Examples:

Transform input before validation

class Person
  include Plumb::Attributes

  step { |result| result.valid(result.value.transform_keys(&:to_sym)) }
  attribute :name, Types::String
end

Add custom validation

class Person
  include Plumb::Attributes

  step do |result|
    if result.value[:name].nil?
      result.invalid(errors: 'Name is required')
    else
      result
    end
  end
  attribute :name, Types::String
end

Parameters:

  • st (Plumb::Step, nil) (defaults to: nil)

    A step object to add to the pipeline

  • block (Proc, nil)

    A block to use as a step (if st is nil)

Returns:

  • (Class)

    Returns self for method chaining



214
215
216
217
# File 'lib/plumb/attributes.rb', line 214

def step(st = nil, &block)
  @_pipeline = _pipeline >> (st || block)
  self
end