MooseX-Attribute-Deflator

Deflate and inflate Moose attribute values


License
BSD-3-Clause

Documentation

SYNOPSIS

 package MySynopsis;

 use Moose;
 use DateTime;

 use MooseX::Attribute::Deflator;

 deflate 'DateTime',
    via { $_->epoch },
    inline_as { '$value->epoch' }; # optional
 inflate 'DateTime',
    via { DateTime->from_epoch( epoch => $_ ) },
    inline_as { 'DateTime->from_epoch( epoch => $value )' }; # optional

 no MooseX::Attribute::Deflator;

 # import default deflators and inflators for Moose types
 use MooseX::Attribute::Deflator::Moose;

 has now => ( is => 'rw', 
            isa => 'DateTime', 
            default => sub { DateTime->now }, 
            traits => ['Deflator'] );

 has hash => ( is => 'rw', 
               isa => 'HashRef', 
               default => sub { { foo => 'bar' } }, 
               traits => ['Deflator'] );

 package main;

 use Test::More;

 my $obj = MySynopsis->new;

 {
     my $attr = $obj->meta->get_attribute('now');
     my $deflated = $attr->deflate($obj);
     like($deflated, qr/^\d+$/);

     my $inflated = $attr->inflate($obj, $deflated);
     isa_ok($inflated, 'DateTime');
 }

 {
     my $attr = $obj->meta->get_attribute('hash');
     my $deflated = $attr->deflate($obj);
     is($deflated, '{"foo":"bar"}');

     my $inflated = $attr->inflate($obj, $deflated);
     is_deeply($inflated, {foo => 'bar'})
 }

 done_testing;

DESCRIPTION

This module consists of a a registry (MooseX::Attribute::Deflator::Registry) an attribute trait MooseX::Attribute::Deflator::Meta::Role::Attribute and predefined deflators and inflators for Moose MooseX::Attribute::Deflator::Moose and MooseX::Types::Strutured MooseX::Attribute::Deflator::Structured. This class is just sugar to set the inflators and deflators.

You can deflate to whatever data structure you want. Loading MooseX::Attribute::Deflator::Moose will cause HashRefs and ArrayRefs to be encoded as JSON strings. However, you can simply overwrite those deflators (and inflators) to deflate to something different like Storable.

Unlike coerce, you don't need to create a deflator and inflator for every type. Instead this module will bubble up the type hierarchy and use the first deflator or inflator it finds.

This comes at a cost: Union types are not supported.

For extra speed, inflators and deflators can be inlined. All in/deflators that come with this module have an inlined version as well. Whenever you implment custom type in/deflators, you should consider writing the inlining code as well. The performance boost is immense. You can check whether an deflator has been inlined by calling:

 $attr->is_deflator_inlined;

Inlining works in Moose >= 1.9 only.

FUNCTIONS

deflate
inflate
 deflate 'DateTime',
    via { $_->epoch },
    inline_as { '$value->epoch' }; # optional

 inflate 'DateTime',
    via { DateTime->from_epoch( epoch => $_ ) },
    inline_as { 'DateTime->from_epoch( epoch => $value )' }; # optional

Defines a deflator or inflator for a given type constraint. This can also be a type constraint defined via MooseX::Types and parameterized types.

The function supplied to via is called with $_ set to the attribute's value and with the following arguments:

$attr

The attribute on which this deflator/inflator has been called

$constraint

The type constraint attached to the attribute

$deflate/$inflate

A code reference to the deflate or inflate function. E.g. this is handy if you want to call the type's parent's parent inflate or deflate method:

 deflate 'MySubSubType', via {
    my ($attr, $constraint, $deflate) = @_;
    return $deflate->($_, $constraint->parent->parent);
 };
$instance

The object instance on which this deflator/inflator has been called.

@_

Any other arguments added to "inflate" in MooseX::Attribute::Deflator::Meta::Role::Attribute or "deflate" in MooseX::Attribute::Deflator::Meta::Role::Attribute.

For inline, the parameters are handled a bit differently. The code generating subroutine is called with the following parameters:

$constraint

The type constraint attached to the attribute.

$attr

The attribute on which this deflator/inflator has been called.

$registry
 my $parent = $registry->($constraint->parent);
 my $code = $parent->($constraint->parent, $attr, $registry, @_);

To get the code generator of a type constraint, call this function.

The inline function is expected to return a string. The generated code has access to a number of variables:

$value

Most important, the value that should be de- or inflated is stored in $value.

$type_constraint

For some more advanced examples, have a look at the source of MooseX::Attribute::Deflator::Moose and MooseX::Attribute::Deflator::Structured.

DEFLATE AN OBJECT INSTANCE

Usually, you want to deflate certain attributes of a class, but this module only works on a per attribute basis. In order to deflate an instance with all of its attributes, you can use the following code:

 sub deflate {
    my $self = shift;
    
    # you probably want to deflate only those that are required or have a value
    my @attributes = grep { $_->has_value($self) || $_->is_required }
                     $self->meta->get_all_attributes;
    
    # works only if all attributes have the 'Deflator' trait applied
    return { map { $_->name => $_->deflate($self) } @attributes };
 }

If you are using MooseX::Attribute::LazyInflator, throw in a call to "is_inflated" in MooseX::Attribute::LazyInflator::Meta::Role::Attribute to make sure that you don't deflate an already deflated attribute. Instead, you can just use "get_raw_value" in Moose::Meta::Attribute to get the deflated value.

PERFORMANCE

The overhead for having custom deflators or inflators per attribute is minimal. The file benchmark.pl tests three ways of deflating the value of a HashRef attribute to a json encoded string (using JSON).

 my $obj     = MyBenchmark->new( hashref => { foo => 'bar' } );
 my $attr    = MyBenchmark->meta->get_attribute('hashref');
deflate
 $attr->deflate($obj); 

Using the deflate attribute method, supplied by this module.

accessor
 JSON::encode_json($obj->hashref);

If the attribute comes with an accessor, you can use this method, to deflate its value. However, you need to know the name of the accessor in order to use this method.

get_value
 JSON::encode_json($attr->get_value($obj, 'hashref'));

This solves the mentioned problem with not knowing the accessor name.

The results clearly states that using the deflate method adds only minimal overhead to deflating the attribute value manually.

               Rate get_value   deflate  accessor
 get_value  69832/s        --      -87%      -88%
 deflate   543478/s      678%        --       -4%
 accessor  564972/s      709%        4%        --