File Coverage

blib/lib/Collection.pm
Criterion Covered Total %
statement 100 130 76.9
branch 22 38 57.8
condition 3 5 60.0
subroutine 16 26 61.5
pod 9 12 75.0
total 150 211 71.0


line stmt bran cond sub pod time code
1             package Collection;
2              
3             #$Id$
4              
5             =head1 NAME
6              
7             Collection - CRUD framework
8              
9             =head1 SYNOPSIS
10              
11             package MyCollection;
12             use Collection;
13             @MyCollection::ISA = qw(Collection);
14              
15             =head1 DESCRIPTION
16              
17             A collection - sometimes called a container - is simply an object that groups multiple elements into a single unit. I are used to store, retrieve, manipulate, and communicate aggregate data.
18              
19             The primary advantages of a I framework are that it reduces programming effort by providing useful data structures and algorithms so you don't have to write them yourself.
20              
21              
22             The I framework consists of:
23              
24             =over 2
25              
26             =item * Wrapper Implementations - Add functionality, such as mirroring and lazy load, to other implementations.
27              
28             =item * Algorithms - methods that perform useful functions, such as caching.
29              
30             =back
31              
32             This module has a task - to be a base class for ather Collections.
33             You can inherit the methods B<_create>, B<_delete>, B<_fetch>, B<_store> and may be B<_prepare_record> for new source of data. As you see this is similar to B (Create - Read - Update- Delete).
34              
35             Sample:
36              
37             my $col = new MyCollection:: ;
38             #fetch objects or data by keys
39             my $data = $col->fetch(1,2,3,4,5);
40             #do something
41             foreach my $item ( values %$data) {
42             $_->attr->{inc} ++
43             }
44             #You can use "lazy" functionality
45             my $not_actualy_fetch = $col->get_lazy(6,7,8,9);
46             #store changed data or objects
47             $col->store;
48             #free memory
49             $col->release;
50              
51              
52             Sample from L:
53              
54             my $beers = new Collection::AutoSQL::
55             dbh => $dbh, #database connect
56             table => 'beers', #table name
57             field => 'bid', #key field (IDs), usually primary,autoincrement
58             cut_key => 1; #delete field 'bid' from readed records,
59            
60             my $heineken = $beers->fetch_one(1);
61             #SELECT * FROM beers WHERE bid in (1)
62              
63              
64             Sample from L:
65              
66             use Collection::Memcached;
67             use Cache::Memcached;
68             $memd = new Cache::Memcached {
69             'servers' => [ "127.0.0.1:11211" ],
70             'debug' => 0,
71             'compress_threshold' => 10_000,
72             };
73             my $collection = new Collection::Memcached:: $memd;
74             my $collection_prefix = new Collection::Memcached:: $memd, 'prefix';
75              
76             =head1 METHODS
77              
78             =cut
79              
80 4     4   2985 use strict;
  4         8  
  4         138  
81 4     4   23 use warnings;
  4         8  
  4         103  
82 4     4   27 use Carp;
  4         5  
  4         251  
83 4     4   22 use Data::Dumper;
  4         7  
  4         175  
84 4     4   1179 use Collection::Utl::ActiveRecord;
  4         7  
  4         157  
85 4     4   23 use Collection::Utl::Base;
  4         6  
  4         151  
86 4     4   2093 use Collection::Utl::LazyObject;
  4         9  
  4         5496  
