1

For some fields of a Perl Moo object I want to replace empty string when it is assigned to the field with undef.

That is I want: $obj->x("") to make the field x undefined.

Please help to develop a Moo extension which does this.


A possible way to do this:

sub make_field_undef {
  my ($class, $field_name) = @_;
  eval "package $class";
  around $field_name => sub {
    my $orig = shift;
    my $self = shift;
    my @args = @_;
    if(@args >= 1) {
      $args[0] = undef if defined $args[0] && $args[0] eq '';
    }
    $orig->($self, @args);
  };
}

But are there "more structured" or "more declarative" ways to do this? Any other ways to do this?


A complete example with my implementation of it follows. But running it produces errors which I do not understand:

package UndefOnEmpty;
use Moo;

sub auto_undef_fields { () }

sub make_fields_undef {
  my ($class) = @_;
  eval "package $class";
  around [$class->auto_undef_fields] => sub {
    my $orig = shift;
    my $self = shift;
    my @args = @_;
    if(@args >= 1) {
      $args[0] = undef if defined $args[0] && $args[0] eq '';
    }
    $orig->($self, @args);
  };
  around 'BUILD' => {
    my ($self, $args) = @_;
    foreach my $field_name ($class->auto_undef_fields) {
      $args->{$field_name} = undef if defined $args->{$field_name} && $args->{$field_name} eq "";
    }
  };
}

1;

Usage example:

#!/usr/bin/perl

package X;
use Moo;
use lib '.';
extends 'UndefOnEmpty';
use Types::Standard qw(Str Int Maybe);
use Data::Dumper;

has 'x' => (is=>'rw', isa=>Maybe[Str]);
has 'y' => (is=>'rw', isa=>Maybe[Str]);

sub auto_undef_fields { qw(x y) }
__PACKAGE__->make_fields_undef;

my $obj = X->new(x=>"");
$obj->y("");
print Dumper $obj->x, $obj->y;

Here are the errors:

$ ./test.pl 
"my" variable $class masks earlier declaration in same scope at UndefOnEmpty.pm line 20.
"my" variable $args masks earlier declaration in same statement at UndefOnEmpty.pm line 21.
"my" variable $field_name masks earlier declaration in same statement at UndefOnEmpty.pm line 21.
"my" variable $args masks earlier declaration in same statement at UndefOnEmpty.pm line 21.
"my" variable $field_name masks earlier declaration in same statement at UndefOnEmpty.pm line 21.
syntax error at UndefOnEmpty.pm line 20, near "foreach "
Compilation failed in require at /usr/share/perl5/Module/Runtime.pm line 317.

Please help to understand what are the causes of the errors.

porton
  • 5,214
  • 11
  • 47
  • 95
  • 2
    this is not a code-writing service. It's to help with specific code issues. Please show us what you've already got, and explain the specific problem area you're having trouble with. – stevieb Sep 15 '16 at 15:56
  • 1
    I'll gladly help you, but you haven't done anything at all yet so I don't see what I can do to help. – Borodin Sep 15 '16 at 16:21
  • I've added my solution attempt – porton Sep 15 '16 at 17:13
  • So how your solutions fails to do what you want? The question 'are there "more structured" or "more declarative" ways to do this?' sounds very unclear; if you solved your issue with your code, you should either show a specific technical reason why it's not suitable for you or this becomes too broad/opinion based for SO. – YakovL Sep 15 '16 at 17:32
  • I've added a complete example with my implementation. But it produces errors which I do not understand. Please help – porton Sep 15 '16 at 19:41

1 Answers1

2

Why not use a coercion with the coerce attribute in has? That seems to be the most simple and straightforward way.

package Foo;
use Moo;

has bar => (
    is     => 'rw',
    coerce => sub { $_[0] eq q{} ? undef : $_[0] },
);

Here's what objects look like then.

package main;
use Data::Printer;
p my $foo1 = Foo->new( bar => q{} );
p my $foo2 = Foo->new( bar => 123 );
p my $foo3 = Foo->new;

__END__

Foo  {
    Parents       Moo::Object
    public methods (2) : bar, new
    private methods (0)
    internals: {
        bar   undef
    }
}
Foo  {
    Parents       Moo::Object
    public methods (2) : bar, new
    private methods (0)
    internals: {
        bar   123
    }
}
Foo  {
    Parents       Moo::Object
    public methods (2) : bar, new
    private methods (0)
    internals: {}
}

In Moose you can also define your own type that derives from your Str and has a built-in coercion. Then you can make all your attributes of that type and turn coercion on.

To make Moo behave like that, look at the documentation of has and the attribute isa, which is right above coerce (linked above).

simbabque
  • 53,749
  • 8
  • 73
  • 136