Skip to content

Callbacks

Callbacks hook into the build lifecycle so a factory can mutate its instance or trigger side-effects at well-defined points. The built-in events are after build, before create, after create, and after stub; custom names can be invoked by hand from other callbacks or custom build strategies.

1
2
3
4
5
6
7
8
define {
  .factory: 'user', {
    .fname: 'Greg';
    .after: 'build', -> $u, $eval { $u.fname = $u.fname.uc };
  };
};

ORM::Factory.build('user').fname;   # 'GREG'

Built-in events

Event Fired during Phase
after build build, create after
before create create (before persistence) before
after create create (after persistence) after
after stub build-stubbed after

attributes-for runs no callbacks — it returns the attribute hash without ever instantiating the model.

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

    .before: 'create', -> $u, $eval { $u.fname = "[$u.fname()]" };
    .after:  'create', -> $u, $eval { say "persisted user: $u.fname()" };
    .after:  'stub',   -> $u, $eval { $u.fname = '<stubbed>'         };
  };
};

Callback signature

The block can take zero, one, or two arguments:

  • 0 — for fire-and-forget side effects
  • 1 — receives the instance under construction
  • 2 — receives the instance and the evaluator, so the callback can read any persisted or transient attribute (including overrides)
1
2
3
.after: 'build', -> $u, $eval {
  $u.greeting = "hello, {$eval.salute}";   # transient via evaluator
};

Multiple callbacks for one event

A factory may declare several callbacks for the same event. They fire in declaration order:

1
2
3
4
5
6
.factory: 'user', {
  .after: 'build', -> $u, $e { $u.events.push: 'one'   };
  .after: 'build', -> $u, $e { $u.events.push: 'two'   };
  .after: 'build', -> $u, $e { $u.events.push: 'three' };
};
# events: ['one', 'two', 'three']

Inheritance

Parent callbacks run before child callbacks. Each factory contributes its own callbacks at its position in the chain:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
define {
  .factory: 'user', {
    .after: 'build', -> $u, $e { $u.events.push: 'parent' };

    .factory: 'admin', {
      .role: 'admin';
      .after: 'build', -> $u, $e { $u.events.push: 'child' };
    };
  };
};

ORM::Factory.build('admin').events;   # ['parent', 'child']

Variants

A variant can register its own callbacks. When the variant is applied, its callbacks are appended after the factory's own:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
define {
  .factory: 'user', {
    .after: 'build', -> $u, $e { $u.events.push: 'base' };

    .variant: 'noisy', {
      .after: 'build', -> $u, $e { $u.events.push: 'noisy' };
    };
  };
};

ORM::Factory.build('user').events;          # ['base']
ORM::Factory.build('user', 'noisy').events; # ['base', 'noisy']

Global callbacks

A before / after at the top of a define block — without a surrounding factory — applies to every factory. Global callbacks fire before any factory-specific callbacks for the same event:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
define {
  .after: 'build', -> $i, $e { $i.events.push: 'global' };

  .factory: 'user', {
    .fname: 'Greg';
    .after: 'build', -> $u, $e { $u.events.push: 'user' };
  };
};

ORM::Factory.build('user').events;   # ['global', 'user']

ORM::Factory.global-callbacks returns the list of registered globals. ORM::Factory.reload clears them along with everything else.

Custom callbacks

callback 'name' registers a callback under an arbitrary event name. Custom callbacks do not fire on any built-in event — they only fire when something asks for them explicitly via evaluator.run-callbacks('name'). The natural caller is a custom build strategy, but built-in callbacks can also chain to a custom name:

1
2
3
4
5
6
7
8
9
define {
  .factory: 'user', {
    .fname: 'Greg';
    .callback: 'shouted', -> $u, $e { $u.fname = $u.fname.uc };
    .after: 'build', -> $u, $e { $e.run-callbacks('shouted') };
  };
};

ORM::Factory.build('user').fname;   # 'GREG'

has_many-style collections

A transient attribute plus an after build callback gives a count knob and a side-effect that builds the children:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
define {
  .factory: 'user', { .fname: 'Greg' };

  .factory: 'post', {
    .title: 'Hello';

    .transient: {
      .comments-count: 0;
    };

    .after: 'build', -> $post, $eval {
      for ^$eval.comments-count -> $i {
        $post.comments.push: ORM::Factory.build('comment', :post($post));
      }
    };
  };
};

ORM::Factory.build('post', :comments-count(3)).comments.elems;   # 3

The transient comments-count is excluded from attributes-for and never reaches the model constructor — only the callback sees it via the evaluator.

Reset between tests

ORM::Factory.reload clears every factory, alias, sequence, global variant, and global callback. Per-test reset is identical to the rest of the library:

1
2
3
4
before-each {
  ORM::Factory.reload;
  ORM::Factory.reset-persistence;
};