I have a filename attribute that has attached validation. Now when the filename isn't specified during construction I want to read it from a configuration file.
subtype 'filename'
=> as 'Str'
=> where { -r $_ }
=> message { "$_ is not a readable file" };
has 'students_file' => ( is => 'rw', isa => 'filename', default => '' );
has 'teachers_file' => ( is => 'rw', isa => 'filename', default => '' );
The issue is that BUILD seems to be run after the validation occurs. All examples of BUILDARGS seem to handle a different way of constructing the object.
So where should I read the configuration and set the attribute?
Give teachers_file and students_file builder methods (or inline default subs) that set them from the config file. The builder will only run if those attributes aren't provided as keys to the constructor (unless you use init_arg => undef to prevent them from being set in the constructor).
If your config is an attribute of its own, with a builder that reads the config file, then you have an ordering problem between config and teachers_file and students_file. You can resolve this by making teachers_file and students_file lazy attributes, which ensures that they won't try to be constructed before the config attribute. However, you may want to make sure that the "foo is not a readable file" error is thrown as early as possible during construction, instead of the first time the attribute is used. You can work around that by adding
sub BUILD {
my $self = shift;
$self->teachers_file;
$self->students_file;
}
which ensures that those attributes are read once (and constructed) before the constructor returns.
Related
The following codes make me so confused, I can't find any related knlowledge about the syntax "has ,is ,default, lazy". Can anybody make a detailed explain for me, best wishes.
has 'absolute_E' => (is => 'rw', default => sub {0} );
has 'retract_speed_mm_min' => (is => 'lazy');
has 'retract_speed_mm_min' => (is => 'lazy');
Judging by this line, this is probably a Moo class. To confirm this, have a look near the top of the file, and you should see something like use Moo.
Moo is an object-oriented framework for Perl. I'll assume you understand OO concepts.
Some historical background: Perl 5 has built-in OO capabilities, however it can get a little cumbersome at times. Then Moose came around as an improved way of OOP in Perl. But Moose was also quite heavy, with a compile-time cost, so Moo (and also Mouse just before it) came after that as something of a lighter-weight subset of Moose.
has is for defining attributes in your class.
has 'absolute_E' => ( is => 'rw', default => sub {0} );
This defines an attribute named absolute_E.
is => 'rw' means it is readable and writable, which means you can do this:
my $value = $obj->absolute_E; # gets the value
$obj->absolute_E($value); # sets the value
When you instantiate the object, you can supply a value for the attribute:
my $obj = My::Class->new( absolute_E => 5 );
But if you don't supply anything then absolute_E is set to 0 by default.
This second attribute has a few more things:
has 'retract_speed_mm_min' => (is => 'lazy');
This is short form for:
has 'retract_speed_mm_min' => (
is => 'ro',
lazy => 1,
builder => '_build_retract_speed_mm_min'
);
This attribute is readonly which means you can't change its value after construction. But you can supply a value at construction as before.
The builder is another way of providing a default value. It requires the class to have a separate method named _build_retract_speed_mm_min that should return the default value.
lazy works with builder. It means that the attribute should not be set by the builder until it the attribute is used. The delay may be used because the builder depends on other attributes in order to build this attribute's value.
There's a lot more in Moo and Moose. I would suggest reading http://modernperlbooks.com/books/modern_perl_2014/07-object-oriented-perl.html and https://metacpan.org/pod/Moose::Manual and https://metacpan.org/pod/Moo.
That code basically equals
has ('absolute_E', 'is', 'rw', 'default', sub {0} );
has ('retract_speed_mm_min', 'is', 'lazy');
And has looks like a user-defined subroutine.
=> is almost the same as ,:
The => operator is a synonym for the comma except that it causes a word on its left to be interpreted as a string if it begins with a letter or underscore and is composed only of letters, digits and underscores.
I'm trying to create a Moose hash in my Catalyst app in my_app.pm so that I can access it globally:
has 'hash' => (
is => 'rw',
isa => 'Hashref'
);
However, as soon as I try to add defaults:
has 'hash' => (
is => 'rw',
isa => 'Hashref',
default => sub { {
'key' => 'val',
}
},
);
Then I get this message in my chrome developer tools console:
Resource interpreted as Document but transferred with MIME type httpd/unix-directory
whenever I try to load the page. Then my pages don't load correctly and try to force me to download an empty file whenever I load the page. Is it not possible to use a Moose hash with defaults in my_app.pm? Does anyone know what I'm doing wrong? Thanks!
You have some kind of problem with the way your app is set up; you're seeing that error above when you should be seeing an error page.
Your default is invalid. sub { 'key' => 'val' } doesn't return a hashref; it returns a list of two items ('key' and 'val'). Try default => sub { +{ 'key' => 'val' } } instead.
So I figured out what I was doing wrong, and unfortunately this is one of those 'doh' moments :)
When defining a Moose attribute as a hash, you have to do it as so:
has 'hash' => (
is => 'rw',
isa => 'HashRef'
);
I accidentally misspelled HashRef and used Hashref. Thanks for all of the help! :)
In my previous question Moose - Loading values from conf files... Jack Maney was kind enough to provide an example of how to do so using Moose.
In order to make the configuration object even more general I decided to use Config::Auto.
The problem is that I still am very green as to how Moose works. For instance, Jack's example is:
package My::Module;
use Moose;
has 'config'=>(isa=>'HashRef[Str]',is=>'rw',required=>1);
around BUILDARGS=>sub
{
my $orig=shift;
my $class=shift;
my $args=shift; #other arguments passed in (if any).
my %config_hash=();
open(my $read,"<","config_file") or confess $!;
while(<$read>)
{
chomp;
my #array=split /:/;
$config_hash{$array[0]}=$array[1];
}
close($read);
$args->{config}=\%config_hash;
return $class->$orig($args);
};
no Moose;
1;
I had modified it to this:
#!/usr/local/bin/perl
package DART::Setup;
use namespace::autoclean;
use Moose;
use Config::Auto;
our $VERSION = '0.0.1';
has 'EMPTY' => ( isa => 'Str', is => 'ro', default => q{} );
has 'PPLTESTEXECUTIONID' => ( isa => 'Int', is => 'ro', default => 0 );
has 'SARQTESTEXECUTIONID' => ( isa => 'Int', is => 'ro', default => 0 );
has 'ISPROXY' => ( isa => 'Int', is => 'ro', default => 0 );
has 'LOCALHOST' => ( isa => 'Str', is => 'ro', default => '127.0.0.1' );
has 'config'=>(isa=>'HashRef[Str]',is=>'rw',required=>1);
has 'SSO' => ( isa => 'Str', is => 'rw', default => q{} );
has 'cookieFile' => ( isa => 'Str', is => 'rw', default => q{} );
around BUILDARGS=>sub
{
my $orig=shift;
my $class=shift;
my $args=shift;
my $cfg = Config::Auto::parse($args);
my %config_hash = %{$cfg};
$args->{config}=\%config_hash;
return $class->$orig($args);
};
return 1;
But to be honest I'm not sure what I'm doing here. First off, how many arguments do I need to provide when I'm creating a new Setup object? Do I just pass it the path to my configuration file, something like:
my $newConfig = DART::Setup->new('/home/y/conf/MyApp/MyApp.cfg');
Or do I need to provide arguments for $orig and $class?
Finally, how do I now access my newly loaded configurations? Can I do something like:
my %configHash = %{$newConfig->config()};
foreach my $key (keys %configHash) {
print "the key is, $key, and the value is: $configHash{$key}\n";
}
Am I understanding this correctly?
Okay, inside of BUILDARGS, you want to read in the config file and pass the key-value pairs into the config attribute. Here's a modified version with another attribute for the config file.
package My::Module;
use Moose;
use Config::Auto;
has 'config'=>(isa=>'HashRef[Str]',is=>'rw',required=>1);
has 'config_file'=>(isa=>'Str',is=>'ro');
around BUILDARGS=>sub
{
my $orig=shift;
my $class=shift;
my $args=shift; #currently {config_file=>'/path/to/file/config_file.conf'} (or whatever)
#make sure we've been passed a config file
confess "No config file found in BUILDARGS" unless defined $args->{config_file};
#Now, we open the user-specified config file via Config::Any
my $ca=Config::Auto->new(source=>$args->{config_file},format=>"colon");
my $parsed=$ca->parse; #hash reference containing the parsed data.
#Now, we add this to our arguments that will become our attributes:
$args->{config}=$parsed;
return $class->$orig($args);
}
no Moose;
1;
The main thing to realize about BUILDARGS is that it takes the following arguments: the names of the class and original constructor (which are passed to Moose::Object) and then any other arguments passed to the constructor. So, if you call
my $mm=My::Module->new({config_file=>"/path/to/file/file.conf"});
Then, in BUILDARGS, we initially have
$args=={config_file=>"/path/to/file/file.conf"}
But after parsing the file and adding the $parsed hash reference, it turns into
$args=={config_file=>"/path/to/file/file.conf",config=>{server=>"mozilla.org",protocol=>"HTTP",...}}
etc, etc.
By writing my $cfg = Config::Auto::parse($args); inside of BUILDARGS, you're trying to pass a config_file argument to the parser in Config::Auto, and it'll have no idea what to do with it.
BUILDARGS is just a way to hook into the constructor at the beginning of construction. For your reference, the construction section of the manual might help in your understanding on that.
Jack Maney's answer is perfectly fine. Building on his suggestion to use a config_file attribute, here's an alternative that uses a lazy builder. Personally, I prefer these to BUILDARGS because the code is slightly simpler. Builders are used to set the default value of an attribute. You need to make it lazy because building the attribute depends on another attribute (in this case config_file) to ensure that construction of the object has completed and the attribute has been set.
package DART::Setup;
use namespace::autoclean;
use Moose;
use MooseX::FileAttribute;
use Config::Auto;
# use MooseX::FileAttribute (optional) to enforce that the file actually exists
# - just a shortcut to remove some boilerplate code if you want
has_file 'config_file' => (
is => 'ro',
must_exist => 1,
required => 1,
);
has 'config' => (
isa => 'HashRef[Str]',
is => 'ro',
# disallow this attribute to be set by the constructor
init_arg => undef,
# cause this attribute to be set up after construction
lazy => 1,
builder => '_build_config',
# or alternatively, use 'default' instead of 'builder'
# (but it still needs to be lazy)
#default => sub { Config::Auto::parse( shift->config_file ) },
);
sub _build_config {
my ( $self ) = #_;
my $config = Config::Auto::parse( $self->config_file );
return $config;
}
I understand that using builder enables subclasses to override attribute defaults easily and roles can require them. This can also be accomplished using default like so:
has 'foo' =>
is => 'rw',
isa => 'Str',
default => sub { $_[0]->_build_foo };
I'm wondering if there are further advantages to using builder I'm not aware of? I've come up with some myself:
builder is declarative so you can introspect that foo is built by _build_foo
builder eliminates a subroutine wrapper making it a bit faster
builder allows the use of the helpful lazy_build.
UPDATE To clarify, this isn't about default vs builder in general but default => sub { $_[0]->_build_foo } vs builder => '_build_foo'.
I think you've already answered your own question. Using builder allows late-binding, which plays nicely with roles and classes that are intended to be subclassed. It's also valuable if the builder is pretty long — I never put a default more than a line long into an attribute definition. There's no real functional difference; default can easily emulate builder, but the result isn't very pretty.
Using 'builder' and 'default' appropriately can make your code easier to read and organize.
'builder' also can fit a familiar pattern of programming where private methods begin with an underscore.
has json => ( is => 'ro', default => sub { JSON->new } )
has schema => ( is => 'ro', builder => '_schema' }
sub _schema {
my $self = shift;
$self->log_debug('constructing schema') if($self->debug);
My::App::Schema->connect($self->dsn,$self->username,$self->password)
}
Additionally, using builder allows you to turn expensive functions into memoized accessors without touching the original method:
sub get_things {
my $self = shift;
return +{ map { $_ => $self->price_for($_) }
$self->wodgets->calulate_expensive_things };
Refactor with memoization:
has things => ( is => 'ro', lazy => 1, builder => 'get_things' );
Those are most of the ways I've used builder to clarify my code.
There's no difference between
default => sub { $_[0]->_build_foo }
and
builder => '_build_foo'
The primary difference between default and builder is that one one calls an anon sub and the other calls a named method.
has created_time_stamp => (
default => sub { time() },
);
versus
has created_time_stamp => (
builder => '_build_created_time_stamp',
);
sub _build_created_time_stamp { time() }
Using default reduces scrolling through the code as everything is where you need it. I use it for that reason. That is uses less typing is a bonus.
It also force you to be more explicit about overriding the builder. Other answers have considered this a con, but I consider calling virtual methods on an object that hasn't even been constructed yet to be a bad practice! That's what BUILD is for.
I've got an Attribute Trait that I want to set on the basis of other class attributes. I realy want a default on an Attribute Trait that gets a copy of the class $self and not the meta for the attribute. I want to do something like this in my class:
after 'BUILD' => sub {
my $self = shift;
$self->meta->get_attribute('id')->column_name( $self->_unique_key_name );
};
But, I want to keep my attribute trait RO? Is this possible. I know the MOP allows one to set the value of a class-attribute, but I can't figure out how to set an attribute on the meta-attribute.
This sounds like a really odd design (why would a metaclass need an instance of a class it is describing?) -- but you can do this easily enough by using the metaclass of the metaclass (remember that Moose meta classes are bootstrapped using the MOP itself):
$self->meta->meta->get_attribute("foo")->default($some_value);
Also remember that defaults need to be wrapped in a coderef if they are references themselves: $some_value = sub { $instance };
Actually, this won't work - 'default' is read only. Rather than fiddling with the MOP at such a low level, I would urge you to reconsider your design - e.g. store your 'default' in another attribute, and writing a default sub that delegated to it:
package MyApp::Meta::Attribute::Trait::Foo;
# set at runtime, when we have an instance to store here
has _default_of_foo => (
is => 'rw', isa => 'Object',
);
has foo => (
is => 'ro', isa => 'Object',
lazy => 1,
default => sub { shift->_default_of_foo },
);