2017 twenty-four merry days of Perl Feed

Making a constructor argument list, checking it twice.

MooseX::StrictConstructor - 2017-12-17

"Wise Old Elf, Wise Old Elf", Licorice Stripysparkles called out, "I think your code is broken."

"Broken, you say, Licorice? That's odd, it passes all its tests."

"Yeah, Wise Old Elf, I know that. But look, when I try setting the lead reindeer to Robbie, Rudolph's son, when I'm constructing the Sleigh, it just doesn't work! It keeps using the default from the lazy builder, giving me back Rudolph each time."

"Nonsense, young one. There's a test for that. Are you sure that _build_lead_reindeer is being erroneously called?"

"Yep, I put some debug code in it to print out to STDERR, and by jove, it's being called each and every time, if I pass a value for lead_reindeer to the constructor or not."

"Most odd. Show me exactly what you're doing."

Stripysparkles opened up his editor and showed the Wise Old Elf his code:

# create the test sleigh
my $sleigh = SantasSleigh->new(
    lead_raindeer => Robbie->new,
);

say ref $sleigh->lead_reindeer;

"Licorice, my boy, that's not how you spell reindeer. R-E-I-N not R-A-I-N!"

"Oops, sorry Wise Old Elf, My Bad!"

"No, Licorice, I don't really think it is your bad. If it's anyone's bad it's mine."

Not Strict Enough

By default Moose lets you pass any argument you want to the constructor method. If it doesn't have a corresponding attribute for that parameter Moose happily ignores the parameter.

This can, as Licorice Stripysparkles had discovered, often lead to some hard to diagnose bugs. The solution is to throw an exception if someone passes you a duff argument.

Now, we could write some complex code in a BUILDARGS method that checked the arguments. But it would be fragile, and we would have to keep updating it when our class (or superclasses, or roles) added new attributes. Why don't we instead take advantage of Moose to do some introspection on the object and work this out dynamically for us? Better yet, we could use a reusable class trait (a role for the class's metaclass) that does all of this for us without having to actually write any code for our particular class.

This is exactly what the CPAN module MooseX::StrictConstructor does. Loading this class in your Moose class monkeys around with the metaclass for the class you're writing in order to add strict constructor checks. So once the Wise Old Elf changed the code to include one extra use statement...

package SantasSleigh;
use Moose;
use MooseX::StrictConstructor;

It meant that when Licorice ran his broken code he got an actual helpful error message:

    Found unknown attribute(s) init_arg passed to the constructor: lead_raindeer
Gravatar Image This article contributed by: Mark Fowler <mark@twoshortplanks.com>