File Coverage

blib/lib/Pixie/Complicity.pm
Criterion Covered Total %
statement 16 30 53.3
branch 3 6 50.0
condition 0 3 0.0
subroutine 7 16 43.7
pod 0 10 0.0
total 26 65 40.0


line stmt bran cond sub pod time code
1             package Pixie::Complicity;
2              
3             $Pixie::Complicity::Loaded++;
4              
5             =head1 NAME
6              
7             Pixie::Complicity - making things play well with pixie
8              
9             =head1 DESCRIPTION
10              
11             Complicity: <>
12              
13             =head2 Rationale
14              
15             For many objects, Pixie can and does store the object transparently
16             with no assistance from the object's class. However, sometimes that's
17             just not the case; most commonly in the case of classes that are
18             implemented using XS, and which store their data off in some C
19             structure that's inaccessible from Perl. Getting at such information
20             without the complicity of the class in question would require Pixie to
21             be, near as dammit, telepathic. And that's not going to happen any
22             time soon.
23              
24             So, we provide a set of methods in UNIVERSAL, which are used by Pixie
25             in the process of storing and fetching objects. All you have to do is
26             override a few of them in the class in question. (Remember, even if
27             you're using a class from CPAN, the class's symbol table is always
28             open, so you can cheat and add the helper methods anyway, we've chosen
29             a method namespace (all methods begin with px_) which we hope doesn't
30             clash with any classes that are out there, in the wild.
31              
32             =head2 Example
33              
34             Consider the C class. It's a very lovely class,
35             implementing a delightfully fast set, with all the set operations
36             you'd expect. However, in order to get the speed, it's been
37             implemented using XS, and the Data::Dumper visible part of it is
38             simply a scalar reference. So, if we want to use Set::Object in our
39             project (and we do), we need to make it complicit with Pixie.
40              
41             So, first we make sure that Pixie knows it's storable:
42              
43             sub Set::Object::px_is_storable { 1 }
44              
45             Then we think about how we're going to render the thing storable. The
46             only important thing about a set, for our purposes, is the list of its
47             members (and what do you know, Set::Object provides a C
48             method to get at that). We'll press the 'memento' pattern into
49             use. The idea is that we create a memento object which will store
50             enough information about an object for that object to be recreated
51             later. We set up Set::Object's C method to create that
52             memento:
53              
54             sub Set::Object::px_freeze {
55             my $self = shift;
56             return bless [ $self->members ], 'Memento::Set::Object';
57             }
58              
59             Easy. For our next trick, we need to provide some way for a memento to
60             be turned back into an object. Pixie guarantees to call C on
61             every object that it retrieves from the data store, so, all we have to
62             do is implement an appropriate C method I
63             class>.
64              
65             sub Memento::Set::Object::px_thaw {
66             my $self = shift;
67             return Set::Object->new(@$self);
68             }
69              
70             And, as if by magic, Set::Objects can now be happily persisted within
71             your Pixie.
72              
73             =head2 The Complicit Methods
74              
75             Pixie puts a lot of methods into UNIVERSAL, because that's where the
76             behaviour makes the most sense. Some of these methods are useful to
77             override when you need to help Pixie out with object storage; others
78             are useful when you're writing the tools that I Pixie (but we
79             haven't actually added many of those yet) and still others are almost
80             certainly never going to be overridden by client code, but we'll
81             document them just in case. We start with the 'storage helper' methods
82             that you are most likely to override:
83              
84             =over 4
85              
86             =item px_is_storable
87              
88             A boolean method. By default, Pixie thinks only HASH and ARRAY based
89             objects are storable. If you have a class that you want to make
90             persistent, and it doesn't use one of these representations, then just
91             add C to your class definition.
92              
93             =item px_freeze
94              
95             Called by Pixie on every object that it stores, C transforms
96             an object into something a little more... storable. Remember,
97             px_freeze operates on the 'real' object, not a copy. Generally you
98             should create a new object in some memento class, dump the storable
99             state into it and return the memento. (Of course, if px_thaw just gets
100             rid of some cached computations, you might prefer to operate directly
101             on the object).
102              
103             =item px_thaw
104              
105             Called by Pixie on every object that it retrieves from the store. Use
106             this to turn memento objects back into the real thing.
107              
108             NB: If your C blesses an object into a seperate memento class
109             then remember to implement C in the memento class, not the
110             source class.
111              
112             =item px_is_immediate
113              
114             Another boolean. Used by Pixie to know whether an object in this class
115             should be immediately fetched in cases where Pixie would normally use
116             a Pixie::Proxy object to provide deferred loading. You generally want
117             to use this for objects that get accessed directly (you naughty
118             encapsulation violator you), because a Pixie::Proxy only fetches the
119             real thing when it notices a method call to the object.
120              
121             =item px_as_rawstruct
122              
123             Returns an unblessed HASH/ARRAY/SCALAR ref which is a shallow clone of
124             the object in question.
125              
126             Sometimes you can get away without having to write C and
127             C. Say you have a hash based object, and some of its keys
128             are the cached (large) results of an expensive computation, which can
129             be entirely derived from the 'real' instance variables. So, to strip
130             those out of the stored object, you could do the following:
131              
132             sub px_as_rawstruct {
133             my $self = shift;
134             {@$self{grep !/^cached_/, keys %$self}}
135             }
136              
137             Aren't hash slices lovely?
138              
139             =item px_empty_new
140              
141             Class method. Returns an empty object in the given class. The default
142             implementation of this does C<$class-Enew()>. We do this so that the
143             class can 'know about' its instance (some classes like to initialize
144             various static variables etc...) but, if your class's 'new' method
145             doesn't cope with an empty argument list, you could override this
146             method. (I'm thinking of adding a 'px_post_populating_hook' method,
147             which would be called after pixie has populated an object. Useful for
148             those classes whose 'new' methods require arguments and then call an
149             init method to set up stuff based on the instance variables...)
150              
151             =back
152              
153             =cut
154              
155             package UNIVERSAL;
156              
157 16     16   92 use Scalar::Util qw/ blessed weaken reftype isweak /;
  16         31  
  16         1204  
158 16     16   80 use strict;
  16         28  
  16         8497  
159              
160             sub px_is_storable {
161 27     27 0 55 my $self = shift;
162              
163 27         335 reftype($self) =~ /^(?:HASH|ARRAY)/;
164             }
165              
166             sub px_class {
167 0     0 0 0 my $proto = shift;
168 0   0     0 return ref($proto) || $proto
169             }
170              
171 0     0 0 0 sub px_oid { $_[0]->PIXIE::oid };
172              
173 25     25 0 77 sub px_freeze { shift }
174 0     0 0 0 sub px_thaw { shift }
175 0     0 0 0 sub px_is_immediate { }
176 0     0 0 0 sub px_in_rootset { 1 }
177              
178             sub px_as_rawstruct {
179 28     28 0 63 my $self = shift;
180 28         98 my $type = reftype($self);
181              
182 28 100       120 if ($type eq 'HASH') {
    50          
    0          
183 25         374 return { %$self };
184             }
185             elsif ($type eq 'ARRAY') {
186 3         30 return [ @$self ];
187             }
188             elsif ($type eq 'SCALAR') {
189 0         0 my $scalar = $$self;
190 0         0 return \$scalar;
191             }
192             }
193              
194             sub px_empty_new {
195 0     0 0 0 my $proto = shift;
196 0         0 $proto->new;
197             }
198              
199             sub px_do_final_restoration {
200 0     0 0 0 my $self = shift;
201 0         0 Pixie->get_the_current_pixie->make_new_object($self, ref($self));
202             }
203              
204              
205             # 'Internal' methods
206              
207              
208 0     0   0 sub _px_extraction_freeze { Pixie->get_the_current_pixie->extraction_freeze(@_) }
209 0     0   0 sub _px_extraction_thaw { Pixie->get_the_current_pixie->extraction_thaw(@_) }
210 30     30   1941 sub _px_insertion_freeze { Pixie->get_the_current_pixie->insertion_freeze(@_) }
211 17     17   102 sub _px_insertion_thaw { Pixie->get_the_current_pixie->insertion_thaw(@_) }
212              
213             1;