87             @Collection::ISA = qw(Collection::Utl::Base);
88             $Collection::VERSION = '0.58';
89             attributes qw( _obj_cache _on_store _on_create _on_delete);
90              
91             sub _init {
92 4     4   6 my $self = shift;
93 4         12 my %arg = @_;
94 4         126 $self->_obj_cache( {} );
95 4         120 $self->_on_store( $arg{on_store} );
96 4         116 $self->_on_create( $arg{on_create} );
97 4         112 $self->_on_delete( $arg{on_delete} );
98 4         27 $self->SUPER::_init(@_);
99             }
100              
101             =head2 _store( {ID1 => [, ID2 => , ...]} )
102              
103             Method for store changed objects. Called with ref to hash :
104              
105             {
106             ID1 =>
107             [,ID2 => ,...]
108             }
109              
110             =cut
111              
112             sub _store {
113 0     0   0 my $pkg = ref $_[0];
114 0         0 croak "$pkg doesn't define an _store method";
115             }
116              
117             =head2 _fetch(ID1[, ID2, ...])
118              
119             Read data for given IDs. Must return reference to hash, where keys is IDs,
120             values is readed data.
121             For example:
122              
123             return {1=>[1..3],2=>[5..6]}
124            
125             =cut
126              
127             sub _fetch {
128 0     0   0 my $pkg = ref $_[0];
129 0         0 croak "$pkg doesn't define an _fetch method";
130             }
131              
132             =head2 _create()
133              
134             Create recods in data storage.
135              
136             Parametrs:
137              
138             user defined format
139              
140             Result:
141             Must return reference to hash, where keys is IDs, values is create records of data
142              
143             =cut
144              
145             sub _create {
146 0     0   0 my $pkg = ref $_[0];
147 0         0 croak "$pkg doesn't define an _create method";
148             }
149              
150             =head2 _delete(ID1[, ID2, ...])
151              
152             Delete records in data storage for given IDs.
153              
154             Parametrs:
155             array id IDs
156              
157             ID1, ID2, ...
158              
159             or array of refs to HASHes
160              
161             { id=>ID1 }, {id => ID2 }, ...
162            
163              
164             Format of parametrs depend method L
165              
166             =cut
167              
168             sub _delete {
169 0     0   0 my $pkg = ref $_[0];
170 0         0 croak "$pkg doesn't define an _delete method";
171             }
172              
173             =head2 _prepare_record( ID1, )
174              
175             Called before insert readed objects into collection.
176             Must return ref to data or object, which will insert to callection.
177              
178             =cut
179              
180             sub _prepare_record {
181 3     3   7 my ( $self, $key, $ref ) = @_;
182 3         6 return $ref;
183             }
184              
185             =head2 create()
186              
187             Public method for create objects.
188              
189              
190             =cut
191              
192             sub create {
193 7     7 1 12 my $self = shift;
194 7         175 my $coll_ref = $self->_obj_cache();
195 7         26 my $results = $self->_create(@_);
196 7         22 my $created = $self->fetch( keys %$results );
197 7 50       22 if (%$created) {
198 7 50       178 if ( ref( $self->_on_create ) eq 'CODE' ) {
199 0         0 $self->_on_create()->(%$created);
200             }
201             }
202 7         35 return $created
203             }
204              
205             =head2 fetch_one(ID1), get_one(ID1)
206              
207             Public methods. Fetch object from collection for given ID.
208             Return ref to objects or undef unless exists.
209              
210             =cut
211              
212             sub get_one {
213 0     0 1 0 my $self = shift;
214 0         0 return $self->fetch_one(@_);
215             }
216              
217             sub fetch_one {
218 10     10 1 1662 my ( $self, $id ) = @_;
219 10         12 my $res;
220 10 50       38 if ( my $item_refs = $self->fetch($id) ) {
221 10         17 $res = $item_refs->{$id};
222             }
223 10         48 return $res;
224             }
225              
226             =head2 fetch(ID1 [, ID2, ...]) , get(ID1 [, ID2, ...])
227              
228             Public methods. Fetch objects from collection for given IDs.
229             Return ref to HASH, where where keys is IDs, values is objects refs.
230              
231              
232             Parametrs:
233              
234              
235             =cut
236              
237             sub get {
238 0     0 1 0 my $self = shift;
239 0         0 return $self->fetch(@_)
240             }
241              
242             sub fetch {
243 27     27 1 60 my $self = shift;
244 27         40 my @ids = ();
245 27         711 my $coll_ref = $self->_obj_cache();
246 27         45 my @fetch = ();
247 27         32 my @exists = ();
248 27         32 my @fetched = ();
249 27         91 foreach my $id (@_) {
250             next
251 28 50       59 unless defined $id;
252              
253             #push nonexists or references to @fetch
254 28 100       60 if ( exists $coll_ref->{$id} ) {
255 6         11 push @exists, $id;
256 6         14 next;
257             }
258 22         56 push @fetch, $id;
259             }
260 27 100 66     126 if ( scalar(@fetch)
261             && ( my $results = $self->_fetch(@fetch) ) )
262             {
263 21         27 while ( my ( $key, $val ) = each %{$results} ) {
  34         113  
264 13         22 push @fetched, $key;
265             #filter already loaded
266 13 50       30 next if exists $coll_ref->{$key};
267              
268             #bless for loaded
269 13         51 my $ref = $self->_prepare_record( $key, $results->{$key} );
270 13 50       72 if ( ref($ref) ) {
271 13         25 $coll_ref->{$key} = $ref;
272              
273             #store loaded keys
274 13         34 push @exists, $key;
275             } else {
276 0         0 warn "Fail prepare for $key";
277             }
278             }
279             }
280 27         41 my %result = ();
281 27         52 foreach my $key (@exists, @fetched) {
282 32         76 $result{$key} = $coll_ref->{$key};
283             }
284 27         112 return \%result;
285             }
286              
287             =head2 release(ID1[, ID2, ...])
288              
289             Release from collection objects with IDs. Only delete given keys from collection or all if empty
290              
291             =cut
292              
293             sub release {
294 5     5 1 7 my $self = shift;
295 5         9 my (@ids) = @_;
296 5         152 my $coll_ref = $self->_obj_cache();
297 5 100       15 unless (@ids) {
298 1         4 my $res = [ keys %$coll_ref ];
299 1         3 undef %{$coll_ref};
  1         7  
300 1         4 return $res;
301             }
302             else {
303              
304             [
305 6         41 map {
306 4         7 delete $coll_ref->{ $_ };
307 6         19 $_
308             }
309             @ids
310             ];
311             } #else
312             }
313              
314             =head2 store([ID1,[ID2,...]])
315              
316             Call _store for changed objects.
317             Store all loaded objects without parameters:
318              
319             $simple_collection->store(); #store all changed
320              
321             or (for 1,2,6 IDs )
322              
323             $simple_collection->store(1,2,6);
324              
325             =cut
326              
327             sub store {
328 7     7 1 16 my $self = shift;
329 7         15 my @store_ids = @_;
330 7         170 my $coll_ref = $self->_obj_cache();
331 7 100       24 @store_ids = keys %$coll_ref unless @store_ids;
332 7         13 my %to_store;
333 7         13 foreach my $id (@store_ids) {
334 10         13 my $ref = $coll_ref->{$id};
335 10 50       26 next unless ref($ref);
336 10 100       32 if ( $self->is_record_changed($ref) ) {
337 6         24 $to_store{$id} = $ref;
338             }
339             }
340 7 100       77 if (%to_store) {
341 6 50       139 if ( ref( $self->_on_store ) eq 'CODE' ) {
342 0         0 $self->_on_store()->(%to_store );
343             }
344 6         26 $self->_store( \%to_store );
345             }
346             }
347              
348             =head2 delete(ID1[,ID2, ...])
349              
350             Release from collections and delete from storage (by calling L<_delete>)
351             objects ID1,ID2...
352              
353             $simple_collection->delete(1,5,84);
354              
355             =cut
356              
357             sub delete {
358 4     4 1 531 my $self = shift;
359 4         8 my (@ids) = @_;
360 4         18 $self->release(@ids);
361 4 50       89 if ( ref( $self->_on_delete ) eq 'CODE' ) {
362 0         0 $self->_on_delete()->(@ids);
363             }
364 4         17 $self->_delete(@ids);
365             }
366              
367             =head2 get_lazy(ID1)
368              
369             Method for base support lazy load objects from data storage.
370             Not really return lazy object.
371              
372             =cut
373              
374             sub get_lazy {
375 0     0 1 0 my ( $self, $id ) = @_;
376 0     0   0 return new Collection::Utl::LazyObject:: sub { $self->fetch_one($id) };
  0         0  
377             }
378              
379             sub is_record_changed {
380 10     10 0 13 my $self = shift;
381 10   50     22 my $record = shift || return;
382 10 50       25 if ( ref($record) eq 'HASH' ) {
383 10         42 return $record->{_changed};
384             =pod
385             if ( my $obj = tied $value ) {
386             push @changed, $id if $obj->_changed();
387             }
388             else {
389             push @changed, $id if $value->{_changed};
390             }
391             =cut
392              
393             }
394             else {
395 0 0         return $record->_changed() if UNIVERSAL::can($record, '_changed');
396 0 0         return $self->is_record_changed( $record->_get_attr ) if UNIVERSAL::can($record, '_get_attr');
397 0           carp "Can't check is record changed for class: " . ref($record);
398             }
399              
400             }
401              
402             sub get_changed_id {
403 0     0 0   my $self = shift;
404 0           my $coll_ref = $self->_obj_cache();
405 0           my @changed = ();
406 0           while ( my ( $id, $value ) = each %$coll_ref ) {
407 0 0         push @changed, $id if $self->is_record_changed($value)
408             }
409 0           return \@changed;
410             }
411              
412             sub list_ids {
413 0     0 0   my $pkg = ref $_[0];
414 0           croak "$pkg doesn't define an list_ids method";
415             }
416             1;
417             __END__