Introducing Sub::Trigger::Lock
Sub::Trigger::Lock is a workaround for the problem that Moose read-only attributes aren't really read-only; at least, not in the way that people might expect them to be. Here's an example:
package Foo { use Moose; has bar => (is => 'ro', isa => 'ArrayRef'); } my $foo = Foo->new( bar => [1,2,3] ); push @{ $foo->bar }, 4; # does not die!
A read-only attribute containing an arrayref of hashref cannot have the attribute changed to reference another array or hash. However, the contents of that array or hash are not magically read-only.
Sub::Trigger::Lock can make them magically read-only.
package Foo { use Moose; use Sub::Trigger::Lock; has bar => (is => 'ro', isa => 'ArrayRef', trigger => Lock); } my $foo = Foo->new( bar => [1,2,3] ); push @{ $foo->bar }, 4; # kablammo!
Sub::Trigger::Lock also contains various utility functions for temporarily unlocking and re-locking the array, which allows the Foo
method to provide its own API for altering the array:
package Foo { use Moose; use Sub::Trigger::Lock -all; has bar => (is => 'ro', isa => 'ArrayRef', trigger => Lock); sub bar_append { my $self = shift; my $guard = unlock $self->bar; push @{ $self->bar }, @_; } } my $foo = Foo->new( bar => [1,2,3] ); $foo->bar_append(4); # ok push @{ $foo->bar }, 5; # kablammo!
That's about it. Sub::Trigger::Lock allows you to stop people from altering your object's hashrefs and arrayrefs from underneath it. It forces them (or at least strongly encourages them) to use your API to alter your object's attributes.
Too good, Toby. Keep up the good work. However it would have been nicer if it comes under the namespace of Moose::* or MooseX::* in my humble opinion.
The reason I've not put it in the Moose namespace, is that it's not really Moose-specific code. It will also work for other frameworks that provide a similar "trigger" feature, including Moo and Mouse.
Looking at the article above, and indeed at the documentation, I realise I've not made that clear at all!
The test suite though includes tests to demonstrate that it works with Moose and Moo.
You're not locking the external access, but the internal implementation. IMO that's the wrong solution.