Reputation: 45
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
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