Commit | Line | Data |
e59193fb |
1 | package MooseX::Storage; |
ec9c1923 |
2 | use Moose qw(confess); |
e59193fb |
3 | |
eebcb6dc |
4 | use MooseX::Storage::Meta::Attribute::DoNotSerialize; |
49f81d6c |
5 | use String::RewritePrefix (); |
eebcb6dc |
6 | |
e59193fb |
7 | sub import { |
8 | my $pkg = caller(); |
ec725183 |
9 | |
ec9c1923 |
10 | return if $pkg eq 'main'; |
ec725183 |
11 | |
ec9c1923 |
12 | ($pkg->can('meta')) |
13 | || confess "This package can only be used in Moose based classes"; |
ec725183 |
14 | |
15 | $pkg->meta->add_method('Storage' => __PACKAGE__->meta->find_method_by_name('_injected_storage_role_generator')); |
f9143059 |
16 | } |
17 | |
9ff679e4 |
18 | my %HORRIBLE_GC_AVOIDANCE_HACK; |
19 | |
61fb1aaa |
20 | sub _rewrite_role_name { |
21 | my ($self, $base, $string) = @_; |
22 | |
23 | my $role_name = scalar String::RewritePrefix->rewrite( |
24 | { |
25 | '' => "MooseX::Storage::$base\::", |
26 | '=' => '', |
27 | }, |
28 | $string, |
29 | ); |
30 | } |
31 | |
32 | sub _expand_role { |
33 | my ($self, $base, $value) = @_; |
f35d2054 |
34 | |
35 | return unless defined $value; |
36 | |
37 | if (ref $value) { |
61fb1aaa |
38 | confess "too many args in arrayref role declaration" if @$value > 2; |
39 | my ($class, $param) = @$value; |
9ff679e4 |
40 | |
61fb1aaa |
41 | $class = $self->_rewrite_role_name($base => $class); |
9ff679e4 |
42 | Class::MOP::load_class($class); |
43 | |
44 | my $role = $class->meta->generate_role(parameters => $param); |
45 | |
46 | $HORRIBLE_GC_AVOIDANCE_HACK{ $role->name } = $role; |
47 | return $role->name; |
f35d2054 |
48 | } else { |
61fb1aaa |
49 | my $class = $self->_rewrite_role_name($base, $value); |
50 | Class::MOP::load_class($class); |
51 | |
52 | my $role = $class; |
53 | |
54 | if ($class->meta->isa( |
55 | 'MooseX::Role::Parameterized::Meta::Role::Parameterizable' |
56 | )) { |
57 | $role = $class->meta->generate_role(parameters => undef); |
58 | $HORRIBLE_GC_AVOIDANCE_HACK{ $role->name } = $role; |
59 | return $role->name; |
60 | } |
61 | |
62 | return $class; |
f35d2054 |
63 | } |
49f81d6c |
64 | } |
65 | |
f9143059 |
66 | sub _injected_storage_role_generator { |
67 | my %params = @_; |
ec725183 |
68 | |
49f81d6c |
69 | $params{base} = '=MooseX::Storage::Basic' unless defined $params{base}; |
ec725183 |
70 | |
61fb1aaa |
71 | my @roles = __PACKAGE__->_expand_role(Base => $params{base}); |
ec725183 |
72 | |
f9143059 |
73 | # NOTE: |
ec725183 |
74 | # you don't have to have a format |
75 | # role, this just means you dont |
f9143059 |
76 | # get anything other than pack/unpack |
61fb1aaa |
77 | push @roles, __PACKAGE__->_expand_role(Format => $params{format}); |
ec725183 |
78 | |
f9143059 |
79 | # NOTE: |
ec725183 |
80 | # many IO roles don't make sense unless |
f9143059 |
81 | # you have also have a format role chosen |
82 | # too, the exception being StorableFile |
49f81d6c |
83 | # |
84 | # NOTE: |
85 | # we dont need this code anymore, cause |
86 | # the role composition will catch it for |
87 | # us. This allows the StorableFile to work |
88 | #(exists $params{'format'}) |
89 | # || confess "You must specify a format role in order to use an IO role"; |
61fb1aaa |
90 | push @roles, __PACKAGE__->_expand_role(IO => $params{io}); |
ec725183 |
91 | |
76218b46 |
92 | # Note: |
93 | # These traits alter the behaviour of the engine, the user can |
94 | # specify these per role-usage |
95 | for my $trait ( @{ $params{'traits'} ||= [] } ) { |
61fb1aaa |
96 | push @roles, __PACKAGE__->_expand_role(Traits => $trait); |
76218b46 |
97 | } |
76b60811 |
98 | |
f9143059 |
99 | return @roles; |
e59193fb |
100 | } |
101 | |
ec9c1923 |
102 | 1; |
e59193fb |
103 | |
ec9c1923 |
104 | __END__ |
e59193fb |
105 | |
ec9c1923 |
106 | =pod |
e59193fb |
107 | |
ec9c1923 |
108 | =head1 NAME |
e9739624 |
109 | |
d109f069 |
110 | MooseX::Storage - A serialization framework for Moose classes |
e59193fb |
111 | |
ec9c1923 |
112 | =head1 SYNOPSIS |
e9739624 |
113 | |
1390c23d |
114 | package Point; |
115 | use Moose; |
116 | use MooseX::Storage; |
ec725183 |
117 | |
1390c23d |
118 | with Storage('format' => 'JSON', 'io' => 'File'); |
ec725183 |
119 | |
1390c23d |
120 | has 'x' => (is => 'rw', isa => 'Int'); |
121 | has 'y' => (is => 'rw', isa => 'Int'); |
ec725183 |
122 | |
1390c23d |
123 | 1; |
ec725183 |
124 | |
1390c23d |
125 | my $p = Point->new(x => 10, y => 10); |
ec725183 |
126 | |
127 | ## methods to pack/unpack an |
1390c23d |
128 | ## object in perl data structures |
ec725183 |
129 | |
1390c23d |
130 | # pack the class into a hash |
c1830046 |
131 | $p->pack(); # { __CLASS__ => 'Point-0.01', x => 10, y => 10 } |
ec725183 |
132 | |
1390c23d |
133 | # unpack the hash into a class |
c1830046 |
134 | my $p2 = Point->unpack({ __CLASS__ => 'Point-0.01', x => 10, y => 10 }); |
1390c23d |
135 | |
ec725183 |
136 | ## methods to freeze/thaw into |
1390c23d |
137 | ## a specified serialization format |
138 | ## (in this case JSON) |
ec725183 |
139 | |
1390c23d |
140 | # pack the class into a JSON string |
c1830046 |
141 | $p->freeze(); # { "__CLASS__" : "Point-0.01", "x" : 10, "y" : 10 } |
ec725183 |
142 | |
1390c23d |
143 | # unpack the JSON string into a class |
ec725183 |
144 | my $p2 = Point->thaw('{ "__CLASS__" : "Point-0.01", "x" : 10, "y" : 10 }'); |
1390c23d |
145 | |
ec725183 |
146 | ## methods to load/store a class |
1390c23d |
147 | ## on the file system |
ec725183 |
148 | |
1390c23d |
149 | $p->store('my_point.json'); |
ec725183 |
150 | |
1390c23d |
151 | my $p2 = Point->load('my_point.json'); |
152 | |
ec9c1923 |
153 | =head1 DESCRIPTION |
154 | |
ec725183 |
155 | MooseX::Storage is a serialization framework for Moose, it provides |
1390c23d |
156 | a very flexible and highly pluggable way to serialize Moose classes |
157 | to a number of different formats and styles. |
158 | |
7b428d1f |
159 | =head2 Important Note |
160 | |
ec725183 |
161 | This is still an early release of this module, so use with caution. |
162 | It's outward facing serialization API should be considered stable, |
7b428d1f |
163 | but I still reserve the right to make tweaks if I need too. Anything |
ec725183 |
164 | beyond the basic pack/unpack, freeze/thaw and load/store should not |
7b428d1f |
165 | be relied on. |
166 | |
1390c23d |
167 | =head2 Levels of Serialization |
168 | |
ec725183 |
169 | There are 3 levels to the serialization, each of which builds upon |
1390c23d |
170 | the other and each of which can be customized to the specific needs |
171 | of your class. |
172 | |
173 | =over 4 |
174 | |
175 | =item B<base> |
176 | |
ec725183 |
177 | The first (base) level is C<pack> and C<unpack>. In this level the |
178 | class is serialized into a Perl HASH reference, it is tagged with the |
1390c23d |
179 | class name and each instance attribute is stored. Very simple. |
180 | |
8af2c2b0 |
181 | This level is not optional, it is the bare minimum that |
1390c23d |
182 | MooseX::Storage provides and all other levels build on top of this. |
183 | |
f105066e |
184 | See L<MooseX::Storage::Basic> for the fundamental implementation and |
c21a034f |
185 | options to C<pack> and C<unpack> |
186 | |
1390c23d |
187 | =item B<format> |
188 | |
ec725183 |
189 | The second (format) level is C<freeze> and C<thaw>. In this level the |
190 | output of C<pack> is sent to C<freeze> or the output of C<thaw> is sent |
191 | to C<unpack>. This levels primary role is to convert to and from the |
192 | specific serialization format and Perl land. |
1390c23d |
193 | |
ec725183 |
194 | This level is optional, if you don't want/need it, you don't have to |
1390c23d |
195 | have it. You can just use C<pack>/C<unpack> instead. |
196 | |
8af2c2b0 |
197 | =for stopwords io |
198 | |
1390c23d |
199 | =item B<io> |
200 | |
ec725183 |
201 | The third (io) level is C<load> and C<store>. In this level we are reading |
202 | and writing data to file/network/database/etc. |
1390c23d |
203 | |
124c2ba5 |
204 | This level is also optional, in most cases it does require a C<format> role |
f105066e |
205 | to also be used, the exception being the C<StorableFile> role. |
1390c23d |
206 | |
207 | =back |
208 | |
76218b46 |
209 | =head2 Behaviour modifiers |
210 | |
211 | The serialization behaviour can be changed by supplying C<traits>. |
212 | This can be done as follows: |
213 | |
214 | use MooseX::Storage; |
215 | with Storage( traits => [Trait1, Trait2,...] ); |
ec725183 |
216 | |
76218b46 |
217 | The following traits are currently bundled with C<MooseX::Storage>: |
218 | |
219 | =over 4 |
220 | |
221 | =item OnlyWhenBuilt |
222 | |
8af2c2b0 |
223 | Only attributes that have been built (i.e., where the predicate returns |
76218b46 |
224 | 'true') will be serialized. This avoids any potentially expensive computations. |
225 | |
226 | See L<MooseX::Storage::Traits::OnlyWhenBuilt> for details. |
227 | |
228 | =back |
229 | |
1390c23d |
230 | =head2 How we serialize |
231 | |
ec725183 |
232 | There are always limits to any serialization framework, there are just |
233 | some things which are really difficult to serialize properly and some |
1390c23d |
234 | things which cannot be serialized at all. |
235 | |
236 | =head2 What can be serialized? |
237 | |
ec725183 |
238 | Currently only numbers, string, ARRAY refs, HASH refs and other |
239 | MooseX::Storage enabled objects are supported. |
1390c23d |
240 | |
ec725183 |
241 | With Array and Hash references the first level down is inspected and |
242 | any objects found are serialized/deserialized for you. We do not do |
8af2c2b0 |
243 | this recursively by default, however this feature may become an |
1390c23d |
244 | option eventually. |
245 | |
8af2c2b0 |
246 | =for stopwords subtypes |
247 | |
ec725183 |
248 | The specific serialize/deserialize routine is determined by the |
249 | Moose type constraint a specific attribute has. In most cases subtypes |
250 | of the supported types are handled correctly, and there is a facility |
1390c23d |
251 | for adding handlers for custom types as well. This will get documented |
252 | eventually, but it is currently still in development. |
253 | |
254 | =head2 What can not be serialized? |
255 | |
ec725183 |
256 | We do not support CODE references yet, but this support might be added |
257 | in using B::Deparse or some other deep magic. |
1390c23d |
258 | |
ec725183 |
259 | Scalar refs are not supported, mostly because there is no way to know |
260 | if the value being referenced will be there when the object is inflated. |
261 | I highly doubt will be ever support this in a general sense, but it |
1390c23d |
262 | would be possible to add this yourself for a small specific case. |
263 | |
ec725183 |
264 | Circular references are specifically disallowed, however if you break |
1390c23d |
265 | the cycles yourself then re-assemble them later you can get around this. |
ec725183 |
266 | The reason we disallow circular refs is because they are not always supported |
267 | in all formats we use, and they tend to be very tricky to do for all |
268 | possible cases. It is almost always something you want to have tight control |
1390c23d |
269 | over anyway. |
270 | |
271 | =head1 CAVEAT |
272 | |
f105066e |
273 | This is B<not> a persistence framework; changes to your object after |
ec725183 |
274 | you load or store it will not be reflected in the stored class. |
1390c23d |
275 | |
276 | =head1 EXPORTS |
277 | |
278 | =over 4 |
279 | |
280 | =item B<Storage (%options)> |
281 | |
f105066e |
282 | This module will export the C<Storage> method and can be used to |
ec725183 |
283 | load a specific set of MooseX::Storage roles to implement a specific |
284 | combination of features. It is meant to make things easier, but it |
285 | is by no means the only way. You can still compose your roles by |
1390c23d |
286 | hand if you like. |
287 | |
25697231 |
288 | By default, options are assumed to be short forms. For example, this: |
289 | |
290 | Storage(format => 'JSON'); |
291 | |
292 | ...will result in looking for MooseX::Storage::Format::JSON. To use a role |
293 | that is not under the default namespace prefix, start with an equal sign: |
294 | |
295 | Storage(format => '=My::Private::JSONFormat'); |
296 | |
8af2c2b0 |
297 | =for stopwords parameterized |
298 | |
25697231 |
299 | To use a parameterized role (for which, see L<MooseX::Role::Parameterized>) you |
300 | can pass an arrayref of the role name (in short or long form, as above) and its |
301 | parameters: |
302 | |
303 | Storage(format => [ JSONpm => { json_opts => { pretty => 1 } } ]); |
304 | |
1390c23d |
305 | =back |
306 | |
ec9c1923 |
307 | =head1 METHODS |
308 | |
309 | =over 4 |
310 | |
311 | =item B<import> |
312 | |
313 | =back |
314 | |
315 | =head2 Introspection |
316 | |
317 | =over 4 |
318 | |
319 | =item B<meta> |
320 | |
321 | =back |
322 | |
8af2c2b0 |
323 | =for stopwords TODO |
324 | |
1390c23d |
325 | =head1 TODO |
326 | |
ec725183 |
327 | This module needs docs and probably a Cookbook of some kind as well. |
7b428d1f |
328 | This is an early release, so that is my excuse for now :) |
1390c23d |
329 | |
ec725183 |
330 | For the time being, please read the tests and feel free to email me |
331 | if you have any questions. This module can also be discussed on IRC |
1390c23d |
332 | in the #moose channel on irc.perl.org. |
333 | |
ec9c1923 |
334 | =head1 BUGS |
335 | |
ec725183 |
336 | All complex software has bugs lurking in it, and this module is no |
ec9c1923 |
337 | exception. If you find a bug please either email me, or add the bug |
338 | to cpan-RT. |
339 | |
340 | =head1 AUTHOR |
341 | |
342 | Chris Prather E<lt>chris.prather@iinteractive.comE<gt> |
343 | |
344 | Stevan Little E<lt>stevan.little@iinteractive.comE<gt> |
345 | |
6c9f2c85 |
346 | Yuval Kogman E<lt>yuval.kogman@iinteractive.comE<gt> |
347 | |
ec9c1923 |
348 | =head1 COPYRIGHT AND LICENSE |
349 | |
1f3074ea |
350 | Copyright 2007-2008 by Infinity Interactive, Inc. |
ec9c1923 |
351 | |
352 | L<http://www.iinteractive.com> |
353 | |
354 | This library is free software; you can redistribute it and/or modify |
355 | it under the same terms as Perl itself. |
e9739624 |
356 | |
357 | =cut |
8af2c2b0 |
358 | |
359 | |