Skip to content

Getting started

This page walks you through defining your first factory, reading it back from the registry, and resetting state between tests.

Install

1
zef install --/test ORM::Factory

Defining a factory

define opens a definition block. Inside, the topic ($_) is bound to a DefinitionBuilder, so DSL calls use the leading-dot shorthand:

1
2
3
4
5
6
7
8
9
use ORM::Factory;

define {
  .factory: 'user', {
    .fname: 'Greg';
    .lname: 'Donald';
    .email: { 'user@example.com' };
  };
};

A static attribute takes any non-Callable value: .fname: 'Greg'. A dynamic attribute takes a single Callable and is evaluated lazily: .email: { 'user@example.com' }.

Class inference and overrides

ORM::Factory camelizes the factory name ('user'User, 'super-admin'SuperAdmin, 'team_lead'TeamLead) and looks the result up in GLOBAL. A top-level class User { … } in any compunit will be found.

When the camelized name doesn't match your class — or the class lives in a namespace — pass it explicitly:

1
2
3
4
5
define {
  .factory: 'super-admin', :class(MyApp::Admin), {
    .role: 'admin';
  };
};

To turn off name-based inference entirely (e.g. you only want explicit classes):

1
ORM::Factory.set-allow-class-lookup(False);

lookup-class on a FactoryDefinition raises X::ORM::Factory::UnknownClass if the class can't be resolved and no override was supplied.

behave EVALs each spec file. our class Foo { … } declared during EVAL does not land in GLOBAL the way a normal compunit does, so name inference may miss it. In behave specs, either bind explicitly (BEGIN GLOBAL::<Foo> := Foo;) or pass :class(Foo) per factory.

Aliases

A factory can be registered under additional names:

1
2
3
4
5
6
7
8
define {
  .factory: 'user', :aliases<author commenter>, {
    .fname: 'Greg';
  };
};

say ORM::Factory.factory-by-name('author').name;   # 'user'
say ORM::Factory.aliases<commenter>;               # 'user'

Aliases collide with both factory names and other aliases — the duplicate-registration guard raises X::ORM::Factory::DuplicateAlias if you try to claim a name that's already in use.

Reading the registry

Definitions land in a process-global registry keyed by name:

1
2
3
4
5
6
7
my %factories = ORM::Factory.factories;        # all definitions
my $user      = ORM::Factory.factory-by-name('user');

say $user.name;                                 # 'user'
say $user.attributes.elems;                     # 3
say $user.attributes[0].name;                   # 'fname'
say $user.attributes[0].value;                  # 'Greg'

Looking up an unregistered name raises X::ORM::Factory::UnknownFactory.

Variants

A variant is an alternate set of overrides scoped to one factory. Define one with .variant, apply it inside the same factory body with a bare leading-dot call:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
define {
  .factory: 'user', {
    .fname: 'Greg';

    .variant: 'admin', {
      .role: 'admin';
    };

    .admin;
  };
};

The .admin call resolves against the registered variant name first. If the name isn't a registered variant, it falls back to capturing an attribute named admin.

Names that collide with the DSL

DSL method names — factory, variant, transient, association, add-attribute, before, after, initialize-with, to-create, modify, skip-create — cannot be captured by the FALLBACK shortcut. Use the explicit add-attribute escape hatch:

1
2
.add-attribute: 'factory', 'acme';   # attribute literally named "factory"
.add-attribute: 'block',   -> { 42 };  # a Callable literal as a static value

add-attribute skips the FALLBACK indirection and stores exactly what you pass: a Callable arg is treated as dynamic, anything else as static, with no chance of colliding with a method name on the builder.

Resetting between tests

ORM::Factory.reload clears the registry. Run it before each test to keep suites from leaking definitions:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use BDD::Behave;
use ORM::Factory;

describe 'my feature', {
  before-each {
    ORM::Factory.reload;
  }

  # …
};

For prove6, just call ORM::Factory.reload at the top of each test block. See Tests for the full convention.

Diagnostics

The library raises typed exceptions under X::ORM::Factory::*:

Exception Trigger
X::ORM::Factory::UnknownFactory factory-by-name on an unregistered name.
X::ORM::Factory::DuplicateFactory Two .factory 'x', … calls with the same name.
X::ORM::Factory::DuplicateVariant Two .variant 'x', … calls in the same factory.
X::ORM::Factory::UsageError Argument shape the DSL can't make sense of (e.g. a variant applied with arguments).