Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Attribute is => 'Maybe[SomeSubtype]' returns Attribute () does not pass type constraint

Tags:

perl

moose

I've created subtype Birth_d with coercion as shown below, and I'm trying to use it in combination with the built-in Maybe type, per Moose::Manual::Types.

I'm getting the error You cannot coerce an attribute (birth_d) unless its type (Maybe[Birth_d]) has a coercion. Here's complete test code:

package Student;
use Moose;
use Moose::Util::TypeConstraints;

use DateTime::Format::MySQL;

class_type 'Birth_d', { class => 'DateTime' };

coerce 'Birth_d',
  from 'Str',
  via { DateTime::Format::MySQL->parse_date( $_ ) };

has 'name' => (
  isa => 'Str',
  is => 'ro',
);

has 'birth_d' => (
  isa => 'Maybe[Birth_d]',   # This works:    isa => 'Birth_d'
  coerce => 1,
  is => 'ro',
);


package main;

use Test::More;

my $student = Student->new(
  name => 'Johnnie Appleseed',
  birth_d => '2015-01-01'
  );

is ( $student->birth_d->ymd(), '2015-01-01' );

my $student2 = Student->new(
  name => 'Foo Bar',
  birth_d => undef
  );

is( $student2->birth_d, undef );

Replacing isa => 'Maybe[Birth_d]' with isa => 'Birth_d' works, but is not what is needed. I need to make the birth_d optional, and if not supplied, should be undef.

I should add, I tried using MooseX::Types to tuck this Birth_d type away in a separate place, but found its cavalier use of barewords a bit unorthodox, so I slowly backed away. I'm open to reconsidering it, if it makes sense to do so.

like image 923
yahermann Avatar asked Mar 09 '23 00:03

yahermann


2 Answers

Moose does not do any chaining of coercions, in other words you have to tell it explicitly how to convert to a Maybe[Birth_d].

You can do this by reusing the existing coercion to Birth_d:

package Student;
use Moose;
use Moose::Util::TypeConstraints;

use DateTime::Format::MySQL;

# save the Moose::Meta::TypeConstraint object
# you can also get it with find_type_constraint('Birth_d')
my $birth_d = class_type 'Birth_d', { class => 'DateTime' };

coerce 'Birth_d',
  from 'Str',
   via { DateTime::Format::MySQL->parse_date( $_ ) };

subtype 'MaybeBirth_d',
     as 'Maybe[Birth_d]';

coerce 'Maybe[Birth_d]',
  from 'Str|Undef',
   via { $birth_d->coerce($_) };

has 'name' => (
  isa => 'Str',
  is => 'ro',
);

has 'birth_d' => (
  isa => 'Maybe[Birth_d]',
  coerce => 1,
  is => 'ro',
  predicate => 'has_birth_d', # as per your comment
);


package main;

use Test::More;

my $student = Student->new(
  name => 'Johnnie Appleseed',
  birth_d => '2015-01-01'
);

is ( $student->birth_d->ymd(), '2015-01-01' );

my $student2 = Student->new(
  name => 'Foo Bar',
  birth_d => undef
);

is( $student2->birth_d, undef );

ok( $student2->has_birth_d );

done_testing;
like image 50
nothingmuch Avatar answered May 08 '23 11:05

nothingmuch


I would find it more useful to not have a Maybe[Birth_d] type, but simply declare the attribute with the Birth_d type, and no "required" set.

That way, if a valid String is passed in, it will be accepted, an invalid String will lead to an error, and nothing just does not need to be passed in.

However, you can coerce to a maybe type:

subtype 'MaybeBirth_d',
    as maybe_type(class_type('DateTime'));

coerce 'MaybeBirth_d',
    from 'Str',
    via { DateTime::Format::MySQL->parse_date( $_ ) };

has 'birth_d' => (
    isa => 'MaybeBirth_d',
    coerce => 1,
    is => 'ro',
);

I just do not see the value of being able to pass in undef for a birthdate - how is that better than not setting it?

I would also like to suggest using no Moose::Util::TypeConstraints; and no Moose; at the end of your package, or namespace::autoclean; at the beginning, as well as __PACKAGE__->meta->make_immutable; at the end of your Student class.

like image 29
bytepusher Avatar answered May 08 '23 09:05

bytepusher