Module: ObjectForge::Molds

Defined in:
lib/object_forge/molds.rb,
lib/object_forge/molds/hash_mold.rb,
lib/object_forge/molds/struct_mold.rb,
lib/object_forge/molds/wrapped_mold.rb,
lib/object_forge/molds/keywords_mold.rb,
lib/object_forge/molds/single_argument_mold.rb

Overview

This module provides a collection of predefined molds to be used in common cases.

Mold is an object that knows how to take a hash of attributes and create an object from them. Molds are #callable objects responsible for actually building objects produced by factories (or doing other, interesting things with them (truly, only the code review is the limit!)). They are supposed to be immutable, shareable, and persistent: initialize once, use for the whole runtime.

A simple mold can easily be just a Proc. All molds must have the following #call signature: call(forged:, attributes:, **). The extra keywords are ignored for possibility of future extensions.

Examples:

A very basic FactoryBot replacement

creator = ->(forged:, attributes:, **) do
  instance = forged.new
  attributes.each_pair { instance.public_send(:"#{_1}=", _2) }
  instance.save!
end
creator.call(forged: User, attributes: { name: "John", age: 30 })
  # => <User name="John" age=30>

Using a mold to serialize collection of objects (contrivedly)

dumpy = ->(forged:, attributes:, **) do
  Enumerator.new(attributes.size) do |y|
    attributes.each_pair { y << forged.dump(_1 => _2) }
  end
end
dumpy.call(forged: JSON, attributes: {a:1, b:2}).to_a
  # => ["{\"a\":1}", "{\"b\":2}"]
dumpy.call(forged: YAML, attributes: {a:1, b:2}).to_a
  # => ["---\n:a: 1\n", "---\n:b: 2\n"]

Abstract factory pattern (kind of)

class FurnitureFactory
  def call(forged:, attributes:, **)
    concrete_factory = concrete_factory(forged)
    attributes[:pieces].map do |piece|
      concrete_factory.public_send(piece, attributes.dig(:color, piece))
    end
  end
  private def concrete_factory(style)
    case style
    when :hitech
      HiTechFactory.new
    when :retro
      RetroFactory.new
    end
  end
end
FurnitureFactory.new.call(forged: :hitech, attributes: {
  pieces: [:chair, :table], color: { chair: :black, table: :white }
})
  # => [<#HiTech::Chair color=:black>, <#HiTech::Table color=:white>]

Abusing molds

printer = ->(forged:, attributes:, **) { PP.pp(attributes, forged) }
printer.call(forged: $stderr, attributes: {a:1, b:2})
  # outputs "{:a=>1, :b=>2}" to $stderr

Since:

  • 0.2.0

Defined Under Namespace

Classes: HashMold, KeywordsMold, SingleArgumentMold, StructMold, WrappedMold

Class Method Summary collapse

Class Method Details

.mold_for(forged) ⇒ #call

Get maybe appropriate mold for the given forged class or object.

Currently provides specific recognition for:

Other objects just get SingleArgumentMold.

Parameters:

  • forged (Class, Any)

Returns:

  • (#call)

    an instance of a mold

Since:

  • 0.3.0



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/object_forge/molds.rb', line 78

def self.mold_for(forged)
  if ::Class === forged
    if forged < ::Struct
      StructMold.new
    elsif defined?(::Data) && forged < ::Data
      KeywordsMold.new
    elsif forged <= ::Hash
      HashMold.new
    else
      SingleArgumentMold.new
    end
  else
    SingleArgumentMold.new
  end
end

.wrap_mold(mold) ⇒ #call?

Wrap mold if needed.

If mold is nil or a callable object, returns it. If it is a Class with #call, wraps it in WrappedMold. Otherwise, raises an error.

Parameters:

  • mold (Class, #call, nil)

Returns:

Raises:

  • (DSLError)

    if mold does not respond to or implement #call

Since:

  • 0.3.0

  • 0.3.0



109
110
111
112
113
114
115
116
117
# File 'lib/object_forge/molds.rb', line 109

def self.wrap_mold(mold)
  if mold.nil? || mold.respond_to?(:call)
    mold # : ObjectForge::mold?
  elsif ::Class === mold && mold.public_method_defined?(:call)
    WrappedMold.new(mold)
  else
    raise MoldError, "mold must respond to or implement #call"
  end
end