X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FMooseX%2FStorage.pm;h=b5731d4f8a48de84b3384d4d8261d4470b3fdec6;hb=e44b5f5498b782752d2c91b6796698c86143a2f0;hp=07bc85f7a7368e268ccaf6966b97f7589372e40d;hpb=eebcb6dc421f51c455af1dc6b8aa3bb0ffc6f148;p=gitmo%2FMooseX-Storage.git diff --git a/lib/MooseX/Storage.pm b/lib/MooseX/Storage.pm index 07bc85f..b5731d4 100644 --- a/lib/MooseX/Storage.pm +++ b/lib/MooseX/Storage.pm @@ -3,47 +3,104 @@ package MooseX::Storage; use Moose qw(confess); use MooseX::Storage::Meta::Attribute::DoNotSerialize; +use String::RewritePrefix (); + +our $VERSION = '0.32'; +our $AUTHORITY = 'cpan:STEVAN'; sub import { my $pkg = caller(); - + return if $pkg eq 'main'; - + ($pkg->can('meta')) || confess "This package can only be used in Moose based classes"; - - $pkg->meta->alias_method('Storage' => sub { - my %params = @_; - - $params{'base'} ||= 'Basic'; - - my @roles = ( - ('MooseX::Storage::' . $params{'base'}), - ); - - # NOTE: - # you don't have to have a format - # role, this just means you dont - # get anything other than pack/unpack - push @roles => 'MooseX::Storage::Format::' . $params{'format'} - if exists $params{'format'}; - - # NOTE: - # if you do choose an IO role, then - # you *must* have a format role chosen - # since load/store require freeze/thaw - if (exists $params{'io'}) { - (exists $params{'format'}) - || confess "You must specify a format role in order to use an IO role"; - push @roles => 'MooseX::Storage::IO::' . $params{'io'}; + + $pkg->meta->add_method('Storage' => __PACKAGE__->meta->find_method_by_name('_injected_storage_role_generator')); +} + +my %HORRIBLE_GC_AVOIDANCE_HACK; + +sub _rewrite_role_name { + my ($self, $base, $string) = @_; + + my $role_name = scalar String::RewritePrefix->rewrite( + { + '' => "MooseX::Storage::$base\::", + '=' => '', + }, + $string, + ); +} + +sub _expand_role { + my ($self, $base, $value) = @_; + + return unless defined $value; + + if (ref $value) { + confess "too many args in arrayref role declaration" if @$value > 2; + my ($class, $param) = @$value; + + $class = $self->_rewrite_role_name($base => $class); + Class::MOP::load_class($class); + + my $role = $class->meta->generate_role(parameters => $param); + + $HORRIBLE_GC_AVOIDANCE_HACK{ $role->name } = $role; + return $role->name; + } else { + my $class = $self->_rewrite_role_name($base, $value); + Class::MOP::load_class($class); + + my $role = $class; + + if ($class->meta->isa( + 'MooseX::Role::Parameterized::Meta::Role::Parameterizable' + )) { + $role = $class->meta->generate_role(parameters => undef); + $HORRIBLE_GC_AVOIDANCE_HACK{ $role->name } = $role; + return $role->name; } - - Class::MOP::load_class($_) - || die "Could not load role (" . $_ . ") for package ($pkg)" - foreach @roles; - - return @roles; - }); + + return $class; + } +} + +sub _injected_storage_role_generator { + my %params = @_; + + $params{base} = '=MooseX::Storage::Basic' unless defined $params{base}; + + my @roles = __PACKAGE__->_expand_role(Base => $params{base}); + + # NOTE: + # you don't have to have a format + # role, this just means you dont + # get anything other than pack/unpack + push @roles, __PACKAGE__->_expand_role(Format => $params{format}); + + # NOTE: + # many IO roles don't make sense unless + # you have also have a format role chosen + # too, the exception being StorableFile + # + # NOTE: + # we dont need this code anymore, cause + # the role composition will catch it for + # us. This allows the StorableFile to work + #(exists $params{'format'}) + # || confess "You must specify a format role in order to use an IO role"; + push @roles, __PACKAGE__->_expand_role(IO => $params{io}); + + # Note: + # These traits alter the behaviour of the engine, the user can + # specify these per role-usage + for my $trait ( @{ $params{'traits'} ||= [] } ) { + push @roles, __PACKAGE__->_expand_role(Traits => $trait); + } + + return @roles; } 1; @@ -54,12 +111,199 @@ __END__ =head1 NAME -MooseX::Storage - An serialization framework for Moose classes +MooseX::Storage - A serialization framework for Moose classes =head1 SYNOPSIS + package Point; + use Moose; + use MooseX::Storage; + + our $VERSION = '0.01'; + + with Storage('format' => 'JSON', 'io' => 'File'); + + has 'x' => (is => 'rw', isa => 'Int'); + has 'y' => (is => 'rw', isa => 'Int'); + + 1; + + my $p = Point->new(x => 10, y => 10); + + ## methods to pack/unpack an + ## object in perl data structures + + # pack the class into a hash + $p->pack(); # { __CLASS__ => 'Point-0.01', x => 10, y => 10 } + + # unpack the hash into a class + my $p2 = Point->unpack({ __CLASS__ => 'Point-0.01', x => 10, y => 10 }); + + ## methods to freeze/thaw into + ## a specified serialization format + ## (in this case JSON) + + # pack the class into a JSON string + $p->freeze(); # { "__CLASS__" : "Point-0.01", "x" : 10, "y" : 10 } + + # unpack the JSON string into a class + my $p2 = Point->thaw('{ "__CLASS__" : "Point-0.01", "x" : 10, "y" : 10 }'); + + ## methods to load/store a class + ## on the file system + + $p->store('my_point.json'); + + my $p2 = Point->load('my_point.json'); + =head1 DESCRIPTION +MooseX::Storage is a serialization framework for Moose, it provides +a very flexible and highly pluggable way to serialize Moose classes +to a number of different formats and styles. + +=head2 Important Note + +This is still an early release of this module, so use with caution. +It's outward facing serialization API should be considered stable, +but I still reserve the right to make tweaks if I need too. Anything +beyond the basic pack/unpack, freeze/thaw and load/store should not +be relied on. + +=head2 Levels of Serialization + +There are 3 levels to the serialization, each of which builds upon +the other and each of which can be customized to the specific needs +of your class. + +=over 4 + +=item B + +The first (base) level is C and C. In this level the +class is serialized into a Perl HASH reference, it is tagged with the +class name and each instance attribute is stored. Very simple. + +This level is not optional, it is the bare minumum that +MooseX::Storage provides and all other levels build on top of this. + +See L for the fundamental implementation and +options to C and C + +=item B + +The second (format) level is C and C. In this level the +output of C is sent to C or the output of C is sent +to C. This levels primary role is to convert to and from the +specific serialization format and Perl land. + +This level is optional, if you don't want/need it, you don't have to +have it. You can just use C/C instead. + +=item B + +The third (io) level is C and C. In this level we are reading +and writing data to file/network/database/etc. + +This level is also optional, in most cases it does require a C role +to also be used, the exception being the C role. + +=back + +=head2 Behaviour modifiers + +The serialization behaviour can be changed by supplying C. +This can be done as follows: + + use MooseX::Storage; + with Storage( traits => [Trait1, Trait2,...] ); + +The following traits are currently bundled with C: + +=over 4 + +=item OnlyWhenBuilt + +Only attributes that have been built (ie, where the predicate returns +'true') will be serialized. This avoids any potentially expensive computations. + +See L for details. + +=back + +=head2 How we serialize + +There are always limits to any serialization framework, there are just +some things which are really difficult to serialize properly and some +things which cannot be serialized at all. + +=head2 What can be serialized? + +Currently only numbers, string, ARRAY refs, HASH refs and other +MooseX::Storage enabled objects are supported. + +With Array and Hash references the first level down is inspected and +any objects found are serialized/deserialized for you. We do not do +this recusively by default, however this feature may become an +option eventually. + +The specific serialize/deserialize routine is determined by the +Moose type constraint a specific attribute has. In most cases subtypes +of the supported types are handled correctly, and there is a facility +for adding handlers for custom types as well. This will get documented +eventually, but it is currently still in development. + +=head2 What can not be serialized? + +We do not support CODE references yet, but this support might be added +in using B::Deparse or some other deep magic. + +Scalar refs are not supported, mostly because there is no way to know +if the value being referenced will be there when the object is inflated. +I highly doubt will be ever support this in a general sense, but it +would be possible to add this yourself for a small specific case. + +Circular references are specifically disallowed, however if you break +the cycles yourself then re-assemble them later you can get around this. +The reason we disallow circular refs is because they are not always supported +in all formats we use, and they tend to be very tricky to do for all +possible cases. It is almost always something you want to have tight control +over anyway. + +=head1 CAVEAT + +This is B a persistence framework; changes to your object after +you load or store it will not be reflected in the stored class. + +=head1 EXPORTS + +=over 4 + +=item B + +This module will export the C method and can be used to +load a specific set of MooseX::Storage roles to implement a specific +combination of features. It is meant to make things easier, but it +is by no means the only way. You can still compose your roles by +hand if you like. + +By default, options are assumed to be short forms. For example, this: + + Storage(format => 'JSON'); + +...will result in looking for MooseX::Storage::Format::JSON. To use a role +that is not under the default namespace prefix, start with an equal sign: + + Storage(format => '=My::Private::JSONFormat'); + +To use a parameterized role (for which, see L) you +can pass an arrayref of the role name (in short or long form, as above) and its +parameters: + + Storage(format => [ JSONpm => { json_opts => { pretty => 1 } } ]); + +=back + =head1 METHODS =over 4 @@ -76,9 +320,18 @@ MooseX::Storage - An serialization framework for Moose classes =back +=head1 TODO + +This module needs docs and probably a Cookbook of some kind as well. +This is an early release, so that is my excuse for now :) + +For the time being, please read the tests and feel free to email me +if you have any questions. This module can also be discussed on IRC +in the #moose channel on irc.perl.org. + =head1 BUGS -All complex software has bugs lurking in it, and this module is no +All complex software has bugs lurking in it, and this module is no exception. If you find a bug please either email me, or add the bug to cpan-RT. @@ -88,9 +341,11 @@ Chris Prather Echris.prather@iinteractive.comE Stevan Little Estevan.little@iinteractive.comE +Yuval Kogman Eyuval.kogman@iinteractive.comE + =head1 COPYRIGHT AND LICENSE -Copyright 2007 by Infinity Interactive, Inc. +Copyright 2007-2008 by Infinity Interactive, Inc. L