perlmoomoops

Changing writer prefix when (is => “rwp”)


If I want to change write protected attribute ie.

use Moops;

class foo {
  has attr => (is => "rwp");
}

one have to use _set_attr().

Is it possible to change that to _attr() without using explicit writer?

Tried use MooseX::::AttributeShortcuts -writer_prefix => 'prefix'; but it did not work.


Solution

  • No, you need to do that yourself by setting the writer.

    TLDR: At the bottom is a monkey-patch to do it anyway.


    The Moops docs say (emphasys mine):

    Moops uses MooseX::MungeHas in your classes so that the has keyword supports some Moo-specific features, even when you're using Moose or Mouse. Specifically, it supports is => 'rwp', is => 'lazy', builder => 1, clearer => 1, predicate => 1, and trigger => 1.

    Now let's go look at Moo. In the has section of the doc, it says (emphasys mine):

    rwp stands for "read-write protected" and generates a reader like ro, but also sets writer to _set_${attribute_name} for attributes that are designed to be written from inside of the class, but read-only from outside. This feature comes from MooseX::AttributeShortcuts.

    Ok, on to MooseX::AttributeShortcuts:

    Specifying is => 'rwp' will cause the following options to be set:

    is     => 'ro'
    writer => "_set_$name"
    

    However, this is just where it was inspired. It is actually implemented in Moo in Method::Generate::Accessor1.

      } elsif ($is eq 'rwp') {
        $spec->{reader} = $name unless exists $spec->{reader};
        $spec->{writer} = "_set_${name}" unless exists $spec->{writer};
      } elsif ($is ne 'bare') {
    

    And even more actually, that is also not where it is done in Moops. In fact, that happens in MooseX::MungeHas, which Moops uses, but only if the caller is not Moo:

                push @code, '  if ($_{is} eq q(rwp)) {';
                push @code, '    $_{is}     = "ro";';
                push @code, '    $_{writer} = "_set_$_" unless exists($_{writer});';
                push @code, '  }';
    

    Looks pretty clear. It's in generated code. The below solution might work if it uses only Moo, but I don't know how to force that.


    You are indeed able to change that in Moo by hooking into Moo's Method::Generate::Accessor using Class::Method::Modifiers and adding a bit of logic in an around modifier to generate_method. This does not work works for Moops as long as there is no Moose-stuff involved.

    use Moops;
    
    BEGIN {
      require Method::Generate::Accessor; # so it's in %INC;
      require Class::Method::Modifiers;
      Class::Method::Modifiers::around( 'Method::Generate::Accessor::generate_method' => sub {
        my $orig = shift;
    
        #      0      1      2      3      4
        #  my ($self, $into, $name, $spec, $quote_opts) = @_;
    
        if ($_[3]->{is} eq 'rwp') {
          $_[3]->{writer} = "_explicitly_set_$_[2]" unless exists $_[3]->{reader};
        }
    
        $orig->(@_);
      });
    }
    
    class Foo {
        has attr => ( is => "rwp" );
    }
    
    use Data::Printer;
    
    my $foo = Foo->new( attr => 1 );
    p $foo;
    

    Output:

    Foo  {
        Parents       Moo::Object
        public methods (2) : attr, new
        private methods (1) : _explicitly_set_attr
        internals: {
            attr   1
        }
    }
    

    1) I found that using grep.cpan.me.