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