1package Rose::DB::Object::Util; 2 3use strict; 4 5use Carp; 6 7use Rose::DB::Object::Helpers(); 8 9use Rose::DB::Object::Constants 10 qw(PRIVATE_PREFIX STATE_IN_DB STATE_LOADING STATE_SAVING MODIFIED_COLUMNS 11 ON_SAVE_ATTR_NAME); 12 13require Exporter; 14our @ISA = qw(Exporter); 15 16our @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 28our %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 45our $VERSION = '0.772'; 46 47sub is_in_db { shift->{STATE_IN_DB()} } 48sub is_loading { shift->{STATE_LOADING()} } 49sub is_saving { shift->{STATE_SAVING()} } 50 51sub set_state_in_db { shift->{STATE_IN_DB()} = 1 } 52sub set_state_loading { shift->{STATE_LOADING()} = 1 } 53sub set_state_saving { shift->{STATE_SAVING()} = 1 } 54 55sub unset_state_in_db { shift->{STATE_IN_DB()} = 0 } 56sub unset_state_loading { shift->{STATE_LOADING()} = 0 } 57sub unset_state_saving { shift->{STATE_SAVING()} = 0 } 58 59sub get_column_value_modified 60{ 61 my($object, $name) = (shift, shift); 62 return $object->{MODIFIED_COLUMNS()}{$name}; 63} 64 65sub set_column_value_modified 66{ 67 my($object, $name) = (shift, shift); 68 my $key = column_value_formatted_key_for_db($object->meta->column($name)->hash_key, $object->db); 69 delete $object->{$key}; 70 return $object->{MODIFIED_COLUMNS()}{$name} = 1; 71} 72 73sub unset_column_value_modified 74{ 75 my($object, $name) = (shift, shift); 76 return delete $object->{MODIFIED_COLUMNS()}{$name}; 77} 78 79sub modified_column_names 80{ 81 keys(%{shift->{MODIFIED_COLUMNS()} || {}}); 82} 83 84sub has_modified_columns 85{ 86 if(@_ > 1 && !$_[1]) 87 { 88 shift->{MODIFIED_COLUMNS()} = {}; 89 } 90 91 scalar %{shift->{MODIFIED_COLUMNS()} || {}} 92} 93 94sub has_loaded_related 95{ 96 if(@_ == 2) # $object, $name 97 { 98 return Rose::DB::Object::Helpers::has_loaded_related(@_); 99 } 100 101 my %args = @_; 102 my $object = delete $args{'object'} or croak "Missing object parameter"; 103 104 Rose::DB::Object::Helpers::has_loaded_related($object, %args); 105} 106 107sub has_modified_children 108{ 109 my($self) = shift; 110 111 my $meta = $self->meta; 112 113 foreach my $fk ($meta->foreign_keys) 114 { 115 my $foreign_object = $fk->object_has_foreign_object($self) || next; 116 117 if(has_modified_columns($foreign_object) || 118 has_modified_children($foreign_object)) 119 { 120 return 1; 121 } 122 } 123 124 foreach my $rel ($meta->relationships) 125 { 126 my $related_objects = $rel->object_has_related_objects($self) || next; 127 128 foreach my $rel_object (@$related_objects) 129 { 130 if(has_modified_columns($rel_object) || 131 has_modified_children($rel_object)) 132 { 133 return 1; 134 } 135 } 136 } 137 138 return 0; 139} 140 141# XXX: A value that is unlikely to exist in a primary key column value 142use constant PK_JOIN => "\0\2,\3\0"; 143 144sub row_id 145{ 146 my($object) = shift; 147 148 my $meta = $object->meta or croak "$object has no meta attribute"; 149 150 return 151 join(PK_JOIN, 152 map { $object->$_() } 153 map { $meta->column_accessor_method_name($_) } 154 $meta->primary_key_column_names); 155} 156 157sub column_value_formatted_key 158{ 159 my($key) = shift; 160 return PRIVATE_PREFIX . "_${key}_formatted"; 161} 162 163sub column_value_formatted_key_for_db 164{ 165 my($key, $db) = @_; 166 return join($;, column_value_formatted_key($key), $db->driver || 'unknown'); 167} 168 169sub column_value_is_inflated_key 170{ 171 my($key) = shift; 172 return PRIVATE_PREFIX . "_${key}_is_inflated"; 173} 174 175sub lazy_column_values_loaded_key 176{ 177 my($key) = shift; 178 return PRIVATE_PREFIX . "_lazy_loaded"; 179} 180 181sub post_save_set_related_objects_code 182{ 183 my($object, $rel_name, $code) = @_; 184 185 if(@_ > 2) 186 { 187 if(defined $code) 188 { 189 return $object->{ON_SAVE_ATTR_NAME()}{'post'}{'rel'}{$rel_name}{'set'} = $code; 190 } 191 else 192 { 193 return delete $object->{ON_SAVE_ATTR_NAME()}{'post'}{'rel'}{$rel_name}{'set'}; 194 } 195 } 196 197 return $object->{ON_SAVE_ATTR_NAME()}{'post'}{'rel'}{$rel_name}{'set'}; 198} 199 200sub post_save_add_related_objects_code 201{ 202 my($object, $rel_name, $code) = @_; 203 204 if(@_ > 2) 205 { 206 if(defined $code) 207 { 208 return $object->{ON_SAVE_ATTR_NAME()}{'post'}{'rel'}{$rel_name}{'add'} = $code; 209 } 210 else 211 { 212 return delete $object->{ON_SAVE_ATTR_NAME()}{'post'}{'rel'}{$rel_name}{'add'}; 213 } 214 } 215 216 return $object->{ON_SAVE_ATTR_NAME()}{'post'}{'rel'}{$rel_name}{'add'}; 217} 218 219sub pre_save_set_foreign_object_code 220{ 221 my($object, $fk_name, $code) = @_; 222 223 if(@_ > 2) 224 { 225 if(defined $code) 226 { 227 return $object->{ON_SAVE_ATTR_NAME()}{'pre'}{'fk'}{$fk_name}{'set'} = $code; 228 } 229 else 230 { 231 return delete $object->{ON_SAVE_ATTR_NAME()}{'pre'}{'fk'}{$fk_name}{'set'}; 232 } 233 } 234 235 return $object->{ON_SAVE_ATTR_NAME()}{'pre'}{'fk'}{$fk_name}{'set'}; 236} 237 2381; 239 240__END__ 241 242=head1 NAME 243 244Rose::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 269L<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 271L<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 273Similarly, 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 277C<Rose::DB::Object::Util> does not export any function names by default. 278 279The 'get_state' tag: 280 281 use Rose::DB::Object::Util qw(:get_state); 282 283will cause the following function names to be imported: 284 285 is_in_db() 286 is_loading() 287 is_saving() 288 289The 'set_state' tag: 290 291 use Rose::DB::Object::Util qw(:set_state); 292 293will cause the following function names to be imported: 294 295 set_state_in_db() 296 set_state_loading() 297 set_state_saving() 298 299The 'unset_state' tag: 300 301 use Rose::DB::Object::Util qw(:unset_state); 302 303will cause the following function names to be imported: 304 305 unset_state_in_db() 306 unset_state_loading() 307 unset_state_saving() 308 309the 'state' tag: 310 311 use Rose::DB::Object::Util qw(:unset_state); 312 313will 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 325The 'columns' tag: 326 327 use Rose::DB::Object::Util qw(:columns); 328 329will 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 337The 'children' tag: 338 339 use Rose::DB::Object::Util qw(:children); 340 341will cause the following function names to be imported: 342 343 has_loaded_related() 344 has_modified_children() 345 346The 'all' tag: 347 348 use Rose::DB::Object::Util qw(:all); 349 350will 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 379Returns true if the column named COLUMN in OBJECT is modified, false otherwise. 380 381=item B<has_loaded_related [ OBJECT, NAME | PARAMS ]> 382 383Given 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 385If 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 389It'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 396Returns 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 400Returns true if OBJECT has any modified columns, false otherwise. 401 402=item B<is_in_db OBJECT> 403 404Given 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 408Given 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 412Given 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 416Returns a list containing the names of all the modified columns in OBJECT. 417 418=item B<set_column_value_modified OBJECT, COLUMN> 419 420Mark the column named COLUMN in OBJECT as modified. 421 422=item B<unset_column_value_modified OBJECT, COLUMN> 423 424Clear the modified mark, if any, on the column named COLUMN in OBJECT. 425 426=item B<set_state_in_db OBJECT> 427 428Mark 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 432Indicate 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 436Indicate 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 440Mark 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 444Indicate 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 448Indicate 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 454John C. Siracusa (siracusa@gmail.com) 455 456=head1 LICENSE 457 458Copyright (c) 2010 by John C. Siracusa. All rights reserved. This program is 459free software; you can redistribute it and/or modify it under the same terms 460as Perl itself. 461