I found myself instantiating the same objects in numerous tests, so I'm trying to replace this constant setup by using Bread::Board. Most of the time I will want the dependencies to have the same configuration. But occasionally I may want to have an instance created with parameters that are not the default. I'd also like to be able to change this after I've created an instance of the container. e.g. I'd normally want
my $c = Test::Container->new;
my $bar = $c->resolve( service => 'bar' ); # where bar depends on foo
but sometimes what I really need is something like
my $bar = $c->resolve( service => 'bar', {
services => {
foo => { init => 'arg' }
}
}
so that service foo is initialized differently while creating the instance of bar.
This was provided to me by Jesse Luehrs (Doy) on #moose and appears that it'll do what I want.
#!/usr/bin/env perl
use v5.14;
use strict;
use warnings;
package Container {
use Moose;
use Bread::Board;
extends 'Bread::Board::Container';
has '+name' => (default => 'Container');
sub BUILD {
my $self = shift;
container $self => as {
service foo => (
block => sub {
my $s = shift;
$s->param('foo_val');
},
parameters => {
foo_val => { isa => 'Str' },
},
);
service bar => (
block => sub {
my $s = shift;
$s->param('foo')->inflate(foo_val => $s->param('foo_val')) . 'BAR';
},
dependencies => ['foo'],
parameters => {
foo_val => { isa => 'Str', default => 'FOO' },
},
);
};
}
}
my $c = Container->new;
warn $c->resolve(service => 'bar');
warn $c->resolve(service => 'bar', parameters => { foo_val => 'baz' });
Related
Given the following:
Moo 2.001001;
Bread::Board 0.34;
a Bread::Board container
base class Foo with property str and around modifier for str
subclass Bar extending Foo
The issue:
[GOOD] when instantiating object from plain Bar, Foo::str modifier is executed;
[BAD] when instantiating object from container service for Bar, Foo::str modifier is not executed;
[WORSE] after [BAD] happens, instantiating from plain Bar no longer works either;
Code example:
#!/usr/qlc/apps/common/perl_5.18.2/bin/perl -w
package Foo;
use Moo;
has str => ( is => 'rw', default => '' );
sub BUILD {
my ($self, $args) = #_;
$self->str($self->str);
}
around str => sub {
my ($orig, $self, $val) = #_;
return $self->$orig unless defined $val;
$self->$orig('prefix_'.$val);
};
# end of Foo
package Bar;
use Moo;
extends 'Foo';
# end of Bar
package main;
use 5.010;
use strictures 2;
use Bread::Board;
my $c = container 'MyApp' => as {
service 'foo' => ( class => 'Foo', parameters => { str => { optional => 1 } } );
service 'bar' => ( class => 'Bar', parameters => { str => { optional => 1 } } );
};
my $foo_plain = Foo->new({ str => 'foo_plain' });
say "foo_plain = ".$foo_plain->str;
my $foo_bb = $c->resolve( service => 'foo', parameters => { str => 'foo_bb' } );
say "foo_bb = ".$foo_bb->str;
$foo_bb->str('foo_bb_setter');
say "foo_bb_setter = ".$foo_bb->str;
my $foo_plain_after_bb = Foo->new({ str => 'foo_plain_after_bb' });
say "foo_plain_after_bb = ".$foo_plain_after_bb->str;
my $bar_plain = Bar->new({ str => 'bar_plain' });
say "bar_plain = ".$bar_plain->str;
my $bar_bb = $c->resolve( service => 'bar', parameters => { str => 'bar_bb' } );
say "bar_bb = ".$bar_bb->str;
$bar_bb->str('bar_bb_setter');
say "bar_bb_setter = ".$bar_bb->str;
my $bar_plain_after_bb = Bar->new({ str => 'bar_plain_after_bb' });
say "bar_plain_after_bb = ".$bar_plain_after_bb->str;
die;
Output:
foo_plain = prefix_foo_plain
foo_bb = prefix_foo_bb
foo_bb_setter = prefix_foo_bb_setter
foo_plain_after_bb = prefix_foo_plain_after_bb
bar_plain = prefix_bar_plain
bar_bb = bar_bb
bar_bb_setter = bar_bb_setter
bar_plain_after_bb = bar_plain_after_bb
Please note outputs bar_bb, bar_bb_setter and bar_plain_after_bb do not contain prefix_ string.
Why?
It was a bug in Bread::Board.
Fixed in this commit.
Thanks #Yanick.
I use an external module (say Foo.pm) that I don't have control over. The way to use it is like below, which works fine:
use Foo ();
my %config = (
MODE => 'NORMAL',
ERROR => \&my_error, # error handling routine
);
Foo::init(%config);
sub my_error {
my ($message) = #_;
...
}
However I'm having trouble to pass in my_error() to the external module when I'm writing in OO style as the first parameter to my_error() is now $self:
package MyPackage;
use Foo ();
sub new {
my $self = bless {
environment => 'TEST',
config => {
MODE => 'NORMAL',
ERROR => \&my_error, # WRONG ??!
},
}, __PACKAGE__;
Foo::init( %{$self->{config}} );
}
sub my_error {
my ($self, $message) = #_;
...
}
How do I solve it? Passing &{ $self->my_error } does not seem to work.
Thanks!
If you need a sub when you don't have one, you need to make one. You can make an anonymous one.
sub { $self->my_error(#_) }
So that means
my $self = bless {
environment => 'TEST',
config => {
MODE => 'NORMAL',
ERROR => sub { $self->my_error(#_) },
},
}, $class;
But there are complications. In your code, $self hasn't been declared yet when you try to capture it. Fix:
my $self = bless({}, $class);
%$self = (
environment => 'TEST',
config => {
MODE => 'NORMAL',
ERROR => sub { $self->my_error(#_) },
},
);
But that creates a memory leak. The sub captures $self, which references a hash that contains a reference to the sub. Fix:
use Scalar::Util qw( weaken );
my $self = bless({}, $class);
{
weaken( my $self = $self );
%$self = (
environment => 'TEST',
config => {
MODE => 'NORMAL',
ERROR => sub { $self->my_error(#_) },
},
);
}
As simbabque points out, the curry::weak module can simplify(?) this a little.
use curry::weak qw( );
my $self = bless({}, $class);
%$self = (
environment => 'TEST',
config => {
MODE => 'NORMAL',
ERROR => $self->curry::weak::my_error(),
},
);
But I think it'll just add confusion.
A good alternative to the final part of ikegami's excellent and detailed answer is to use curry::weak.
use curry::weak;
my $self = bless({}, $class);
%$self = (
environment => 'TEST',
config => {
MODE => 'NORMAL',
ERROR => $self->curry::weak::my_error(),
},
);
mst, the author of curry, gives a reasonably understandble explanation of how that works in this lightning talk.
If I have a method that takes either one or the other of two named parameters, exactly one of which must be present, is there a way to handle that with Params::Validate?
$store->put( content_ref => $stringref );
or
$store->put( path => $path_to_file );
I'm not seeing it in the docs, but it seems like an obvious use case, so I thought I should ask.
You can use callbacks to achieve something along those lines:
#!/usr/bin/env perl
use strict;
use warnings;
package My::Class;
use Params::Validate;
use YAML;
sub new { bless {} => shift }
sub _xor_param {
my $param = shift;
return sub { defined($_[0]) and not defined($_[1]->{$param}) }
}
my %validation_spec = (
content_ref => {
'default' => undef,
callbacks => {
"Provided only if no 'path' is given"
=> _xor_param('path')
},
},
path => {
'default' => undef,
callbacks => {
"Provided only if no 'content_ref' is given"
=> _xor_param('content_ref')
},
},
);
sub put {
my $self = shift;
validate(#_, \%validation_spec);
print Dump \#_;
}
package main;
my $x = My::Class->new;
$x->put(path => 'some path');
$x->put(content_ref => \'some content');
$x->put(path => 'another_path', content_ref => \'some other content');
Output:
---
- path
- some path
---
- content_ref
- !!perl/ref
=: some content
The 'content_ref' parameter ("SCALAR(0xab83cc)") to My::Class::put did not pass
the 'Provided only if no 'path' is given' callback
at C:\temp\v.pl line 37
My::Class::put(undef, 'path', 'another_path', 'content_ref',
'SCALAR(0xab83cc)') called at C:\temp\v.pl line 47
I write perl classes, but I don't know how to have a array or a hash in my $this variable ?
I have a pack.pm :
#!/usr/bin/perl -w
use strict;
use Parallel::ForkManager;
package Pack;
our $cgi = new CGI;
sub new {
my ($classe, $nom, $nbports, $gio) = #_;
my $this = {
"nom" => $nom,
"nbports" => $nbports,
"gio" => $gio
};
bless($this, $classe);
return $this;
}
...
1;
I would like to have a #tab, I can access via $this->tab, but I don't want to give it in arg to the instance.
How does it work in Perl ?
Thanks.
Given your answer to my comments, I think you want
my($this) = {
"nom" => $nom,
"nbports" => $nbports,
"gio" => $gio,
"tab" => []
};
i.e. set $this->{tab} to be a reference to a new anonymous array.
Now you can reference it as you wish, e.g.
$this->{"tab"}[0] = "new value";
print "Table contains ", scalar(#{$this->{"tab"}}), "entries\n";
Consider using Moose for your OO Perl needs.
I've created a Moose version of your object that includes an attribute with an attribute featuring Array trait delegation, inlcuding currying of delegated methods. Moose offers easy ways to generate powerful, encapsulated classes without writing reams of boilerplate.
I created a class Pack with attributes: nom, nbports, gio, and tab.
nom is a read-only string and is required when the object is created.
nbports is a read-only integer value and defaults to 32 when not provided.
gio is an optional, read-write boolean value.
tab is an array of strings. All sorts of behavior has been defined for tab:
all_tabs returns a list of the contents of tabs
add_tab pushes values onto the end of tabs
tab_count returns a count of the elements in tabs
alpha_tabs returns a list of the members of tabs alphabetical order
turn_tabs returns a list of the strings in tabs, but with the letters in reverse
Any attempts to set an attribute with be checked for type correctness.
Moose creates all the required methods to support these complex behaviors with the following code:
package Pack;
use Moose;
has 'nom' => (
is => 'ro',
isa => 'Str',
required => 1,
);
has 'nbports' => (
is => 'ro',
isa => 'Int',
default => 32,
);
has 'gio' => (
is => 'rw',
isa => 'Bool',
predicate => 'has_gio',
);
has 'tab' => (
traits => ['Array'],
is => 'ro',
isa => 'ArrayRef[Str]',
default => sub {[]},
handles => {
all_tabs => 'elements',
add_tab => 'push',
turn_tabs => [ 'map', sub { reverse } ],
tab_count => 'count',
alpha_tabs => [ 'sort', sub { lc($a) cmp lc($b) } ],
},
);
__PACKAGE__->meta->make_immutable;
no Moose;
1;
Usable like so:
my $p = Pack->new( nom => 'Roger', tab => [qw( fee fie foe fum )] );
my $gio_state = 'UNSET';
if( $p->has_gio ) {
$gio_state = $p->gio ? 'TRUE' : 'FALSE';
}
print "GIO is $gio_state\n";
my #turned = $p->turn_tabs; # eef eif eof muf
$p->add_tabs( 'faa', 'fim' );
my #sorted = $p->alpha_tabls; # faa fee fie fim foe fum
my $count = $p->tab_count; # 6
my $ports = $p->nbports; # 32
try with:
sub set_tab {
my ($self, #tab) = #_;
$self->{ tab } = \#tab;
}
In our classes we have a pattern where we create an attribute to represent a
calculated value. For obvious reasons we want to cache the calculated value
and then invalidate the cache when one of the underlying values change.
So we currently have this:
package FooBar;
use Moose;
has 'foo' => (
accessor => {
'foo' => sub {
my $self = shift;
if (#_ > 0) {
# writer
$self->{foo} = $_[0];
# reset fields that are dependant on me
$self->{bar} = undef;
}
# reader part;
return $self->{foo};
}
}
);
has 'bar' => (
accessor => {
'bar' => sub {
my $self = shift;
if (#_ > 0) {
# writer
$self->{bar} = $_[0];
}
# reader part;
$self->{bar} = calculate_bar($self->foo, $self->baz)
if (not defined($self->{bar}));
return $self->{bar};
}
}
);
sub calculate_bar { ... }
This long hand method is getting very tedious and error prone when calculated values
depend on other calculated values.
Is there a smarter/simpler way for 'bar' to monitor the attributes it depends on
vs having 'foo' know who is dependent on it? Also how can I avoid setting bar via hash
member access?
If I understand you correctly, you can use triggers to clear attributes when one is set. Here's an example:
has 'foo' => (
is => 'rw',
trigger => sub{
my ($self) = #_;
$self->clear_bar;
}
);
has 'bar' => (
is => 'rw',
clearer => 'clear_bar',
lazy => 1,
default => sub{
my ($self) = #_;
return calculate_bar( ... );
}
);
So, any writes to foo via $obj->foo($newvalue) will cause bar to be cleared, and recreated on next access.
I think it is quite possible that you're making this harder on yourself by using an Attributes implicit memoization with lazy, when you could just make the memoization explicit making your whole program more transparent
has [qw/foo bar baz/] => ( isa => 'Value', is => 'rw' );
use Memoize;
memoize('_memoize_this');
sub old_lazy_attr {
my $self = shift;
_memoize_this( $self->attr1, $self->attr2, $self->attr3 );
}
sub _memoize_this {
my #args = #_;
# complex stuff
return $result
}
See cpan's Memoize for information and control of the internal cache, also remember that a Memoized function can not be dependent on the state of the object. So the arguments must be passed in explicitly.
Would this work?
#!/usr/bin/perl
package Test;
use Modern::Perl;
use Moose;
has a => (is => 'rw', isa => 'Str', trigger => \&change_a);
has b => (is => 'rw', isa => 'Str', trigger => \&change_b);
has c => (is => 'rw', isa => 'Str');
sub change_a
{
my $self = shift;
say 'update b';
$self->b($self->a . ', bar');
}
sub change_b
{
my $self = shift;
say 'update c';
}
package main;
my $test = Test->new->a('Foo');
Output:
$ perl test.pl
update b
update c
I haven't done any poking around in Moose internals and the meta object protocol, but I think this is a good time to do it.
You want to patch the code generation so that when you specify an attribute as
has 'foo' => ();
has 'bar' => (
depends_on => [qw( foo )],
lazy => \&calculate_bar,
);
the code generation phase creates code for the foo and bar attributes as you specified above.
How to do this is an exercise left to the reader. If I had a clue, I'd try to give you a start. Unfortunately, all I can advise you with is "This is a job for the MOP".