0

I want to modify an attribute's value every time it is set, no matter if it is done within constructor or by a 'writer'(i don't use 'builder' or 'default' in that case). Basically the attribute(not necessary 'Str' type) is passed to the constructor and in some cases I want to modify its value after that, but in every scenario I want to do some regexp on it (for example).

My first approach was to use a BUILDARGS and around method, both of would use the same regex function, but then I wonder about coercion. The only problem is I don't know how to create a subtype/type definition that will force coercion no matter what.

For example:

package Foo;
use Moose::Util::TypeConstraints;

subtype 'Foo::bar' => as 'Str';
coerce 'Foo::bar'
    => from 'Str'
        => via {
            $_ =~ s/some_stuff//g;
            $_ =~ s/other_stuff//g;
            $_ =~ s/some_other_stuff//g;
        };

has 'bar' => (isa => 'Foo:bar', coerce => 1);

I don't want to define subtype/type with 'where' clause like

subtype 'Foo::bar' => as 'Str' => where {$_ !~ /some_stuff/ && $_ !~ /other_stuff/ && ... };

because it seems tedious to me.

Edit: I'm looking for a comprehensive solution I could use not only with 'Str' type attributes but also 'ArrayRef', 'HashRef' etc.

roland16
  • 27
  • 4
  • Are you saying the coercion doesn't always occur? Under what situation is the value not coerced? – ikegami Jul 15 '15 at 15:57
  • @ikegami Foo->new( bar => 'Some string' ) The coercion won't occur because passed value is already a string. "A coercion lets you tell Moose to automatically convert one type to another", and both in and out types are string. I don't insist on using this method, just looking for best option. – roland16 Jul 15 '15 at 18:11
  • So you're saying the coercion never occurs? Please specify what your problem is!!! At least provide a demonstration of it! – ikegami Jul 15 '15 at 18:35
  • You're right, i wasn't clear enough. The coercion works, but it doesn't step into 'via' block due to the fact that both passed value and subtype are strings and there is no 'where' clause in subtype definition that could force coercion to step into 'via' block. – roland16 Jul 15 '15 at 19:09

1 Answers1

0

Sounds like you want a trigger.

package Foo;
use Moose;

has 'bar' => (
    is      => 'rw',
    isa     => 'Str',
    trigger => sub {
        my ( $self, $value, $old_value ) = @_;
say 'in trigger';
        # prevent infinite loop
        return if $old_value && $old_value eq $value;

        my $original_value = $value;
        $value =~ s/some_stuff//g;
        $value =~ s/other_stuff//g;
        $value =~ s/some_other_stuff//g;

        # prevent infinite loop
        return if $value eq $original_value;

say '... setting new value';
        $self->bar($value);
    },
);

package main;

my $foo = Foo->new( bar => 'foo some_stuff and other_stuff and some_more_stuff' );
say $foo->bar;

I want to modify an attribute's value every time it is set, no matter if it is done within constructor or by a 'writer'(i don't use 'builder' or 'default' in that case)

This is exactly what a trigger does. The doc says almost verbatim what you asked for:

NOTE: Triggers will only fire when you assign to the attribute, either in the constructor, or using the writer. Default and built values will not cause the trigger to be fired.

Edit: There was a bug in the inifite loop detection. It now works and will stop on the second invocation. I left debug output in to demonstrate.

in trigger
... setting new value
in trigger
foo  and  and some_more_stuff
simbabque
  • 53,749
  • 8
  • 73
  • 136
  • You will get an infinite recursion. Trigger fires **after** the attribute is set and because you are setting the 'bar' once again in the sub, trigger will fall into infinite loop. The above example would work if you set the attribute this way `$self->{bar} = 'modified_value'` in the trigger sub, but from what i read handle $self as a hash reference is bad practice. – roland16 Jul 16 '15 at 12:23
  • The first version of my answer did not have an infinite loop. In fact, it changed nothing because I had a bug. I have updated the answer with a version that will do what you want without creating an inifite loop. – simbabque Jul 16 '15 at 12:55
  • Well your question was for `Str` and you explicitly said you don't want to actually verify the type of your string in the last part of the question. Checking if the string needs those replacements _is exactly what the type does_ on which the coercion would be based. For things that are not a string I agree that coercion makes a lot more sense. But you have asked about a very specific case, and this is the answer for that specific question. Take it or leave it. :) – simbabque Jul 16 '15 at 13:25
  • I'm looking for more comprehensive solution. These workarounds works well with 'Str' type of value, but won't do with more complex structures. From what i've read coercion would be the best solution, but i dont' know how to implement it. – roland16 Jul 16 '15 at 13:25
  • Basically my problem is exactly what the first sentence states. The code below was only an example and i didn't mention anything about string type in description( i will edit my question so no one will be confused about it). I'm looking for a comprehensive solution and i don't want to use workarounds, but if there's no better solution i promise i'll mark your answer as the best one;) – roland16 Jul 16 '15 at 13:48