File Coverage

blib/lib/Rose/DB/Object/Util.pm
Criterion Covered Total %
statement 21 86 24.4
branch 0 28 0.0
condition 0 15 0.0
subroutine 8 29 27.5
pod 16 24 66.6
total 45 182 24.7


line stmt bran cond sub pod time code
1             package Rose::DB::Object::Util;
2              
3 63     63   210692 use strict;
  63         175  
  63         1866  
4              
5 63     63   336 use Carp;
  63         176  
  63         3465  
6              
7 63     63   39399 use Rose::DB::Object::Helpers();
  63         241  
  63         2203  
8              
9             use Rose::DB::Object::Constants
10 63         60681 qw(PRIVATE_PREFIX STATE_IN_DB STATE_LOADING STATE_SAVING MODIFIED_COLUMNS
11 63     63   440 ON_SAVE_ATTR_NAME);
  63         146  
12              
13             require Exporter;
14             our @ISA = qw(Exporter);
15              
16             our @EXPORT_OK =
17             qw(is_in_db is_loading is_saving
18             set_state_in_db set_state_loading set_state_saving
19             unset_state_in_db unset_state_loading unset_state_saving
20             row_id column_value_formatted_key column_value_is_inflated_key
21             column_value_formatted_key_for_db
22             lazy_column_values_loaded_key modified_column_names has_modified_columns
23             has_modified_children has_loaded_related set_column_value_modified
24             unset_column_value_modified get_column_value_modified
25             post_save_set_related_objects_code post_save_add_related_objects_code
26             pre_save_set_foreign_object_code);
27              
28             our %EXPORT_TAGS =
29             (
30             all => \@EXPORT_OK,
31             get_state => [ qw(is_in_db is_loading is_saving) ],
32             set_state => [ qw(set_state_in_db set_state_loading set_state_saving) ],
33             unset_state => [ qw(unset_state_in_db unset_state_loading unset_state_saving) ],
34             columns => [ qw(set_column_value_modified get_column_value_modified
35             unset_column_value_modified modified_column_names
36             has_modified_columns) ],
37             children => [ qw(has_modified_children has_loaded_related) ],
38             on_save_code => [ qw(post_save_set_related_objects_code
39             post_save_add_related_objects_code
40             pre_save_set_foreign_object_code) ],
41             );
42              
43             $EXPORT_TAGS{'state'} = [ map { @$_ } @EXPORT_TAGS{qw(get_state set_state unset_state)} ];
44              
45             our $VERSION = '0.772';
46              
47 0     0 1 0 sub is_in_db { shift->{STATE_IN_DB()} }
48 0     0 1 0 sub is_loading { shift->{STATE_LOADING()} }
49 0     0 1 0 sub is_saving { shift->{STATE_SAVING()} }
50              
51 0     0 1 0 sub set_state_in_db { shift->{STATE_IN_DB()} = 1 }
52 0     0 1 0 sub set_state_loading { shift->{STATE_LOADING()} = 1 }
53 0     0 1 0 sub set_state_saving { shift->{STATE_SAVING()} = 1 }
54              
55 0     0 1 0 sub unset_state_in_db { shift->{STATE_IN_DB()} = 0 }
56 0     0 1 0 sub unset_state_loading { shift->{STATE_LOADING()} = 0 }
57 0     0 1 0 sub unset_state_saving { shift->{STATE_SAVING()} = 0 }
58              
59             sub get_column_value_modified
60             {
61 0     0 1 0 my($object, $name) = (shift, shift);
62 0         0 return $object->{MODIFIED_COLUMNS()}{$name};
63             }
64              
65             sub set_column_value_modified
66             {
67 0     0 1 0 my($object, $name) = (shift, shift);
68 0         0 my $key = column_value_formatted_key_for_db($object->meta->column($name)->hash_key, $object->db);
69 0         0 delete $object->{$key};
70 0         0 return $object->{MODIFIED_COLUMNS()}{$name} = 1;
71             }
72              
73             sub unset_column_value_modified
74             {
75 0     0 1 0 my($object, $name) = (shift, shift);
76 0         0 return delete $object->{MODIFIED_COLUMNS()}{$name};
77             }
78              
79             sub modified_column_names
80             {
81 0 0   0 1 0 keys(%{shift->{MODIFIED_COLUMNS()} || {}});
  0         0  
82             }
83              
84             sub has_modified_columns
85             {
86 0 0 0 0 1 0 if(@_ > 1 && !$_[1])
87             {
88 0         0 shift->{MODIFIED_COLUMNS()} = {};
89             }
90              
91 0 0       0 scalar %{shift->{MODIFIED_COLUMNS()} || {}}
  0         0  
92             }
93              
94             sub has_loaded_related
95             {
96 0 0   0 1 0 if(@_ == 2) # $object, $name
97             {
98 0         0 return Rose::DB::Object::Helpers::has_loaded_related(@_);
99             }
100              
101 0         0 my %args = @_;
102 0 0       0 my $object = delete $args{'object'} or croak "Missing object parameter";
103              
104 0         0 Rose::DB::Object::Helpers::has_loaded_related($object, %args);
105             }
106              
107             sub has_modified_children
108             {
109 0     0 1 0 my($self) = shift;
110              
111 0         0 my $meta = $self->meta;
112              
113 0         0 foreach my $fk ($meta->foreign_keys)
114             {
115 0   0     0 my $foreign_object = $fk->object_has_foreign_object($self) || next;
116              
117 0 0 0     0 if(has_modified_columns($foreign_object) ||
118             has_modified_children($foreign_object))
119             {
120 0         0 return 1;
121             }
122             }
123              
124 0         0 foreach my $rel ($meta->relationships)
125             {
126 0   0     0 my $related_objects = $rel->object_has_related_objects($self) || next;
127              
128 0         0 foreach my $rel_object (@$related_objects)
129             {
130 0 0 0     0 if(has_modified_columns($rel_object) ||
131             has_modified_children($rel_object))
132             {
133 0         0 return 1;
134             }
135             }
136             }
137              
138 0         0 return 0;
139             }
140              
141             # XXX: A value that is unlikely to exist in a primary key column value
142 63     63   554 use constant PK_JOIN => "\0\2,\3\0";
  63         166  
  63         45267  
