1use warnings; 2use strict; 3 4package Jifty::Record; 5 6use Jifty::Config; 7use List::MoreUtils qw(); 8 9=head1 NAME 10 11Jifty::Record - Represents a Jifty object that lives in the database. 12 13=head1 DESCRIPTION 14 15C<Jifty::Record> is a kind of L<Jifty::Object> that has a database 16representation; that is, it is also a L<Jifty::DBI::Record> as well. 17 18=cut 19 20use base qw( Jifty::Object Jifty::DBI::Record); 21 22__PACKAGE__->mk_accessors('_is_readable'); 23 24sub _init { 25 my $self = shift; 26 my %args = (@_); 27 $self->_get_current_user(%args); 28 29 $self->SUPER::_init(@_); 30} 31 32=head1 METHODS 33 34=cut 35 36=head2 create PARAMHASH 37 38C<create> can be called as either a class method or an object method. 39 40Takes an array of key-value pairs and inserts a new row into the 41database representing this object. 42 43Overrides L<Jifty::DBI::Record> in these ways: 44 45=over 4 46 47=item Remove C<id> values unless they are truly numeric 48 49=item Automatically load by id after create 50 51=item actually stop creating the new record if a field fails to validate. 52 53=back 54 55=cut 56 57sub create { 58 my $class = shift; 59 my $self; 60 if ( ref($class) ) { 61 ( $self, $class ) = ( $class, undef ); 62 } else { 63 $self = $class->new(); 64 } 65 66 my %attribs = @_; 67 68 unless ( $self->check_create_rights(@_) ) { 69 $self->log->error( $self->current_user->id . " tried to create a ", 70 ref $self, " without permission" ); 71 wantarray ? return ( 0, _('Permission denied') ) : return (0); 72 } 73 74 foreach my $key ( keys %attribs ) { 75 $attribs{$key} = $self->run_canonicalization_for_column( 76 column => $key, 77 value => $attribs{$key}, 78 extra => [\%attribs, { for => 'create' }], 79 ); 80 } 81 foreach my $key ( keys %attribs ) { 82 my $attr = $attribs{$key}; 83 my ( $val, $msg ) = $self->run_validation_for_column( 84 column => $key, 85 value => $attribs{$key}, 86 extra => [\%attribs, { for => 'create' }], 87 ); 88 if ( not $val ) { 89 $self->log->error("There was a validation error for $key"); 90 if ($class) { 91 return ($self); 92 } else { 93 return ( $val, $msg ); 94 } 95 } 96 97 # remove blank values. We'd rather have nulls 98 if ( exists $attribs{$key} 99 and ( !defined $attr || ( not ref($attr) and $attr eq '' ) ) ) 100 { 101 delete $attribs{$key}; 102 } 103 } 104 105 my $msg = $self->SUPER::create(%attribs); 106 if ( ref($msg) ) { 107 108 # It's a Class::ReturnValue 109 return $msg; 110 } 111 my ( $id, $status ) = $msg; 112 $self->load_by_cols( id => $id ) if ($id); 113 if ($class) { 114 return $self; 115 } else { 116 return wantarray ? ( $id, $status ) : $id; 117 } 118} 119 120=head2 id 121 122Returns the record id value. 123This routine short-circuits a much heavier call up through Jifty::DBI 124 125=cut 126 127sub _primary_key {'id'} 128sub id { $_[0]->{'values'}->{'id'} } 129 130=head2 load_or_create 131 132C<load_or_create> can be called as either a class method or an object method. 133It attempts to load a record with the named parameters passed in. If it 134can't do so, it creates a new record. 135 136=cut 137 138sub load_or_create { 139 my $class = shift; 140 my $self; 141 if ( ref($class) ) { 142 ( $self, $class ) = ( $class, undef ); 143 } else { 144 $self = $class->new(); 145 } 146 147 my %args = (@_); 148 149 my ( $id, $msg ) = $self->load_by_cols(%args); 150 unless ( $self->id ) { 151 return $self->create(%args); 152 } 153 154 return ( $id, $msg ); 155} 156 157=head2 as_create_action PARAMHASH 158 159Returns the L<Jifty::Action::Record::Create> action for this model 160class. 161 162The PARAMHASH allows you to add additional parameters to pass to 163L<Jifty::Web/new_action>. 164 165=cut 166 167sub _action_from_record { 168 my $self = shift; 169 my $verb = shift; 170 my $class = ref $self || $self; 171 $class =~ s/::Model::/::Action::$verb/; 172 return $class; 173} 174 175sub as_create_action { 176 my $self = shift; 177 my $action_class = $self->_action_from_record('Create'); 178 return Jifty->web->new_action( class => $action_class, @_ ); 179} 180 181=head2 as_update_action PARAMHASH 182 183Returns the L<Jifty::Action::Record::Update> action for this model 184class. The current record is passed to the constructor. 185 186The PARAMHASH allows you to add additional parameters to pass to 187L<Jifty::Web/new_action>. 188 189=cut 190 191sub as_update_action { 192 my $self = shift; 193 my $action_class = $self->_action_from_record('Update'); 194 return Jifty->web->new_action( 195 class => $action_class, 196 record => $self, 197 @_, 198 ); 199} 200 201=head2 as_delete_action PARAMHASH 202 203Returns the L<Jifty::Action::Record::Delete> action for this model 204class. The current record is passed to the constructor. 205 206The PARAMHASH allows you to add additional parameters to pass to 207L<Jifty::Web/new_action>. 208 209=cut 210 211sub as_delete_action { 212 my $self = shift; 213 my $action_class = $self->_action_from_record('Delete'); 214 return Jifty->web->new_action( 215 class => $action_class, 216 record => $self, 217 @_, 218 ); 219} 220 221=head2 as_search_action PARAMHASH 222 223Returns the L<Jifty::Action::Record::Search> action for this model 224class. 225 226The PARAMHASH allows you to add additional parameters to pass to 227L<Jifty::Web/new_action>. 228 229=cut 230 231sub as_search_action { 232 my $self = shift; 233 my $action_class = $self->_action_from_record('Search'); 234 return Jifty->web->new_action( 235 class => $action_class, 236 @_, 237 ); 238} 239 240=head2 _guess_table_name 241 242Guesses a table name based on the class's last part. In addition to 243the work performed in L<Jifty::DBI::Record>, this method also prefixes 244the table name with the plugin table prefix, if the model belongs to a 245plugin. 246 247=cut 248 249sub _guess_table_name { 250 my $self = shift; 251 my $table = $self->SUPER::_guess_table_name; 252 253 # Add plugin table prefix if a plugin model 254 my $class = ref($self) ? ref($self) : $self; 255 my $app_plugin_root = Jifty->app_class({require => 0}, 'Plugin'); 256 if ( $class =~ /^(?:Jifty::Plugin::|$app_plugin_root)/ ) { 257 258 # Guess the plugin class name 259 my $plugin_class = $class; 260 $plugin_class =~ s/::Model::(.*)$//; 261 262 # Try to load that plugin's configuration 263 my ($plugin) = grep { ref $_ eq $plugin_class } Jifty->plugins; 264 265 # Add the prefix if found 266 if ( defined $plugin ) { 267 $table = $plugin->table_prefix . $table; 268 } 269 270 # Uh oh. Warn, but try to keep going. 271 else { 272 warn 273 "Model $class looks like a plugin model, but $plugin_class could not be found."; 274 } 275 } 276 277 return $table; 278} 279 280=head2 current_user_can RIGHT [ATTRIBUTES] 281 282Should return true if the current user (C<< $self->current_user >>) is 283allowed to do I<RIGHT>. Possible values for I<RIGHT> are: 284 285=over 286 287=item create 288 289Called just before an object's C<create> method is called, as well as 290before parameter validation. ATTRIBUTES is the attributes that 291the object is trying to be created with, as the attributes aren't on 292the object yet to be inspected. 293 294=item read 295 296Called before any attribute is accessed on the object. 297ATTRIBUTES is a hash with a single key C<column> and a single 298value, the name of the column being queried. 299 300=item update 301 302Called before any attribute is changed on the object. 303ATTRIBUTES is a hash of the arguments passed to _set. 304 305=item delete 306 307Called before the object is deleted. 308 309=back 310 311Models wishing to customize authorization checks should override this 312method. You can do so like this: 313 314 sub current_user_can { 315 my ($self, $right, %args) = @_; 316 317 # Make any custom checks that return 1 to allow or return 0 to deny... 318 319 # Fallback upon the default implementation to handle the 320 # SkipAccessControl configuration setting, superuser, bootstrap, 321 # delegation, and the before_access hook 322 return $self->SUPER::current_user_can($right, %args); 323 } 324 325If you are sure you don't want your model to fallback using the 326default implementation, you can replace the last line with whatever 327fallback policy required. 328 329=head3 Authorization steps 330 331The default implementation proceeds as follows: 332 333=over 334 335=item 1. 336 337If the C<SkipAccessControl> setting is set to a true value in the 338framework configuration section of F<etc/config.yml>, 339C<current_user_can> always returns true. 340 341=item 2. 342 343The method first attempts to call the C<before_access> hooks to check 344for any allow or denial. See L</The before_access hook>. 345 346=item 3. 347 348Next, the default implementation returns true if the current user is a 349superuser or a bootstrap user. 350 351=item 4. 352 353Then, if the model can perform delegation, usually by using 354L<Jifty::RightsFrom>, the access control decision is deferred to 355another object (via the C<delegate_current_user_can> subroutine). 356 357=item 5. 358 359Otherwise, it returns false. 360 361=back 362 363=head3 The before_access hook 364 365This implementation may make use of a trigger called C<before_access> 366to make the decision. A new handler can be added to the trigger point 367by calling C<add_handler>: 368 369 $record->add_trigger( 370 name => 'before_access', 371 code => \&before_access, 372 abortable => 1, 373 ); 374 375The C<before_access> handler will be passed the same arguments that 376were used to call C<current_user_can>, including the current record 377object, the operation being checked, and any arguments being passed to 378the operation. 379 380The C<before_access> handler should return one of three strings: 381C<'deny'>, C<'allow'>, or C<'ignore'>. The C<current_user_can> 382implementation reacts as follows to these results: 383 384=over 385 386=item 1. 387 388If a handler is abortable and aborts by returning a false value (such 389as C<undef>), C<current_user_can> returns false. 390 391=item 2. 392 393If any handler returns 'deny', C<current_user_can> returns false. 394 395=item 3. 396 397If any handler returns 'allow' and no handler returns 'deny', 398C<current_user_can> returns true. 399 400=item 4. 401 402In all other cases, the results of the handlers are ignored and 403C<current_user_can> proceeds to check using superuser, bootstrap, and 404delegation. 405 406=back 407 408=cut 409 410sub current_user_can { 411 my $self = shift; 412 my $right = shift; 413 414 # Turn off access control for the whole application 415 if ( Jifty->config->framework('SkipAccessControl') ) { 416 return 1; 417 } 418 419 my $hook_status = $self->call_trigger( before_access => $right, @_ ); 420 421 # If not aborted... 422 if ( defined $hook_status ) { 423 424 # Compile the handler results 425 my %results; 426 $results{ $_->[0] }++ for ( @{ $self->last_trigger_results } ); 427 428 # Deny always takes precedent 429 if ( $results{deny} ) { 430 return 0; 431 } 432 433 # Then allow... 434 elsif ( $results{allow} ) { 435 return 1; 436 } 437 438 # Otherwise, no instruction from the handlers, move along... 439 } 440 441 # Abort! Return false for safety if the hook exploded 442 else { 443 return 0; 444 } 445 446 447 Carp::confess "No current user" unless ( $self->current_user ); 448 if ( $self->current_user->is_bootstrap_user 449 or $self->current_user->is_superuser ) 450 { 451 return (1); 452 } 453 454 if ( $self->can('delegate_current_user_can') ) { 455 return $self->delegate_current_user_can( $right, @_ ); 456 } 457 458 unless ( $self->current_user->isa('Jifty::CurrentUser') ) { 459 $self->log->error( 460 "Hm. called to authenticate without a currentuser - " 461 . $self->current_user ); 462 return (0); 463 } 464 return (0); 465 466} 467 468=head2 check_create_rights ATTRIBUTES 469 470Internal helper to call L</current_user_can> with C<create>. 471 472=cut 473 474sub check_create_rights { return shift->current_user_can( 'create', @_ ) } 475 476=head2 check_read_rights 477 478Internal helper to call L</current_user_can> with C<read>. 479 480Passes C<column> as a named parameter for the column the user is checking rights on. 481 482=cut 483 484sub check_read_rights { 485 my $self = shift; 486 return (1) if $self->_is_readable; 487 return $self->current_user_can( 'read', column => shift ); 488} 489 490=head2 check_update_rights 491 492Internal helper to call L</current_user_can> with C<update>. 493 494=cut 495 496sub check_update_rights { return shift->current_user_can( 'update', @_ ) } 497 498=head2 check_delete_rights 499 500Internal helper to call L</current_user_can> with C<delete>. 501 502=cut 503 504sub check_delete_rights { return shift->current_user_can( 'delete', @_ ) } 505 506sub _set { 507 my $self = shift; 508 509 unless ( $self->check_update_rights(@_) ) { 510 return ( 0, _('Permission denied') ); 511 } 512 $self->SUPER::_set(@_); 513} 514 515sub _value { 516 my $self = shift; 517 my $column = shift; 518 519 unless ( $self->check_read_rights( $column => @_ ) ) { 520 return (undef); 521 } 522 my $value = $self->SUPER::_value( $column => @_ ); 523 return $value if ref $value or $self->column($column)->type eq 'blob'; 524 525 Encode::_utf8_on($value) if defined $value; 526 $value; 527} 528 529=head2 as_user CurrentUser 530 531Returns a copy of this object with the current_user set to the given 532current_user. This is a way to act on behalf of a particular user (perhaps the 533owner of the object) 534 535=cut 536 537sub as_user { 538 my $self = shift; 539 my $user = shift; 540 541 my $clone = $self->new( current_user => $user ); 542 $clone->load( $self->id ); 543 return $clone; 544} 545 546=head2 as_superuser 547 548Returns a copy of this object with the current_user set to the 549superuser. This is a convenient way to duck around ACLs if you have 550code that needs to for some reason or another. 551 552=cut 553 554sub as_superuser { 555 my $self = shift; 556 return $self->as_user( $self->current_user->superuser ); 557} 558 559=head2 delete PARAMHASH 560 561Overrides L<Jifty::DBI::Record> to check the delete ACL. 562 563=cut 564 565sub delete { 566 my $self = shift; 567 unless ( $self->check_delete_rights(@_) ) { 568 $self->log->logcluck("Permission denied"); 569 return ( 0, _('Permission denied') ); 570 } 571 $self->SUPER::delete(@_); 572} 573 574=head2 brief_description 575 576Display the friendly name of the record according to _brief_description. 577 578To change what this returns, override L<_brief_description> instead. 579 580=cut 581 582sub brief_description { 583 my $self = shift; 584 my $method = $self->_brief_description; 585 return $self->$method; 586} 587 588=head2 _brief_description 589 590When displaying a list of records, Jifty can display a friendly value rather 591than the column's unique id. Out of the box, Jifty always tries to display the 592'name' field from the record. If there is no 'name' field, Jifty falls back to 593the record id. 594 595You can override this method to return the name of a method on your record 596class which will return a nice short human readable description for this 597record. 598 599=cut 600 601sub _brief_description { 602 my $self = shift; 603 return 'name' if $self->can('name'); 604 return 'id'; 605} 606 607=head2 null_reference 608 609By default, L<Jifty::DBI::Record> returns C<undef> on non-existent 610related fields; Jifty prefers to get back an object with an undef id. 611 612=cut 613 614sub null_reference { 0 } 615 616=head2 _new_collection_args 617 618Overrides the default arguments which this collection passes to new 619collections, to pass the C<current_user>. 620 621=cut 622 623sub _new_collection_args { 624 my $self = shift; 625 return ( current_user => $self->current_user ); 626} 627 628=head2 _new_record_args 629 630Overrides the default arguments which this collection passes to new 631records, to pass the C<current_user>. 632 633=cut 634 635sub _new_record_args { 636 my $self = shift; 637 return ( current_user => $self->current_user ); 638} 639 640=head2 cache_key_prefix 641 642Returns a unique key for this application for the Memcached cache. 643This should be global within a given Jifty application instance. 644 645=cut 646 647sub cache_key_prefix { 648 Jifty->config->framework('Database')->{'Database'}; 649} 650 651sub _cache_config { 652 { 'cache_p' => 1, 653 'cache_for_sec' => 60, 654 }; 655} 656 657=head2 since 658 659By default, all models exist since C<undef>, the ur-time when the 660application was created. Please override it for your model class. 661 662=cut 663 664=head2 printable_table_schema 665 666When called, this method will generate the SQL schema for the current 667version of this class and return it as a scalar, suitable for printing 668or execution in your database's command line. 669 670=cut 671 672sub printable_table_schema { 673 my $class = shift; 674 675 my $schema_gen = $class->_make_schema(); 676 return $schema_gen->create_table_sql_text; 677} 678 679 680=head2 table_schema_statements 681 682When called, this method will generate the SQL schema statements 683for the current version of this class and return it as array. 684 685=cut 686 687sub table_schema_statements { 688 my $class = shift; 689 690 my $schema_gen = $class->_make_schema(); 691 return $schema_gen->create_table_sql_statements; 692} 693 694 695 696 697=head2 create_table_in_db 698 699When called, this method will generate the SQL schema for the current 700version of this class and insert it into the application's currently 701open database. 702 703=cut 704 705sub create_table_in_db { 706 my $class = shift; 707 708 # Run all CREATE commands 709 for my $statement ( $class->table_schema_statements ) { 710 my $ret = Jifty->handle->simple_query($statement); 711 $ret or die "error creating table $class: " . $ret->error_message; 712 } 713 714} 715 716=head2 drop_table_in_db 717 718When called, this method will generate the SQL to remove this model's 719table in the database and execute it in the application's currently 720open database. This method can destroy a lot of data. Be sure you 721know what you're doing. 722 723 724=cut 725 726sub drop_table_in_db { 727 my $self = shift; 728 my $ret = Jifty->handle->simple_query( 'DROP TABLE ' . $self->table ); 729 $ret or die "error removing table $self: " . $ret->error_message; 730} 731 732sub _make_schema { 733 my $class = shift; 734 735 require Jifty::DBI::SchemaGenerator; 736 my $schema_gen = Jifty::DBI::SchemaGenerator->new( Jifty->handle ) 737 or die "Can't make Jifty::DBI::SchemaGenerator"; 738 my $ret = $schema_gen->add_model( $class->new ); 739 $ret or die "couldn't add model $class: " . $ret->error_message; 740 741 return $schema_gen; 742} 743 744=head2 add_column_sql column_name 745 746Returns the SQL statement necessary to add C<column_name> to this 747class's representation in the database 748 749=cut 750 751sub add_column_sql { 752 my $self = shift; 753 my $column_name = shift; 754 755 my $col = $self->column($column_name); 756 my $definition = $self->_make_schema() 757 ->column_definition_sql( $self->table => $col->name ); 758 return "ALTER TABLE " . $self->table . " ADD COLUMN " . $definition; 759} 760 761=head2 add_column_in_db column_name 762 763Executes the SQL code generated by add_column_sql. Dies on failure. 764 765=cut 766 767sub add_column_in_db { 768 my $self = shift; 769 my $ret = Jifty->handle->simple_query( $self->add_column_sql(@_) ); 770 $ret 771 or die "error adding column " 772 . $_[0] 773 . " to $self: " 774 . $ret->error_message; 775 776} 777 778=head2 drop_column_sql column_name 779 780Returns the SQL statement necessary to remove C<column_name> from this 781class's representation in the database 782 783=cut 784 785sub drop_column_sql { 786 my $self = shift; 787 my $column_name = shift; 788 789 my $col = $self->column($column_name); 790 return "ALTER TABLE " . $self->table . " DROP COLUMN " . $col->name; 791} 792 793=head2 drop_column_in_db column_name 794 795Executes the SQL code generated by drop_column_sql. Dies on failure. 796 797=cut 798 799sub drop_column_in_db { 800 my $self = shift; 801 my $ret = Jifty->handle->simple_query( $self->drop_column_sql(@_) ); 802 $ret 803 or die "error dropping column " 804 . $_[0] 805 . " to $self: " 806 . $ret->error_message; 807 808} 809 810=head2 schema_version 811 812This method is used by L<Jifty::DBI::Record> to determine which schema 813version is in use. It returns the current database version stored in 814the configuration. 815 816Jifty's notion of the schema version is currently broken into two: 817 818=over 819 820=item 1. 821 822The Jifty version is the first. In the case of models defined by Jifty 823itself, these use the version found in C<$Jifty::VERSION>. 824 825=item 2. 826 827Any model defined by your application use the database version 828declared in the configuration. In F<etc/config.yml>, this is located 829at: 830 831 framework: 832 Database: 833 Version: 0.0.1 834 835=back 836 837A model is considered to be defined by Jifty if it the package name 838starts with "Jifty::". Otherwise, it is assumed to be an application 839model. 840 841=cut 842 843sub schema_version { 844 my $class = shift; 845 846 # Return the Jifty schema version 847 if ( $class =~ /^Jifty::Model::/ ) { 848 return $Jifty::VERSION; 849 } 850 851 # TODO need to consider Jifty plugin versions? 852 853 # Return the application schema version 854 else { 855 my $config = Jifty->config(); 856 return $config->framework('Database')->{'Version'}; 857 } 858} 859 860=head2 column_serialized_as 861 862 863 864=cut 865 866sub column_serialized_as { 867 my ($class, $column) = @_; 868 my $meta = $column->attributes->{serialized} or return; 869 $meta->{columns} ||= [$column->refers_to->default_serialized_as_columns] 870 if $column->refers_to; 871 return $meta; 872} 873 874=head2 default_serialized_as_columns 875 876=cut 877 878sub default_serialized_as_columns { 879 my $class = shift; 880 return (List::MoreUtils::uniq 'id', $class->_brief_description); 881} 882 883=head2 jifty_serialize_format 884 885This is used to create a hash reference of the object's values. Unlike 886Jifty::DBI::Record->as_hash, this won't transform refers_to columns into JDBI 887objects. Override this if you want to include calculated values (for use in, 888say, your REST interface) 889 890=cut 891 892sub jifty_serialize_format { 893 my $record = shift; 894 my %data; 895 896 # XXX: maybe just test ->virtual? 897 for my $column (grep { $_->readable } $record->columns ) { 898 next if UNIVERSAL::isa($column->refers_to, 899 'Jifty::DBI::Collection'); 900 next if $column->container; 901 my $name = $column->aliased_as || $column->name; 902 903 if ((my $refers_to = $column->refers_to) && 904 (my $serialize_meta = $record->column_serialized_as($column))) { 905 my $column_data = $record->$name(); 906 if ( $column_data && $column_data->id ) { 907 $name = $serialize_meta->{name} if $serialize_meta->{name}; 908 $data{$name} = { map { $_ => scalar $record->$name->$_ } @{$serialize_meta->{columns} } }; 909 } 910 else { 911 $data{$name} = undef; 912 } 913 } 914 else { 915 $data{$name} = Jifty::Util->stringify($record->_value($name)); 916 } 917 } 918 919 return \%data; 920} 921 922=head2 autogenerate_action 923 924Controls which of the L<Jifty::Action::Record> subclasses are 925automatically set up for this model; this subroutine is passed one of 926the strings C<Create>, C<Update>, C<Delete>, C<Search> or C<Execute>, and should 927return a true value if that action should be autogenerated. 928 929The default method returns 0 for all action classes if the model is 930marked as L</is_private>. It returns 0 for all actions that are not 931C<Search> if the model is marked as L</is_protected>; otherwise, it 932returns true. 933 934=cut 935 936sub autogenerate_action { 937 my $class = shift; 938 my($action) = @_; 939 940 return 0 if $class->is_private; 941 return 0 if $class->is_protected and $action ne "Search"; 942 943 return 1; 944} 945 946=head2 is_private 947 948Override this method to return true to not generate any actions for 949this model, and to hide it from REST introspection. 950 951=cut 952 953sub is_private { 0 } 954 955=head2 is_protected 956 957Override this method to return true to only generate Search actions 958for this model. 959 960=cut 961 962sub is_protected { return shift->is_private } 963 964=head2 enumerable 965 966Controls whether autogenerated actions with columns that refer to this 967class should attempt to provide a drop-down of possible values or not. 968This method will be called as a class method, and defaults to true. 969 970=cut 971 972sub enumerable { 1 } 973 9741; 975