App-karr
view release on metacpan or search on metacpan
.claude/skills/getty-perl-moo/SKILL.md view on Meta::CPAN
## Pattern 7 â Method Modifiers
```perl
before calc => sub { die "x<0" if $_[1] < 0 }; # validate, can't change return
around calc => sub {
my ($orig, $self, $x) = @_;
return $self->$orig($x) + 1; # can change return value
};
after calc => sub { ... }; # side-effects, logging
```
**Rules:** `before`/`after` cannot alter return value; `around` can. Always forward `@_` correctly in `around`. Multiple modifiers from multiple roles stack â order is composition-order-sensitive.
---
## Pattern 8 â Attribute Options Cheatsheet
```perl
has name => (is => 'ro', required => 1);
has tags => (is => 'ro', default => sub { [] }); # ALWAYS coderef for refs
has id => (is => 'lazy'); # built on first access
sub _build_id { "id:" . $_[0]->name }
has status => (
is => 'rw',
trigger => 1, # calls _trigger_status on set
);
sub _trigger_status { die "bad" unless $_[1] =~ /\A(new|ok)\z/ }
has _secret => (is => 'ro', init_arg => 'secret'); # constructor param alias
```
`trigger` fires on `new()` and `set`, NOT on `default`/`builder`. Old value is NOT passed (unlike Moose). `is => 'lazy'` = lazy reader, runs builder on first access.
---
## Pattern 9 â Lifecycle Hooks
```perl
around BUILDARGS => sub {
my ($orig, $class, @args) = @_;
return { source => $args[0] } if @args == 1 && !ref $args[0]; # normalize
$class->$orig(@args);
};
sub FOREIGNBUILDARGS { # maps args to non-Moo parent's constructor
my ($class, $args) = @_;
return ($args->{source});
}
sub BUILD { # runs AFTER all attributes are set; parentâchild order
my ($self, $args) = @_;
die "invalid" unless length $args->{source};
}
# DEMOLISH: childâparent order. Never override DESTROY directly.
```
Do NOT call `SUPER::BUILD` manually â Moo handles the chain.
---
## Pattern 10 â Strict Constructor
```perl
package StrictThing;
use Moo;
use MooX::StrictConstructor;
has size => (is => 'rw');
StrictThing->new(size => 5, colour => 'blue'); # dies: unknown attribute 'colour'
```
**Caveat:** Interacts with `namespace::clean` (can sweep `new`). If needed, protect via `-except => ['new']` or adjust import order per the module docs.
---
## Pattern 11 â Role Conflict Resolution
```perl
# Single with â conflict â dies:
# with 'RoleA', 'RoleB'; # both define foo() â fatal
# Sequential with â first wins:
with 'RoleA'; # foo() from RoleA is now in the class
with 'RoleB'; # foo() already exists â RoleA wins silently
```
"Class wins": if the class defines `foo()` itself, neither role's version is used. For complex conflict strategies: refactor roles to avoid the overlap.
---
## Pattern 12 â Parameterized Roles
```perl
package Counter;
use Moo::Role;
use MooX::Role::Parameterized;
parameter name => (is => 'ro', required => 1);
role {
my ($p, $mop) = @_;
my $n = $p->name;
$mop->has($n => (is => 'rw', default => sub { 0 }));
$mop->method("inc_$n" => sub { $_[0]->$n($_[0]->$n + 1) });
};
package Thing;
use Moo;
use MooX::Role::Parameterized::With;
with Counter => { name => 'hits' };
Thing->new->inc_hits; # generates: hits attribute + inc_hits method
```
Module is marked **experimental**. `role { }` block runs at composition time; `$mop` proxies `has/around/before/after/requires`.
---
## Pattern 13 â Moose Interop
When Moose is loaded before Moo classes are compiled, Moo auto-inflates its metaclasses. This means:
- Moose class can `extends` a Moo class
- Moo class can `with` a Moose role
```perl
BEGIN { require Moose }
package MyMooseClass;
use Moose;
extends 'MyMooClass'; # works if Moose was loaded first
```
For Moose-style syntax in Moo (`isa => 'Str'`, `lazy_build`), use `MooX::late`. Avoid `Any::Moose` â deprecated, points to Moo.
---
## Type Constraints
Moo has no built-in type system â `isa` takes a coderef, and `Type::Tiny` objects
are coderefs, so `Types::Standard` plugs straight in:
```perl
use Types::Standard qw( Str ArrayRef );
has name => ( is => 'ro', isa => Str );
has tags => ( is => 'ro', isa => ArrayRef[Str], default => sub { [] } );
```
Where to type and where not, own type libraries, parameter signatures:
**`getty-perl-typing`**.
---
## Decision Guide
| Situation | Use |
|---|---|
| Shared attributes/methods, stable "is-a" | `extends` |
| Optional/horizontal feature | `Moo::Role` + `with` |
| Same pattern, different config | `MooX::Role::Parameterized` |
| Delegate method set to sub-object | `handles` |
| Array/Hash operations on attribute | `Sub::HandlesVia` |
| Logging/validation/caching wrapper | `before`/`around`/`after` |
| Catch constructor typos | `MooX::StrictConstructor` |
| Cross-project boilerplate | `Import::Into` house-style module |
| Named types | `Type::Tiny` / `Types::Standard` |
| Multiple roles define same method | Sequential `with` or refactor |
| Legacy non-Moo parent | `FOREIGNBUILDARGS` |
| Multiple inheritance | Last resort; use `mro 'c3'` |
---
## Common Pitfalls
- `default => []` â **shared state bug**. Always `default => sub { [] }`.
- `extends 'A'; extends 'B'` â replaces, does NOT add B to A. Use `extends 'A', 'B'`.
- Imports after `use Moo::Role` are **composed into consumers** as methods.
- `namespace::autoclean` < 0.16 inflates Moo classes to Moose unexpectedly.
- `trigger` does NOT receive old value (unlike Moose).
- `Sub::HandlesVia` must be loaded *after* `use Moo`.
- `BUILD` chain is automatic; calling `SUPER::BUILD` manually breaks it.
- Never override `DESTROY`; use `DEMOLISH`.
( run in 3.243 seconds using v1.01-cache-2.11-cpan-b301d465b3d )