bif
bif

Reputation: 45

Passing dynamic parameters to a parameterized Moose role

I want to consume the MooseX::Storage parameterized role, but I also want to allow parameters to be supplied through the constructor. Here is the "static" version which works as expected:

package Note;
use Moose;
use namespace::autoclean;
use MooseX::StrictConstructor;
use MooseX::Storage;

has title   => (is => 'ro', isa => 'Str');
has body    => (is => 'rw', isa => 'Str');

with Storage(format => 'JSON', io => 'File');

__PACKAGE__->meta->make_immutable;

package main;
use warnings;
use strict;
use feature 'say';

## make a new note and store it
my $note = Note->new(title => 'Note 1');
$note->body("Here is the note");
$note->store($note->title . ".json");

## load the stored note
undef $note;
$note = Note->load("Note 1.json");
say $note->body;

But I want to make the Storage options dynamic. I thought of using a trait as described in MooseX::Traits, something like this:

package Note;
use Moose;
use namespace::autoclean;
use MooseX::StrictConstructor;

has title   => (is => 'ro', isa => 'Str');
has body    => (is => 'rw', isa => 'Str');

with 'MooseX::Traits';

__PACKAGE__->meta->make_immutable;

package main;
use warnings;
use strict;
use feature 'say';
use MooseX::Storage;

## make a new note; set the storage format dynamically
my $note = Note->with_traits(Storage => {format => 'JSON', io => 'File'})->new(title => 'Note 1');
$note->body("Here is the note");
$note->store($note->title . ".json");

## load the stored note
undef $note;
$note = Note->load("Note 1.json");
say $note->body;

But I get an error:

Can't locate Storage.pm in @INC

I've tried to use MooseX::Storage in both classes, etc. I've looked at meta->apply and apply_all_roles, but neither of those seems to play well with parameterized roles.

There's something I'm not grasping about parameterized roles. I'm wondering if I should wrap the parameterized role in a non-parameterized role (e.g., StorageWithJSONFile) and use that a trait. Would that be a good approach?

What's an appropriate way to set the parameters of this role during object construction? Or is there a better way to give an object a parameterized role?

Edit: I've learned that the Storage() function that MooseX::Storage exports returns a list of roles when it is invoked. This cleared up a few thing for me, so I try this:

package Note;
use Moose;
use namespace::autoclean;
use MooseX::StrictConstructor;
use MooseX::Storage;
use Moose::Util 'apply_all_roles';

has title   => (is => 'ro', isa => 'Str');
has body    => (is => 'rw', isa => 'Str');
has storage => (is => 'ro', isa => 'HashRef[Str]');

sub BUILD {
    my ($self) = @_;
    apply_all_roles($self, Storage(%{$self->storage}));
}

__PACKAGE__->meta->make_immutable;

package main;
use warnings;
use strict;
use feature 'say';

## make a new note and store it: this works!
my $note = Note->new(title => 'Note 1', storage => {format => 'JSON', io => 'File'});
$note->body("Here is the note");
$note->store($note->title . ".json");

## load the stored note: this does not work: Can't locate object method "load" via package "Note" at test4.pl line 32.
undef $note;
$note = Note->load("Note 1.json");  ## where are the roles?
say $note->body;

The contents of Note 1.json:

{"__CLASS__":"Moose::Meta::Class::__ANON__::SERIAL::2","body":"Here is the note","storage":{"format":"JSON","io":"File"},"title":"Note 1"}

I seem to have a chicken-egg problem: load() applies to the class after BUILD runs; BUILD won't run until load() is called.

I think what I need is a new class with the role composed in. I may be overthinking this.

Upvotes: 1

Views: 182

Answers (1)

bif
bif

Reputation: 45

You can create a new role (JSONFiler) that composes the Storage behaviors, then apply that role dynamically using with_traits:

package Note;
use Moose;
use namespace::autoclean;
use MooseX::StrictConstructor;

has title   => (is => 'ro', isa => 'Str');
has body    => (is => 'rw', isa => 'Str');

with 'MooseX::Traits';

__PACKAGE__->meta->make_immutable;

package JSONFiler;
use Moose::Role;
use MooseX::Storage;

with Storage(format => 'JSON', io => 'File');

package main;
use warnings;
use strict;
use feature 'say';

## create a new Note class with the Storage traits
my $note_class = Note->with_traits('JSONFiler');

## make a new note and store it
my $note = $note_class->new(title => 'Note 1');
$note->body("Here is the note");
$note->store($note->title . ".json");

## load the stored note
undef $note;
$note = $note_class->load("Note 1.json");
say $note->body;

Upvotes: 1

Related Questions