143              
144             sub row_id
145             {
146 0     0 0 0 my($object) = shift;
147              
148 0 0       0 my $meta = $object->meta or croak "$object has no meta attribute";
149              
150             return
151             join(PK_JOIN,
152 0         0 map { $object->$_() }
153 0         0 map { $meta->column_accessor_method_name($_) }
  0         0  
154             $meta->primary_key_column_names);
155             }
156              
157             sub column_value_formatted_key
158             {
159 342     342 0 596 my($key) = shift;
160 342         1046 return PRIVATE_PREFIX . "_${key}_formatted";
161             }
162              
163             sub column_value_formatted_key_for_db
164             {
165 0     0 0 0 my($key, $db) = @_;
166 0   0     0 return join($;, column_value_formatted_key($key), $db->driver || 'unknown');
167             }
168              
169             sub column_value_is_inflated_key
170             {
171 297     297 0 474 my($key) = shift;
172 297         712 return PRIVATE_PREFIX . "_${key}_is_inflated";
173             }
174              
175             sub lazy_column_values_loaded_key
176             {
177 183     183 0 654 my($key) = shift;
178 183         251537 return PRIVATE_PREFIX . "_lazy_loaded";
179             }
180              
181             sub post_save_set_related_objects_code
182             {
183 0     0 0   my($object, $rel_name, $code) = @_;
184              
185 0 0         if(@_ > 2)
186             {
187 0 0         if(defined $code)
188             {
189 0           return $object->{ON_SAVE_ATTR_NAME()}{'post'}{'rel'}{$rel_name}{'set'} = $code;
190             }
191             else
192             {
193 0           return delete $object->{ON_SAVE_ATTR_NAME()}{'post'}{'rel'}{$rel_name}{'set'};
194             }
195             }
196              
197 0           return $object->{ON_SAVE_ATTR_NAME()}{'post'}{'rel'}{$rel_name}{'set'};
198             }
199              
200             sub post_save_add_related_objects_code
201             {
202 0     0 0   my($object, $rel_name, $code) = @_;
203              
204 0 0         if(@_ > 2)
205             {
206 0 0         if(defined $code)
207             {
208 0           return $object->{ON_SAVE_ATTR_NAME()}{'post'}{'rel'}{$rel_name}{'add'} = $code;
209             }
210             else
211             {
212 0           return delete $object->{ON_SAVE_ATTR_NAME()}{'post'}{'rel'}{$rel_name}{'add'};
213             }
214             }
215              
216 0           return $object->{ON_SAVE_ATTR_NAME()}{'post'}{'rel'}{$rel_name}{'add'};
217             }
218              
219             sub pre_save_set_foreign_object_code
220             {
221 0     0 0   my($object, $fk_name, $code) = @_;
222              
223 0 0         if(@_ > 2)
224             {
225 0 0         if(defined $code)
226             {
227 0           return $object->{ON_SAVE_ATTR_NAME()}{'pre'}{'fk'}{$fk_name}{'set'} = $code;
228             }
229             else
230             {
231 0           return delete $object->{ON_SAVE_ATTR_NAME()}{'pre'}{'fk'}{$fk_name}{'set'};
232             }
233             }
234              
235 0           return $object->{ON_SAVE_ATTR_NAME()}{'pre'}{'fk'}{$fk_name}{'set'};
236             }
237              
238             1;
239              
240             __END__
241              
242             =head1 NAME
243              
244             Rose::DB::Object::Util - Utility functions for use in Rose::DB::Object subclasses and method makers.
245              
246             =head1 SYNOPSIS
247              
248             package MyDBObject;
249              
250             use Rose::DB::Object::Util qw(:all);
251              
252             use Rose::DB::Object;
253             our @ISA = qw(Rose::DB::Object);
254             ...
255             sub whatever
256             {
257             my($self) = shift;
258             ...
259             if(is_loading($self))
260             {
261             ...
262             set_state_in_db($self);
263             }
264             ...
265             }
266              
267             =head1 DESCRIPTION
268              
269             L<Rose::DB::Object::Util> provides functions that are useful for developers who are subclassing L<Rose::DB::Object> or otherwise extending or modifying its behavior.
270              
271             L<Rose::DB::Object>s have some awareness of their current situation. Certain optimizations rely on this awareness. For example, when loading column values directly from the database, there's no reason to validate the format of the data or immediately "inflate" the values. The L<is_loading|/is_loading> function will tell you when these steps can safely be skipped.
272              
273             Similarly, it may be useful to set these state characteristics in your code. The C<set_sate_*> functions provide that ability.
274              
275             =head1 EXPORTS
276              
277             C<Rose::DB::Object::Util> does not export any function names by default.
278              
279             The 'get_state' tag:
280              
281             use Rose::DB::Object::Util qw(:get_state);
282              
283             will cause the following function names to be imported:
284              
285             is_in_db()
286             is_loading()
287             is_saving()
288              
289             The 'set_state' tag:
290              
291             use Rose::DB::Object::Util qw(:set_state);
292              
293             will cause the following function names to be imported:
294              
295             set_state_in_db()
296             set_state_loading()
297             set_state_saving()
298              
299             The 'unset_state' tag:
300              
301             use Rose::DB::Object::Util qw(:unset_state);
302              
303             will cause the following function names to be imported:
304              
305             unset_state_in_db()
306             unset_state_loading()
307             unset_state_saving()
308              
309             the 'state' tag:
310              
311             use Rose::DB::Object::Util qw(:unset_state);
312              
313             will cause the following function names to be imported:
314              
315             is_in_db()
316             is_loading()
317             is_saving()
318             set_state_in_db()
319             set_state_loading()
320             set_state_saving()
321             unset_state_in_db()
322             unset_state_loading()
323             unset_state_saving()
324              
325             The 'columns' tag:
326              
327             use Rose::DB::Object::Util qw(:columns);
328              
329             will cause the following function names to be imported:
330              
331             get_column_value_modified()
332             set_column_value_modified()
333             unset_column_value_modified()
334             modified_column_names()
335             has_modified_columns()
336              
337             The 'children' tag:
338              
339             use Rose::DB::Object::Util qw(:children);
340              
341             will cause the following function names to be imported:
342              
343             has_loaded_related()
344             has_modified_children()
345              
346             The 'all' tag:
347              
348             use Rose::DB::Object::Util qw(:all);
349              
350             will cause the following function names to be imported:
351              
352             is_in_db()
353             is_loading()
354             is_saving()
355              
356             set_state_in_db()
357             set_state_loading()
358             set_state_saving()
359              
360             unset_state_in_db()
361             unset_state_loading()
362             unset_state_saving()
363              
364             get_column_value_modified()
365             set_column_value_modified()
366             unset_column_value_modified()
367             modified_column_names()
368             has_modified_columns()
369              
370             has_loaded_related()
371             has_modified_children()
372              
373             =head1 FUNCTIONS
374              
375             =over 4
376              
377             =item B<get_column_value_modified OBJECT, COLUMN>
378              
379             Returns true if the column named COLUMN in OBJECT is modified, false otherwise.
380              
381             =item B<has_loaded_related [ OBJECT, NAME | PARAMS ]>
382              
383             Given an OBJECT and a foreign key or relationship name, return true if one or more related objects have been loaded into OBJECT, false otherwise.
384              
385             If the name is passed as a plain string NAME, then a foreign key with that name is looked up. If no such foreign key exists, then a relationship with that name is looked up. If no such relationship or foreign key exists, a fatal error will occur. Example:
386              
387             has_loaded_related($object, 'bar');
388              
389             It's generally not a good idea to add a foreign key and a relationship with the same name, but it is technically possible. To specify the domain of the name, pass the name as the value of a C<foreign_key> or C<relationship> parameter. You must also pass the object as the value of the C<object> parameter. Example:
390              
391             has_loaded_related(object => $object, foreign_key => 'bar');
392             has_loaded_related(object => $object, relationship => 'bar');
393              
394             =item B<has_modified_children OBJECT>
395              
396             Returns true if OBJECT L<has_loaded_related|/has_loaded_related> objects, at least one of which L<has_modified_columns|/has_modified_columns> or L<has_modified_children|/has_modified_children>, false otherwise.
397              
398             =item B<has_modified_columns OBJECT>
399              
400             Returns true if OBJECT has any modified columns, false otherwise.
401              
402             =item B<is_in_db OBJECT>
403              
404             Given the L<Rose::DB::Object>-derived object OBJECT, returns true if the object was L<load|Rose::DB::Object/load>ed from, or has ever been L<save|Rose::DB::Object/save>d into, the database, or false if it has not.
405              
406             =item B<is_loading OBJECT>
407              
408             Given the L<Rose::DB::Object>-derived object OBJECT, returns true if the object is currently being L<load|Rose::DB::Object/load>ed, false otherwise.
409              
410             =item B<is_saving OBJECT>
411              
412             Given the L<Rose::DB::Object>-derived object OBJECT, returns true if the object is currently being L<save|Rose::DB::Object/save>d, false otherwise.
413              
414             =item B<modified_column_names OBJECT>
415              
416             Returns a list containing the names of all the modified columns in OBJECT.
417              
418             =item B<set_column_value_modified OBJECT, COLUMN>
419              
420             Mark the column named COLUMN in OBJECT as modified.
421              
422             =item B<unset_column_value_modified OBJECT, COLUMN>
423              
424             Clear the modified mark, if any, on the column named COLUMN in OBJECT.
425              
426             =item B<set_state_in_db OBJECT>
427              
428             Mark the L<Rose::DB::Object>-derived object OBJECT as having been L<load|Rose::DB::Object/load>ed from or L<save|Rose::DB::Object/save>d into the database at some point in the past.
429              
430             =item B<set_state_loading OBJECT>
431              
432             Indicate that the L<Rose::DB::Object>-derived object OBJECT is currently being L<load|Rose::DB::Object/load>ed from the database.
433              
434             =item B<set_state_saving OBJECT>
435              
436             Indicate that the L<Rose::DB::Object>-derived object OBJECT is currently being L<save|Rose::DB::Object/save>d into the database.
437              
438             =item B<unset_state_in_db OBJECT>
439              
440             Mark the L<Rose::DB::Object>-derived object OBJECT as B<not> having been L<load|Rose::DB::Object/load>ed from or L<save|Rose::DB::Object/save>d into the database at some point in the past.
441              
442             =item B<unset_state_loading OBJECT>
443              
444             Indicate that the L<Rose::DB::Object>-derived object OBJECT is B<not> currently being L<load|Rose::DB::Object/load>ed from the database.
445              
446             =item B<unset_state_saving OBJECT>
447              
448             Indicate that the L<Rose::DB::Object>-derived object OBJECT is B<not> currently being L<save|Rose::DB::Object/save>d into the database.
449              
450             =back
451              
452             =head1 AUTHOR
453              
454             John C. Siracusa (siracusa@gmail.com)
455              
456             =head1 LICENSE
457              
458             Copyright (c) 2010 by John C. Siracusa. All rights reserved. This program is
459             free software; you can redistribute it and/or modify it under the same terms
460             as Perl itself.