Module: ObjectForge

Defined in:
lib/object_forge.rb,
lib/object_forge/forge.rb,
lib/object_forge/molds.rb,
lib/object_forge/version.rb,
lib/object_forge/crucible.rb,
lib/object_forge/sequence.rb,
lib/object_forge/forge_dsl.rb,
lib/object_forge/forgeyard.rb,
lib/object_forge/molds/hash_mold.rb,
lib/object_forge/un_basic_object.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

A small factory library for Ruby objects with minimal assumptions about framework, persistence, or runtime environment.

These are the main classes you should be aware of:

  • Forge is a factory for objects. Usually created through Forgeyard#define or Forge.define using a DSL similar to FactoryBot, Forges can be used standalone, or as a part of a Forgeyard.

  • Forgeyard is a registry of named related Forges. A Forgeyard allows to Forgeyard#define a Forge, and Forgeyard#forge a new object using a defined Forge.

  • Molds are object constructors used by Forges. Several common molds are shipped with ObjectForge, but you will probably find it useful to create your own.

Successful use may also depend on understanding these:

  • ForgeDSL is a block-based DSL inspired by FactoryBot and ROM::Factory. It allows defining arbitrary attributes (possibly using sequences), with support for traits (collections of attributes with non-default values).

  • Sequence is a representation of a sequence of values. They are usually used implicitly through ForgeDSL#sequence, but can be created explicitly to be shared (or used outside of ObjectForge).

  • Crucible is used to resolve attributes.

ObjectForge itself provides a top-level convenience API for working with a singular DEFAULT_YARD when you expect to never need more than one Forgeyard, such as in test suites.

Examples:

Quick example

Frobinator = Struct.new(:frob, :inator, keyword_init: true)

# Forge's name and target class are completely independent.
ObjectForge.define(:frobber, Frobinator) do |f|
  f.frob { "Frob" + inator.call }
  f.inator { -> { "inator" } }

  f.trait :static do |tf|
    tf.frob { "Static" }
  end
end

# These methods are aliases:
ObjectForge.forge(:frobber)
  # => #<struct Frobinator frob="Frobinator", inator=#<Proc:...>>
ObjectForge.build(:frobber, frob: -> { "Frob" + inator }, inator: "orn")
  # => #<struct Frobinator frob="Froborn", inator="orn">
ObjectForge.call(:frobber, :static, inator: "Value")
  # => #<struct Frobinator frob="Static", inator="Value">

A more involved example

require "logger"

# A custom mold is needed for Logger because it has positional and keyword parameters.
logger_mold = ->(forge_target:, attributes:, **) {
  forge_target.new(
    attributes[:file],
    *attributes[:rotation],
    **attributes.slice(:level, :progname, :formatter)
  )
}

# This is a one-off, universal factory, so using a Forgeyard is not needed.
$logger_factory = ObjectForge::Forge.define(Logger) do |f|
  f.mold = logger_mold
  # Default crucible is almost always good enough, but let's use a simpler and faster resolver.
  f.crucible = ->(attributes) { attributes.transform_values { _1.is_a?(Proc) ? _1.call : _1 } }

  f.file { $stderr }

  f.trait :stdout do |tf|
    tf.file { $stdout }
  end

  f.trait :logfiles do |tf|
    require "date"
    tf.file { "log-#{Date.today}.log" }
    tf.rotation { "daily" }
  end
end

class MyClass
  def initialize
    @logger = $logger_factory.build(:stdout, progname: self.class)
  end

  def call
    @logger.info("called!")
  end
end

MyClass.new.call
  # outputs "I, [2026-05-25T13:56:33.533669 #206330]  INFO -- MyClass: called!" to $stdout

Defined Under Namespace

Modules: Molds Classes: CircularAttributeDependencyError, Crucible, DSLError, Error, Forge, ForgeDSL, Forgeyard, ObjectInterfaceError, Sequence, UnBasicObject

Constant Summary collapse

DEFAULT_YARD =
Note:

Default forgeyard is intended to be useful for non-shareable code, like simple application tests and specs. It should not be used in application code, especially in gems.

Default Forgeyard that is used by define and forge.

Since:

  • 0.1.0

Forgeyard.new
VERSION =

Current version

"0.4.0"

Class Method Summary collapse

Class Method Details

.define(name, forge_target) {|dsl| ... } ⇒ Forge

Note:

Default forgeyard is intended to be useful for non-shareable code, like simple application tests and specs. It should not be used in application code, especially in gems.

Define and create a forge in DEFAULT_YARD.

Returns forge.

Parameters:

  • name (Symbol)

    name to register forge under

  • forge_target (Class, Any)

    class or object to forge

Yield Parameters:

Yield Returns:

  • (void)

Returns:

See Also:

Since:

  • 0.1.0



149
150
151
# File 'lib/object_forge.rb', line 149

def self.define(...)
  DEFAULT_YARD.define(...)
end

.forge(name, *traits, **overrides) {|object| ... } ⇒ Any Also known as: build, call

Note:

Default forgeyard is intended to be useful for non-shareable code, like simple application tests and specs. It should not be used in application code, especially in gems.

Build an instance using a forge from DEFAULT_YARD.

Returns forged instance.

Parameters:

  • name (Symbol)

    name of the forge

  • traits (Array<Symbol>)

    traits to apply

  • overrides (Hash{Symbol => Any})

    attribute overrides

Yield Parameters:

  • object (Any)

    forged instance

Yield Returns:

  • (void)

Returns:

  • (Any)

    forged instance

Raises:

  • (ArgumentError)

    if a trait name is unknown

  • (KeyError)

    if forge with the specified name is not registered

See Also:

Since:

  • 0.1.0



168
169
170
# File 'lib/object_forge.rb', line 168

def self.forge(...)
  DEFAULT_YARD.forge(...)
end

.sequence(initial) ⇒ Sequence

Create a sequence, to be used wherever it needs to be.

Parameters:

Returns:

See Also:

Since:

  • 0.1.0



133
134
135
# File 'lib/object_forge.rb', line 133

def self.sequence(...)
  Sequence.new(...)
end