データをイテレーションしながらオブジェクト化

したくなったのでMooseでもよかったのですが
これまたでっちあげてみた。

package Data::Object;
use strict;
use warnings;
our $VERSION = '0.01';
use Data::Object::Iterator;

sub new {
    my ($class, $args) = @_;

    my $itr = Data::Object::Iterator->new($args);

    return $itr;
}
1;

package Data::Object::Class;
use strict;
use warnings;
use Sub::Install qw/install_sub/;

sub new {
    my ($class, $data) = @_;

    my $self = bless $data, $class;
    $self->init;
    return $self;
}

sub init {
    my $self = shift;

    for my $field (keys %{$self->{data}} ) {
        next if $self->can($field);
        install_sub({
            as   => $field,
            code => $self->_razy_inflate_data($field),
        });
    }
}

sub _razy_inflate_data {
    my ($self, $field) = @_;

    return sub {
        my $self = shift;
        if ( $self->{hooks}->{$field} ) {
            $self->{hooks}->{$field}->($self->{data}->{$field});
        } else {
            $self->{data}->{$field};
        }
    };
}

1;

package Data::Object::Iterator;
use strict;
use warnings;
use Digest::SHA1 qw/sha1_hex/;

sub new {
    my ($class, $args) = @_;

    my $klass = 'Data::Object::Class::A' . sha1_hex $$ . $args;
    eval qq|
        package $klass;
        use strict;
        use warnings;
        use base 'Data::Object::Class';
        1;
    |;
    die "can't create klass : $klass $@" if $@;

    $args->{klass} = $klass;
    my $self = bless $args, $class;

    $self->reset;

    return $self;
}

sub iterator {
    my $self = shift;

    my $data = $self->{data}->[$self->{_position}++];
    return unless $data;

    my $obj = $self->{klass}->new(
        +{
            hooks => $self->{add_hooks},
            data  => $data,
        },
    );

    return $obj;
}

sub reset {
    my $self = shift;
    $self->{_position} = 0;
    return;
}

sub next {
    my $self = shift;
    $self->iterator;
}

1;

test code:

use strict;
use warnings;
use Data::Object;
use URI;
use Test::Declare;

plan tests => blocks * 3;


my @data = (
    +{
        name  => 'nekokak',
        url   => 'http://d.hatena.ne.jp/nekokak/',
    },
    +{
        name  => 'example',
        url   => 'http://example.com/',
    },
);

describe 'test Date::Object' => run {
    test 'do test' => run {
        my $obj = Data::Object->new(
            {
                add_hooks => +{
                    url => sub {
                        my $val = shift;
                        return URI->new($val);
                    },
                },
                data => \@data,
            }
        );

        for my $data (@data) {
            my $row = $obj->next;
            is $row->name, $data->{name};
            is $row->url,  $data->{url};
            isa_ok $row->url, 'URI::http';
        }
    };
};

ここではANONクラス作るときにClass::Anonをつかわずに自前でevalしてます。
これもネタなので適当です。