root/branches/release-34/lib/MT/Object.pm @ 1873

Revision 1873, 60.1 kB (checked in by bchoate, 20 months ago)

Applied patches from Ogawa-san to add an optimized 'exist' method for testing for existing rows. BugId:69661

  • Property svn:keywords set to Author Date Id Revision
Line 
1# Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd.
2# This program is distributed under the terms of the
3# GNU General Public License, version 2.
4#
5# $Id$
6
7package MT::Object;
8
9use strict;
10use base qw( Data::ObjectDriver::BaseObject MT::ErrorHandler );
11
12use MT;
13use MT::Util qw(offset_time_list);
14
15my (@PRE_INIT_PROPS, @PRE_INIT_META);
16
17sub install_pre_init_properties {
18    # Just in case; to prevent any weird recursion
19    local $MT::plugins_installed = 1;
20
21    foreach my $def (@PRE_INIT_PROPS) {
22        my ($class, $props) = @$def;
23        $class->install_properties($props);
24    }
25    @PRE_INIT_PROPS = ();
26
27    foreach my $def (@PRE_INIT_META) {
28        my ($class, $meta) = @$def;
29        $class->install_meta($meta);
30    }
31    @PRE_INIT_META = ();
32}
33
34sub install_properties {
35    my $class = shift;
36    my ($props) = @_;
37
38    if ( ( $class ne 'MT::Config') && ( !$MT::plugins_installed ) ) {
39        # We're too early in the phase of MT's bootstrapping to
40        # be installing properties; we can't query the registry yet
41        # since plugins are not all accounted for. So save this
42        # set of properties to install it later (odds are, the
43        # package has been loaded to afford installing callbacks
44        # or accessing constants and isn't being used to load
45        # actual data.)
46        #
47        # The only exception to this rule is MT::Config; we must
48        # have access to the MT configuration table in order to
49        # bootstrap MT.
50
51        push @PRE_INIT_PROPS, [$class, $props];
52        return;
53    }
54
55    my $super_props = $class->SUPER::properties();
56    if ($super_props) {
57        # subclass; merge hash
58        for (qw(primary_key meta_column class_column datasource driver audit meta)) {
59            $props->{$_} = $super_props->{$_}
60                if exists $super_props->{$_} && !(exists $props->{$_});
61        }
62        for my $p (qw(column_defs defaults indexes meta_columns)) {
63            if (exists $super_props->{$p}) {
64                foreach my $k (keys %{ $super_props->{$p} }) {
65                    if (!exists $props->{$p}{$k}) {
66                        $props->{$p}{$k} = $super_props->{$p}{$k};
67                    }
68                }
69                if ($p eq 'column_defs') {
70                    $class->__parse_defs($props->{column_defs});
71                }
72            }
73        }
74        if ($super_props->{class_type}) {
75            # copy reference of class_to_type/type_to_class hashes
76            $props->{__class_to_type} = $super_props->{__class_to_type};
77            $props->{__type_to_class} = $super_props->{__type_to_class};
78        }
79    }
80
81    # Legacy MT::Object types only define 'columns'; we still support that
82    # but they aren't handled properly with the upgrade system as a result.
83    if (exists $props->{column_defs}) {
84        $props->{columns} = [ keys %{ $props->{column_defs} } ];
85    } else {
86        map { $props->{column_defs}{$_} = () } @{ $props->{columns} };
87    }
88
89    # Support audit flags
90    if ($props->{audit}) {
91        unless (exists $props->{column_defs}{created_on}) {
92            $props->{column_defs}{created_on} = 'datetime';
93            $props->{column_defs}{created_by} = 'integer';
94            $props->{column_defs}{modified_on} = 'datetime';
95            $props->{column_defs}{modified_by} = 'integer';
96            push @{ $props->{columns} }, qw( created_on created_by modified_on modified_by );
97        }
98    }
99
100    # Metadata column
101    $props->{meta_column} ||= 'meta' if exists $props->{meta};
102    if (my $col = $props->{meta_column}) {
103        if (!$props->{column_defs}{$col}) {
104            $props->{column_defs}{$col} = 'blob';
105            push @{ $props->{columns} }, $col;
106        }
107        no strict 'refs'; ## no critic
108        *{$class . '::' . $col} = \&__meta_column;
109        $class->add_trigger( pre_save => \&pre_save_serialize_metadata );
110    }
111
112    # Classed object types
113    $props->{class_column} ||= 'class' if exists $props->{class_type};
114    if (my $col = $props->{class_column}) {
115        if (!$props->{column_defs}{$col}) {
116            $props->{column_defs}{$col} = 'string(255)';
117            push @{$props->{columns}}, $col;
118            $props->{indexes}{$col} = 1;
119        }
120        if (!$super_props || !$super_props->{class_column}) {
121            $class->add_trigger( pre_search => \&pre_search_scope_terms_to_class );
122            $class->add_trigger( post_load => \&post_load_rebless_object );
123        }
124        if (my $type = $props->{class_type}) {
125            $props->{defaults}{$col} = $type;
126            $props->{__class_to_type}{$class} = $type;
127            $props->{__type_to_class}{$type} = $class;
128        }
129    }
130
131    my $type_id;
132    if ($type_id = $props->{class_type}) {
133        if ($type_id ne $props->{datasource}) {
134            $type_id = $props->{datasource} . '.' . $type_id;
135        }
136    } else {
137        $type_id = $props->{datasource};
138    }
139
140    $class->SUPER::install_properties($props);
141
142    # check for any supplemental columns from other components
143    my $more_props = MT->registry('object_types', $type_id);
144    if ($more_props && (ref($more_props) eq 'ARRAY')) {
145        my $cols = {};
146        for my $prop (@$more_props) {
147            next if ref($prop) ne 'HASH';
148            MT::__merge_hash($cols, $prop, 1);
149        }
150        my @classes = grep { !ref($_) } @$more_props;
151        foreach my $isa_class (@classes) {
152            next if UNIVERSAL::isa($class, $isa_class);
153            eval "require $isa_class;" or die;
154            no strict 'refs'; ## no critic
155            push @{$class . '::ISA'}, $isa_class;
156        }
157        if (%$cols) {
158            # special case for 'plugin' key...
159            delete $cols->{plugin} if exists $cols->{plugin};
160            for my $name (keys %$cols) {
161                next if exists $props->{column_defs}{$name};
162                $class->install_column($name, $cols->{$name});
163                $props->{indexes}{$name} = 1
164                    if $cols->{$name} =~ m/\bindexed\b/;
165                if ($cols->{$name} =~ m/\bdefault (?:'([^']+?)'|(\d+))\b/) {
166                    $props->{defaults}{$name} = defined $1 ? $1 : $2;
167                }
168            }
169        }
170    }
171
172    my $pk = $props->{primary_key} || '';
173    @{$props->{columns}} = sort { $a eq $pk ? -1 : $b eq $pk ? 1 : $a cmp $b }
174        @{$props->{columns}};
175
176    # Child classes are declared as an array;
177    # convert them to a hashref for easier lookup.
178    if ((ref $props->{child_classes}) eq 'ARRAY') {
179        my $classes = $props->{child_classes};
180        $props->{child_classes} = {};
181        @{$props->{child_classes}}{@$classes} = ();
182    }
183
184    # We're declared as a child of some other class; associate ourselves
185    # with that package (the invoking class should have already use'd it.)
186    if (exists $props->{child_of}) {
187        my $parent_classes = $props->{child_of};
188        if (!ref $parent_classes) {
189            $parent_classes = [ $parent_classes ];
190        }
191        foreach my $pc (@$parent_classes) {
192            my $pp = $pc->properties;
193            $pp->{child_classes} ||= {};
194            $pp->{child_classes}{$class} = ();
195        }
196    }
197
198    # Special handling for 'Taggable' objects; automatic saving
199    # and removal of tags.
200    my @isa;
201    {
202        no strict 'refs';
203        @isa = @{ $class . '::ISA' };
204    }
205    foreach my $isa_pkg ( @isa ) {
206        next unless $isa_pkg =~ /able$/;
207        next if $isa_pkg eq $class;
208        if ($isa_pkg->can('install_properties')) {
209            $isa_pkg->install_properties($class);
210        }
211    }
212
213    # install legacy date translation
214    if (0 < scalar @{ $class->columns_of_type('datetime', 'timestamp') }) {
215        if ($props->{audit}) {
216            $class->add_trigger( pre_save  => \&assign_audited_fields);
217            $class->add_trigger( post_save => \&translate_audited_fields );
218        }
219
220        $class->add_trigger( pre_save  => get_date_translator(\&ts2db, 1) );
221        $class->add_trigger( post_load => get_date_translator(\&db2ts, 0) );
222    }
223
224    if ( exists($props->{cacheable}) && !$props->{cacheable} ) {
225        no warnings 'redefine';
226        no strict 'refs'; ## no critic
227        *{$class . '::driver'} = sub { $_[0]->dbi_driver(@_) };
228    }
229
230    return $props;
231}
232
233# A post-load trigger for classed objects
234sub post_load_rebless_object {
235    my $obj = shift;
236    my $props = $obj->properties;
237    if (my $col = $props->{class_column}) {
238        my $type = $obj->column($col);
239        my $pkg = ref($obj);
240        if ($pkg->class_type ne $type) {
241            if (my $class = $props->{__type_to_class}{$type}) {
242                bless $obj, $class;
243            } else {
244                my %models = map { $_ => 1 } MT->models($props->{datasource});
245                if (exists $models{ $props->{datasource} . '.' . $type}) {
246                    $class = MT->model($props->{datasource} . '.' . $type);
247                } elsif (exists $models{$type}) {
248                    $class = MT->model($type);
249                }
250                bless $obj, $class if $class;
251            }
252        }
253    }
254}
255
256# A pre-search trigger for classed objects
257sub pre_search_scope_terms_to_class {
258    my ($class, $terms, $args) = @_;
259    # scope search terms to class
260
261    $terms ||= {};
262    return if (ref $terms eq 'HASH') && exists($terms->{id});
263
264    my $props = $class->properties;
265    my $col = $props->{class_column}
266        or return;
267    if (ref $terms eq 'HASH') {
268        if (exists $terms->{$col}) {
269            if ($terms->{$col} eq '*') {
270                # class term is '*', which signifies filtering for all classes.
271                # simply delete the term in this case.
272                delete $terms->{$col} ;
273            } elsif ($terms->{$col} =~ m/^(\w+:)\*$/) {
274                # class term is in form "foo:*"; translate to a sql-compatible
275                # syntax of "like 'foo:%'"
276                $terms->{$col} = \"like '$1%'";
277            }
278            # term has been explicitly given or explictly removed. make
279            # no further changes.
280            return;
281        }
282        $terms->{$col} = $props->{class_type};
283    }
284    elsif (ref $terms eq 'ARRAY') {
285        if (my @class_terms = grep { ref $_ eq 'HASH' && 1 == scalar keys %$_ && $_->{$col} } @$terms) {
286            # Filter out any unlimiting class terms (class = *).
287            @$terms = grep { ref $_ ne 'HASH' || 1 != scalar keys %$_ || !$_->{$col} || $_->{$col} ne '*' } @$terms;
288
289            # The class column has been explicitly given or removed, so don't
290            # add one.
291            return;
292        }
293        @$terms = ( { $col => $props->{class_type} } => 'AND' => [ @$terms ] );
294    }
295}
296
297sub class_label {
298    my $pkg = shift;
299    return MT->translate($pkg->datasource);
300}
301
302sub class_label_plural {
303    my $pkg = shift;
304    my $label = $pkg->datasource;
305    $label =~ s/y$/ie/;
306    $label .= 's';
307    return MT->translate($label);
308}
309
310sub class_labels {
311    my $pkg = shift;
312    my @all_types = MT->models($pkg->properties->{datasource});
313    my %names;
314    foreach my $type (@all_types) {
315        my $class = $pkg->class_handler($type);
316        $names{$type} = $class->class_label;
317    }
318    return \%names;
319}
320
321# Returns a hashref of asset identifiers mapped to the localized string
322# used to name them. (Ie, image => 'Image').
323sub class_type {
324    my $pkg = shift;
325    if (ref $pkg) {
326        return $pkg->column($pkg->properties->{class_column});
327    } else {
328        return $pkg->properties->{class_type};
329    }
330}
331
332sub class_handler {
333    my $pkg = shift;
334    my $props = $pkg->properties;
335    my ($type) = @_;
336    my $package = $props->{__type_to_class}{$type};
337    unless ($package) {
338        my $ds = $props->{datasource};
339        if (($type eq $ds) || ($type =~ m/\./)) {
340            $package = MT->model($type);
341        } else {
342            $package = MT->model($ds . '.' . $type);
343        }
344    }
345    if ($package) {
346        if (defined *{$package.'::new'}) {
347            return $package;
348        } else {
349            eval "use $package;";
350            return $package unless $@;
351            eval "use $pkg; $package->new;";
352            return $package unless $@;
353        }
354    }
355    return $pkg;
356}
357
358sub add_class {
359    my $pkg = shift;
360    my ($type, $class) = @_;
361    my $props = $pkg->properties;
362    if ($type =~ m/::/) {
363        ($type, $class) = ($class, $type);
364    }
365
366    if (my $old_class = $props->{__type_to_class}{$type}) {
367        delete $props->{__class_to_type}{$old_class};
368    }
369    $props->{__type_to_class}{$type} = $class;
370    $props->{__class_to_type}{$class} = $type;
371}
372
373# 'meta' metadata column support
374
375sub install_meta {
376    my $class = shift;
377    my ($props) = @_;
378    if ( ( $class ne 'MT::Config' ) && (!$MT::plugins_installed) ) {
379        push @PRE_INIT_META, [$class, $props];
380        return;
381    }
382    my $cprops = $class->properties;
383    my $fields = $cprops->{meta_columns} ||= {};
384    my $meta_col = $cprops->{meta_column};
385    foreach my $name (@{ $props->{columns} }) {
386        $fields->{$name} = ();
387        # Skip adding this method if the class overloads it.
388        # this lets the SUPER::columnname magic do it's thing
389        unless ($class->can($name)) {
390            no strict 'refs'; ## no critic
391            *{"${class}::$name"} = sub { shift->$meta_col($name, @_) };
392        }
393    }
394}
395
396sub has_meta {
397    my $props = $_[0]->properties;
398    return $props->{meta} && (@_ > 1 ? exists $props->{meta_columns}{$_[1]} : 1);
399}
400
401sub pre_save_serialize_metadata {
402    my ($obj) = shift;
403    my $meta_col = $obj->properties->{meta_column};
404    if ($obj->{changed_cols}{$meta_col}) {
405        require MT::Serialize;
406        my $meta = $obj->$meta_col;
407        $obj->$meta_col(MT::Serialize->serialize(\$meta));
408    }
409}
410
411sub __thaw_meta {
412    my ($meta) = @_;
413    $$meta = '' unless defined $$meta;
414    require MT::Serialize;
415    my $out = MT::Serialize->unserialize($$meta);
416    if (ref $out eq 'REF') {
417        return $$out;
418    } else {
419        return {};
420    }
421}
422
423# $obj->meta returns a hashref of metadata information
424# $obj->meta($scalar) allows assignment of a serialized value
425# $obj->meta('name', 'value') assigns an individual metadata element
426# $obj->meta('name') returns an individual metadata value
427# $obj->save will automatically serialize the metadata back to the database
428sub __meta_column {
429    my $obj = shift;
430    my $meta_col = $obj->properties->{meta_column} or return;
431
432    if (@_) {
433        my $var = shift;
434        if ((defined $var) && ($var =~ m/^SERG\0\0\0\0/)) {
435            return $obj->column($meta_col, $var);
436        }
437        my $meta = $obj->column($meta_col);
438        if (!defined($meta)) {
439            $obj->{column_values}{$meta_col} = $meta = {};
440        } elsif (!ref $meta) {
441            $obj->{column_values}{$meta_col} = $meta = __thaw_meta(\$meta);
442        }
443        if (@_) {
444            $meta->{$var} = shift if @_;
445            $obj->{changed_cols}{$meta_col}++;
446        }
447        return $meta->{$var};
448    } else {
449        my $meta = $obj->column($meta_col);
450        if (!ref $meta) {
451            $meta = __thaw_meta(\$meta);
452            $obj->{column_values}{$meta_col} = $meta;
453        }
454        # we should assume changes are going to be made, since
455        # we can't really monitor the hash once it has left us
456        $obj->{changed_cols}{$meta_col}++;
457        return $meta;
458    }
459}
460
461sub ts2db { 
462    return unless $_[0]; 
463    if($_[0] =~ m{ \A \d{4} - }xms) { 
464        return $_[0]; 
465    } 
466    my $ret = sprintf '%04d-%02d-%02d %02d:%02d:%02d', unpack 'A4A2A2A2A2A2', $_[0]; 
467    return $ret; 
468} 
469 
470sub db2ts { 
471    my $ts = $_[0]; 
472    $ts =~ s/(?:\+|-)\d{2}$//; 
473    $ts =~ tr/\- ://d; 
474    return $ts; 
475} 
476
477sub get_date_translator {
478    my $translator = shift;
479    my $change = shift;
480    return sub {
481        my $obj = shift;
482        my $dbd = $obj->driver->dbd;
483        FIELD: for my $field (@{$obj->columns_of_type('datetime', 'timestamp')}) {
484            my $value = $obj->column($field);
485            next FIELD if !defined $value;
486            my $new_val = $translator->($value); 
487            if((defined $new_val) && ($new_val ne $value)) {
488                $obj->column($field, $new_val, { no_changed_flag => !$change });
489            }
490        }
491    };
492}
493
494sub translate_audited_fields {
495    my ($obj, $orig_obj) = @_;
496    my $dbd = $obj->driver->dbd;
497    FIELD: for my $field (qw( created_on modified_on )) {
498        my $value = $orig_obj->column($field);
499        next FIELD if !defined $value;
500        my $new_val = db2ts($value); 
501        if((defined $new_val) && ($new_val ne $value)) {
502            $orig_obj->column($field, $new_val);
503        }
504    }
505    return;
506}
507
508sub nextprev {
509    my $obj = shift;
510    my $class = ref($obj);
511    my %param = @_;
512    my ($direction, $terms, $args, $by_field)
513        = @param{qw( direction terms args by )};
514    return undef unless ($direction eq 'next' || $direction eq 'previous');
515    my $next = $direction eq 'next';
516
517    if (!$by_field) {
518        return if !$class->properties->{audit};
519        $by_field = 'created_on';
520    }
521
522    # Selecting the adjacent object can be tricky since timestamps
523    # are not necessarily unique for entries. If we find that the
524    # next/previous object has a matching timestamp, keep selecting entries
525    # to select all entries with the same timestamp, then compare them using
526    # id as a secondary sort column.
527
528    my ($id, $ts) = ($obj->id, $obj->$by_field());
529    local @$args{qw( sort range_incl )}
530        = ( [ { column => $by_field, desc => $next ? 'ASC' : 'DESC' },
531            { column => 'id', desc => $next ? 'ASC' : 'DESC' } ],
532            { $by_field => 1 });
533
534    my $sibling = $class->load({
535        $by_field => ($next ? [ $ts, undef ] : [ undef, $ts ]),
536        'id' => $id,
537        %{$terms}
538    }, { not => { 'id' => 1 }, limit => 1, %$args });
539
540    return $sibling;
541}
542
543## Drivers.
544
545sub count          { shift->_proxy('count',          @_) }
546sub exist          { shift->_proxy('exist',          @_) }
547sub count_group_by { shift->_proxy('count_group_by', @_) }
548sub sum_group_by   { shift->_proxy('sum_group_by',   @_) }
549sub avg_group_by   { shift->_proxy('avg_group_by',   @_) }
550sub remove_all     { shift->_proxy('remove_all',     @_) }
551
552sub remove {
553    my $obj = shift;
554    my(@args) = @_;
555    if (!ref $obj) {
556        return $obj->driver->direct_remove($obj, @args);
557    } else {
558        return $obj->driver->remove($obj, @args);
559    }
560}
561
562sub load {
563    my $self = shift;
564    if (defined $_[0] && (!ref $_[0] || (ref $_[0] ne 'HASH' && ref $_[0] ne 'ARRAY'))) {
565        return $self->lookup($_[0]);
566    } else {
567        if (wantarray) {
568            ## MT::Object::load returns a list in list context, just like
569            ## a D::OD search.
570            return $self->search(@_);
571        } else {
572            ## MT::Object::load returns the first result in scalar context.
573            my $iter = $self->search(@_);
574            return if !defined $iter;
575            return $iter->();
576        }
577    }
578}
579
580# More or less replacing Data::ObjectDriver::Driver::DBI::search here
581# to provide an 'early-finish' iterator as MT::ObjectDriver had provided.
582
583sub load_iter   {
584    my $class = shift;
585    my($terms, $args) = @_;
586
587    my $driver = $class->driver;
588    my $dbi_driver = $driver;
589
590    while ( $dbi_driver->isa('Data::ObjectDriver::Driver::BaseCache') ) {
591        $dbi_driver = $dbi_driver->fallback;
592    }
593
594    if ($dbi_driver->dbd eq 'MT::ObjectDriver::Driver::SQLite') {
595        # for SQLite, use search method, since this technique
596        # will cause it to lock the table
597        return scalar $class->search(@_);
598    }
599
600    my $rec = {};
601    my $sth = $dbi_driver->fetch($rec, $class, $terms, $args);
602
603    my $iter = sub {
604        ## This is kind of a hack--we need $driver to stay in scope,
605        ## so that the DESTROY method isn't called. So we include it
606        ## in the scope of the closure.
607        my $d = $dbi_driver;
608        my $d2 = $driver;
609
610        if (@_ && ($_[0] eq 'finish')) {
611            if ($sth) {
612                $sth->finish;
613                $dbi_driver->end_query($sth);
614            }
615            undef $sth;
616            return;
617        }
618
619        unless ($sth->fetch) {
620            $sth->finish;
621            $dbi_driver->end_query($sth);
622            return;
623        }
624        my $obj;
625        $obj = $class->new;
626        $obj->set_values_internal($rec);
627        ## Don't need a duplicate as there's no previous version in memory
628        ## to preserve.
629        $obj->call_trigger('post_load') unless $args->{no_triggers};
630        $driver->cache_object($obj) if $obj && (!$args->{fetchonly});
631        $obj;
632    };
633    return $iter;
634}
635
636## Callbacks
637
638sub assign_audited_fields {
639    my ($obj, $orig_obj) = @_;
640    if ($obj->properties->{audit}) {
641        my $blog_id;
642        if ($obj->has_column('blog_id')) {
643            $blog_id = $obj->blog_id;
644        }
645        my @ts = offset_time_list(time, $blog_id);
646        my $ts = sprintf '%04d%02d%02d%02d%02d%02d',
647            $ts[5]+1900, $ts[4]+1, @ts[3,2,1,0];
648
649        my $app = MT->instance;
650        if ($app && $app->can('user')) {
651            if (my $user = $app->user) {
652                if (!defined $obj->created_on) {
653                    $obj->created_by($user->id);
654                    $orig_obj->created_by($obj->created_by);
655                }
656            }
657        }
658        unless ($obj->created_on) {
659            $obj->created_on($ts);
660            $orig_obj->created_on($ts);
661            # intentionally not calling modified_by to distinguish
662            $obj->modified_on($ts);
663            $orig_obj->modified_on($ts);
664        }
665    }
666}
667
668sub modified_by {
669    my $obj = shift;
670    my ($user_id) = @_;
671    if ($user_id) {
672        if ($obj->properties->{audit}) {
673            my $res = $obj->SUPER::modified_by($user_id);
674
675            my $blog_id;
676            if ($obj->has_column('blog_id')) {
677                $blog_id = $obj->blog_id;
678            }
679            my @ts = offset_time_list(time, $blog_id);
680            my $ts = sprintf '%04d%02d%02d%02d%02d%02d',
681                $ts[5]+1900, $ts[4]+1, @ts[3,2,1,0];
682            $obj->modified_on($ts);
683            return $res;
684        }
685    }
686    return $obj->SUPER::modified_by(@_);
687}
688
689# D::OD uses Class::Trigger. Map the call_trigger calls to also invoke
690# MT's callbacks (but internal Class::Trigger routines should be invoked
691# first in the case of pre-triggers, and last in the case of post-triggers).
692
693sub call_trigger {
694    my $obj = shift;
695    my $name = shift;
696    my $class = ref $obj || $obj;
697    my $pre_trigger = $name =~ m/^pre_/;
698    $obj->SUPER::call_trigger($name, @_) if $pre_trigger;
699    MT->run_callbacks($class . '::' . $name, $obj, @_);
700    $obj->SUPER::call_trigger($name, @_) unless $pre_trigger;
701}
702
703# Support for MT-based callbacks.
704
705sub add_callback {
706    my $class = shift;
707    my $meth = shift;
708    MT->add_callback($class . '::' . $meth, @_);
709}
710
711## Construction/initialization.
712
713sub init {
714    my $obj = shift;
715    $obj->SUPER::init(@_);
716    $obj->set_defaults();
717    return $obj;
718}
719
720sub set_defaults {
721    my $obj = shift;
722    my $defaults = $obj->properties->{'defaults'};
723    $obj->{'column_values'} = $defaults ? {%$defaults} : {};
724}
725
726sub __properties { }
727
728our $DRIVER;
729sub driver {
730    require MT::ObjectDriverFactory;
731    return $DRIVER ||= MT::ObjectDriverFactory->new;
732}
733
734# ref to the fallback driver for non-cacheable classes
735our $DBI_DRIVER;
736sub dbi_driver {
737    unless ($DBI_DRIVER) {
738        my $driver = driver(@_);
739        while ( $driver->can('fallback') ) {
740            if ($driver->fallback) {
741                $driver = $driver->fallback;
742            } else {
743                last;
744            }
745        }
746        $DBI_DRIVER = $driver;
747    }
748    return $DBI_DRIVER;
749}
750
751sub table_name {
752    my $obj = shift;
753    return $obj->driver->table_for($obj);
754}
755
756sub clone {
757    my $obj = shift;
758    my($param) = @_;
759    my $clone = $obj->SUPER::clone_all;
760
761    ## If the caller has listed a set of columns not to copy to the clone,
762    ## delete them from the clone.
763    if ($param && ($param->{Except} || $param->{except})) {
764        for my $col (keys %{ $param->{Except} || $param->{except} }) {
765            $clone->$col(undef);
766        }
767    }
768    return $clone;
769}
770
771sub columns_of_type {
772    my $obj = shift;
773    my(@types) = @_;
774    my $props = $obj->properties;
775    my $cols = $props->{columns};
776    my $col_defs = $obj->column_defs;
777    my @cols;
778    my %types = map { $_ => 1 } @types;
779    for my $col (@$cols) {
780        push @cols, $col
781            if $col_defs->{$col} && exists $types{$col_defs->{$col}{type}};
782    }
783    \@cols;
784}
785
786sub created_on_obj {
787    my $obj = shift;
788    return $obj->column_as_datetime('created_on');
789}
790
791sub column_as_datetime {
792    my $obj = shift;
793    my ($col) = @_;
794    if (my $ts = $obj->column($col)) {
795        my $blog;
796        if ($obj->isa('MT::Blog')) {
797            $blog = $obj;
798        } else {
799            if (my $blog_id = $obj->blog_id) {
800                require MT::Blog;
801                $blog = MT::Blog->lookup($blog_id);
802            }
803        }
804        my($y, $mo, $d, $h, $m, $s) = $ts =~ /(\d\d\d\d)[^\d]?(\d\d)[^\d]?(\d\d)[^\d]?(\d\d)[^\d]?(\d\d)[^\d]?(\d\d)/;
805        require MT::DateTime;
806        my $four_digit_offset;
807        if ($blog) {
808            $four_digit_offset = sprintf('%.02d%.02d', int($blog->server_offset),
809                                        60 * abs($blog->server_offset
810                                                 - int($blog->server_offset)));
811        }
812        return new MT::DateTime(year => $y, month => $mo, day => $d,
813                                hour => $h, minute => $m, second => $s,
814                                time_zone => $four_digit_offset);
815    }
816    undef;
817}
818
819sub join_on {
820    return [ @_ ];
821}
822
823sub remove_children {
824    my $obj = shift;
825    return 1 unless ref $obj;
826
827    my ($param) = @_;
828    my $child_classes = $obj->properties->{child_classes} || {};
829    my @classes = keys %$child_classes;
830    return 1 unless @classes;
831
832    $param ||= {};
833    my $key = $param->{key} || $obj->datasource . '_id';
834    my $obj_id = $obj->id;
835    for my $class (@classes) {
836        eval "use $class;";
837        $class->remove({ $key => $obj_id });
838    }
839    1;
840}
841
842sub get_by_key {
843    my $class = shift;
844    my ($key) = @_;
845    my($obj) = $class->search($key);
846    $obj ||= new $class;
847    $obj->set_values($key);
848    return $obj;
849}
850
851sub set_by_key {
852    my $class = shift;
853    my ($key, $value) = @_;
854    my ($obj) = $class->search($key);
855    unless ($obj) {
856        $obj = new $class;
857        $obj->set_values($key);
858    }
859    $obj->set_values($value) if $value;
860    $obj->save or return;
861    return $obj;
862}
863
864# This method is overridden since D::OD uses column_values to retrieve
865# the content to cache if caching is enabled. Thus, we must ensure any
866# metadata is serialized prior to caching.
867sub column_values {
868    my $props = $_[0]->properties;
869    if ($props->{meta_column}
870        && $_[0]->{changed_cols}{$props->{meta_column}}) {
871        $_[0]->pre_save_serialize_metadata;
872    }
873    return $_[0]->SUPER::column_values(@_);
874}
875
876# We override D::OD's set_values method here only allowing the
877# assignment of a column if the value given is defined. There are
878# some legacy reasons for doing this, mostly for backward
879# compatibility.
880sub set_values {
881    my $obj = shift;
882    my ($values) = @_;
883    for my $col (keys %$values) {
884        unless ( $obj->has_column($col) ) {
885            Carp::croak("You tried to set inexistent column $col to value $values->{$col} on " . ref($obj));
886        }
887        $obj->$col($values->{$col}) if defined $values->{$col};
888    }
889}
890
891sub column_def {
892    my $obj = shift;
893    my ($name) = @_;
894    my $defs = $obj->column_defs;
895    my $def = $defs->{$name};
896    if (!ref($def)) {
897        $defs->{$name} = $def = $obj->__parse_def($name, $def);
898    }
899    return $def;
900}
901
902sub index_defs {
903    my $obj = shift;
904    my $props = $obj->properties;
905    $props->{indexes};
906}
907
908sub column_defs {
909    my $obj = shift;
910    my $props = $obj->properties;
911    my $defs = $props->{column_defs};
912    return undef if !$defs;
913    my ($key) = keys %$defs;
914    if (!(ref $defs->{$key})) {
915        $obj->__parse_defs($props->{column_defs});
916    }
917    $props->{column_defs};
918}
919
920sub __parse_defs {
921    my $obj = shift;
922    my ($defs) = @_;
923    foreach my $col ( keys %$defs ) {
924        next if ref($defs->{$col});
925        $defs->{$col} = $obj->__parse_def($col, $defs->{$col});
926    }
927}
928
929sub __parse_def {
930    my $obj = shift;
931    my ($col, $def) = @_;
932    return undef if !defined $def;
933    my $props = $obj->properties;
934    my %def;
935    if ($def =~ s/^([^( ]+)\s*//) {
936        $def{type} = $1;
937    }
938    if ($def =~ s/^\((\d+)\)\s*//) {
939        $def{size} = $1;
940    }
941    $def{not_null} = 1 if $def =~ m/\bnot null\b/i;
942    $def{key} = 1 if $def =~ m/\bprimary key\b/i;
943    $def{key} = 1 if ($props->{primary_key}) && ($props->{primary_key} eq $col);
944    $def{auto} = 1 if $def =~ m/\bauto[_ ]increment\b/i;
945    $def{default} = $props->{defaults}{$col} if exists $props->{defaults}{$col};
946    \%def;
947}
948
949sub cache_property {
950    my $obj = shift;
951    my $key = shift;
952    my $code = shift;
953    if (ref $key eq 'CODE') {
954        ($key, $code) = ($code, $key);
955    }
956    $key ||= (caller(1))[3];
957
958    my $r = MT->request;
959    my $oc = $r->cache('object_cache');
960    unless ($oc) {
961        $oc = {};
962        $r->cache('object_cache', $oc);
963    }
964    $oc = $oc->{"$obj"} ||= {};
965    if (@_) {
966        $oc->{$key} = $_[0];
967    } else {
968        if ((!exists $oc->{$key}) && $code) {
969            $oc->{$key} = $code->($obj, @_);
970        }
971    }
972    return exists $oc->{$key} ? $oc->{$key} : undef;
973}
974
975sub clear_cache {
976    my $obj = shift;
977    my $oc = MT->request('object_cache') or return;
978    if (@_) {
979        $oc = $oc->{"$obj"};
980        delete $oc->{shift} if $oc;
981    } else {
982        delete $oc->{"$obj"};
983    }
984}
985
986sub to_hash {
987    my $obj = shift;
988    my $hash = {};
989    my $props = $obj->properties;
990    my $pfx = $obj->datasource;
991    my $values = $obj->column_values;
992    foreach (keys %$values) {
993        $hash->{"${pfx}.$_"} = $values->{$_};
994    }
995    if (my $meta = $props->{meta_columns}) {
996        foreach (keys %$meta) {
997            $hash->{"${pfx}.$_"} = $obj->meta($_);
998        }
999    }
1000    if ($obj->has_column('blog_id')) {
1001        my $blog_id = $obj->blog_id;
1002        require MT::Blog;
1003        if (my $blog = MT::Blog->lookup($blog_id)) {
1004            my $blog_hash = $blog->to_hash;
1005            $hash->{"${pfx}.$_"} = $blog_hash->{$_} foreach keys %$blog_hash;
1006        }
1007    }
1008    $hash;
1009}
1010
10111;
1012__END__
1013
1014=head1 NAME
1015
1016MT::Object - Movable Type base class for database-backed objects
1017
1018=head1 SYNOPSIS
1019
1020Creating an I<MT::Object> subclass:
1021
1022    package MT::Foo;
1023    use strict;
1024
1025    use base 'MT::Object';
1026
1027    __PACKAGE__->install_properties({
1028        columns_defs => {
1029            'id'  => 'integer not null auto_increment',
1030            'foo' => 'string(255)',
1031        },
1032        indexes => {
1033            foo => 1,
1034        },
1035        primary_key => 'id',
1036        datasource => 'foo',
1037    });
1038
1039Using an I<MT::Object> subclass:
1040
1041    use MT;
1042    use MT::Foo;
1043
1044    ## Create an MT object to load the system configuration and
1045    ## initialize an object driver.
1046    my $mt = MT->new;
1047
1048    ## Create an MT::Foo object, fill it with data, and save it;
1049    ## the object is saved using the object driver initialized above.
1050    my $foo = MT::Foo->new;
1051    $foo->foo('bar');
1052    $foo->save
1053        or die $foo->errstr;
1054
1055=head1 DESCRIPTION
1056
1057I<MT::Object> is the base class for all Movable Type objects that will be
1058serialized/stored to some location for later retrieval; this location could
1059be a DBM file, a relational database, etc.
1060
1061Movable Type objects know nothing about how they are stored--they know only
1062of what types of data they consist, the names of those types of data (their
1063columns), etc. The actual storage mechanism is in the I<MT::ObjectDriver::Driver::DBI>
1064class and its driver subclasses; I<MT::Object> subclasses, on the other hand,
1065are essentially just standard in-memory Perl objects, but with a little extra
1066self-knowledge.
1067
1068This distinction between storage and in-memory representation allows objects
1069to be serialized to disk in many different ways--for example, an object could
1070be stored in a MySQL database, in a DBM file, etc. Adding a new storage method
1071is as simple as writing an object driver--a non-trivial task, to be sure, but
1072one that will not require touching any other Movable Type code.
1073
1074=head1 SUBCLASSING
1075
1076Creating a subclass of I<MT::Object> is very simple; you simply need to
1077define the properties and metadata about the object you are creating. Start
1078by declaring your class, and inheriting from I<MT::Object>:
1079
1080    package MT::Foo;
1081    use strict;
1082
1083    use base 'MT::Object';
1084
1085=item * __PACKAGE__->install_properties($args)
1086
1087Then call the I<install_properties> method on your class name; an easy way
1088to get your class name is to use the special I<__PACKAGE__> variable:
1089
1090    __PACKAGE__->install_properties({
1091        column_defs => {
1092            'id' => 'integer not null auto_increment',
1093            'foo' => 'string(255)',
1094        },
1095        indexes => {
1096            foo => 1,
1097        },
1098        primary_key => 'id',
1099        datasource => 'foo',
1100    });
1101
1102I<install_properties> performs the necessary magic to install the metadata
1103about your new class in the MT system. The method takes one argument, a hash
1104reference containing the metadata about your class. That hash reference can
1105have the following keys:
1106
1107=over 4
1108
1109=item * column_defs
1110
1111The definition of the columns (fields) in your object. Column names are also
1112used for method names for your object, so your column name should not
1113contain any strange characters. (It could also be used as part of the name of
1114the column in a relational database table, so that is another reason to keep
1115column names somewhat sane.)
1116
1117The value for the I<columns> key should be a reference to an hashref
1118containing the key/value pairs that are names of your columns matched with
1119their schema definition.
1120
1121The type declaration of a column is pseudo-SQL. The data types loosely match
1122SQL types, but are vendor-neutral, and each MT::ObjectDriver will map these
1123to appropriate types for the database it services. The format of a column
1124type is as follows:
1125
1126    'column_name' => 'type(size) options'
1127
1128The 'type' part of the declaration can be any one of:
1129
1130=over 4
1131
1132=item * string
1133
1134For storing string data, typically up to 255 characters, but assigned a length identified by '(size)'.
1135
1136=item * integer
1137
1138For storing integers, maybe limited to 32 bits.
1139
1140=item * boolean
1141
1142For storing boolean values (numeric values of 1 or 0).
1143
1144=item * smallint
1145
1146For storing small integers, typically limited to 16 bits.
1147
1148=item * datetime
1149
1150For storing a full date and time value.
1151
1152=item * timestamp
1153
1154For storing a date and time that automatically updates upon save.
1155
1156=item * blob
1157
1158For storing binary data.
1159
1160=item * text
1161
1162For storing text data.
1163
1164=item * float
1165
1166For storing floating point values.
1167
1168=back
1169
1170Note: The physical data storage capacity of these types will vary depending on
1171the driver's implementation. Please refer to the documentation of the
1172MT::ObjectDriver you're using to determine the actual capacity for these
1173types.
1174
1175The '(size)' element of the declaration is only valid for the 'string' type.
1176
1177The 'options' element of the declaration is not required, but is used to
1178specify additional attributes of the column. Such as:
1179
1180=over 4
1181
1182=item * not null
1183
1184Specify this option when you wish to constrain the column so that it must contain a defined value. This is only enforced by the database itself, not by the MT::ObjectDriver.
1185
1186=item * auto_increment
1187
1188Specify for integer columns (typically the primary key) to automatically assign a value.
1189
1190=item * primary key
1191
1192Specify for identifying the column as the primary key (only valid for a single column).
1193
1194=back
1195
1196=item * indexes
1197
1198Specifies the column indexes on your objects; this only has consequence for
1199some object drivers (DBM, for example), where indexes are not automatically
1200maintained by the datastore (as they are in a relational database).
1201
1202The value for the I<indexes> key should be a reference to a hash containing
1203column names as keys, and the value C<1> for each key--each key represents
1204a column that should be indexed.
1205
1206B<NOTE:> with the DBM driver, if you do not set up an index on a column you
1207will not be able to select objects with values matching that column using the
1208I<load> and I<load_iter> interfaces (see below).
1209
1210=item * audit
1211
1212Automatically adds bookkeeping capabilities to your class--each object will
1213take on four new columns: I<created_on>, I<created_by>, I<modified_on>, and
1214I<modified_by>. The created_on, created_by columns will be populated
1215automatically (if they have not already been assigned at the time of saving
1216the object). Your application is responsible for updating the modified_on,
1217modified_by columns as these may require explicit application-specific
1218assignments (ie, your application may only want them updated during explicit
1219user interaction with the object, as opposed to cases where the object is
1220being changed and saved for mechanical purposes like upgrading a table).
1221
1222=item * datasource
1223
1224The name of the datasource for your class. The datasource is a name uniquely
1225identifying your class--it is used by the object drivers to construct table
1226names, file names, etc. So it should not be specific to any one driver.
1227
1228=item * meta
1229
1230Specify this property if you wish to add an additional 'meta' column to
1231the object properties. This is a special type of column that is used to
1232store complex data structures for the object. The data is serialized into
1233a blob for storage using the L<MT::Serialize> package.
1234
1235=item * meta_column
1236
1237If you wish to specify the name of the column to be used for storing
1238the object metadata, you may declare this property to name the column.
1239The default column name is 'meta'.
1240
1241=item * class_type
1242
1243If class_type is declared, an additional 'class' column is added to the
1244object properties. This column is then used to differentiate between
1245multiple object types that share the same physical table.
1246
1247Note that if this is used, all searches will be constrained to match
1248the class type of the package.
1249
1250=item * class_column
1251
1252Defines the name of the class column (default is 'class') for storing
1253classed objects (see 'class_type' above).
1254
1255=back
1256
1257=head1 USAGE
1258
1259=head2 System Initialization
1260
1261Before using (loading, saving, removing) an I<MT::Object> class and its
1262objects, you must always initialize the Movable Type system. This is done
1263with the following lines of code:
1264
1265    use MT;
1266    my $mt = MT->new;
1267
1268Constructing a new I<MT> objects loads the system configuration from the
1269F<mt.cfg> configuration file, then initializes the object driver that will
1270be used to manage serialized objects.
1271
1272=head2 Creating a new object
1273
1274To create a new object of an I<MT::Object> class, use the I<new> method:
1275
1276    my $foo = MT::Foo->new;
1277
1278I<new> takes no arguments, and simply initializes a new in-memory object.
1279In fact, you need not ever save this object to disk; it can be used as a
1280purely in-memory object.
1281
1282=head2 Setting and retrieving column values
1283
1284To set the column value of an object, use the name of the column as a method
1285name, and pass in the value for the column:
1286
1287    $foo->foo('bar');
1288
1289The return value of the above call will be C<bar>, the value to which you have
1290set the column.
1291
1292To retrieve the existing value of a column, call the same method, but without
1293an argument:
1294
1295    $foo->foo
1296
1297This returns the value of the I<foo> column from the I<$foo> object.
1298
1299=over 4
1300
1301=item * $obj->init()
1302
1303=back
1304
1305This method is used to initialize the object upon construction.
1306
1307=over 4
1308
1309=item * $obj->set_defaults()
1310
1311=back
1312
1313This method is used by the I<init> method to set the object defaults.
1314
1315=head2 Saving an object
1316
1317To save an object using the object driver, call the I<save> method:
1318
1319=over 4
1320
1321=item * $foo->save();
1322
1323=back
1324
1325On success, I<save> will return some true value; on failure, it will return
1326C<undef>, and you can retrieve the error message by calling the I<errstr>
1327method on the object:
1328
1329    $foo->save
1330        or die "Saving foo failed: ", $foo->errstr;
1331
1332If you are saving objects in a loop, take a look at the
1333L</"Note on object locking">.
1334
1335=head2 Loading an existing object or objects
1336
1337=over 4
1338
1339=item * $obj->load()
1340
1341=item * $obj->load_iter()
1342
1343=back
1344
1345You can load an object from the datastore using the I<load> method. I<load>
1346is by far the most complicated method, because there are many different ways
1347to load an object: by ID, by column value, by using a join with another type
1348of object, etc.
1349
1350In addition, you can load objects either into an array (I<load>), or by using
1351an iterator to step through the objects (I<load_iter>).
1352
1353I<load> has the following general form:
1354
1355    my @objects = MT::Foo->load(\%terms, \%arguments);
1356
1357I<load_iter> has the following general form:
1358
1359    my $iter = MT::Foo->load_iter(\%terms, \%arguments);
1360
1361Both methods share the same parameters; the only difference is the manner in
1362which they return the matching objects.
1363
1364If you call I<load> in scalar context, only the first row of the array
1365I<@objects> will be returned; this works well when you know that your I<load>
1366call can only ever result in one object returned--for example, when you load
1367an object by ID.
1368
1369I<\%terms> should be either:
1370
1371=over 4
1372
1373=item * The numeric ID of an object in the datastore.
1374
1375=item * A reference to a hash.
1376
1377The hash should have keys matching column names and the values are the
1378values for that column.
1379
1380For example, to load an I<MT::Foo> object where the I<foo> column is
1381equal to C<bar>, you could do this:
1382
1383    my @foo = MT::Foo->load({ foo => 'bar' });
1384
1385In addition to a simple scalar, the hash value can be a reference to an array;
1386combined with the I<range> setting in the I<\%arguments> list, you can use
1387this to perform range searches. If the value is a reference, the first element
1388in the array specifies the low end of the range, and the second element the
1389high end.
1390
1391=back
1392
1393I<\%arguments> should be a reference to a hash containing parameters for the
1394search. The following parameters are allowed:
1395
1396=over 4
1397
1398=item * sort => "column"
1399
1400Sort the resulting objects by the column C<column>; C<column> must be an
1401indexed column (see L</"indexes">, above).
1402
1403=item * direction => "ascend|descend"
1404
1405To be used together with I<sort>; specifies the sort order (ascending or
1406descending). The default is C<ascend>.
1407
1408=item * limit => "N"
1409
1410Rather than loading all of the matching objects (the default), load only
1411C<N> objects.
1412
1413=item * offset => "M"
1414
1415To be used together with I<limit>; rather than returning the first C<N>
1416matches (the default), return matches C<M> through C<N + M>.
1417
1418=item * start_val => "value"
1419
1420To be used together with I<limit> and I<sort>; rather than returning the
1421first C<N> matches, return the first C<N> matches where C<column> (the sort
1422column) is greater than C<value>.
1423
1424=item * range
1425
1426To be used together with an array reference as the value for a column in
1427I<\%terms>; specifies that the specific column should be searched for a range
1428of values, rather than one specific value.
1429
1430The value of I<range> should be a hash reference, where the keys are column
1431names, and the values are all C<1>; each key specifies a column that should
1432be interpreted as a range.
1433
1434    MT::Foo->load( { created_on => [ '20011008000000', undef ] },
1435        { range => { created_on => 1 } } );
1436
1437This selects C<MT::Foo> objects whose created_on date is greater than
14382001-10-08 00:00:00.
1439
1440=item * range_incl
1441
1442Like the 'range' attribute, but defines an inclusive range.
1443
1444=item * join
1445
1446Can be used to select a set of objects based on criteria, or sorted by
1447criteria, from another set of objects. An example is selecting the C<N>
1448entries most recently commented-upon; the sorting is based on I<MT::Comment>
1449objects, but the objects returned are actually I<MT::Entry> objects. Using
1450I<join> in this situation is faster than loading the most recent
1451I<MT::Comment> objects, then loading each of the I<MT::Entry> objects
1452individually.
1453
1454Note that I<join> is not a normal SQL join, in that the objects returned are
1455always of only one type--in the above example, the objects returned are only
1456I<MT::Entry> objects, and cannot include columns from I<MT::Comment> objects.
1457
1458I<join> has the following general syntax:
1459
1460    join => MT::Foo->join_on( JOIN_COLUMN, I<\%terms>, I<\%arguments> )
1461
1462Use the actual MT::Object-descended package name and the join_on static method
1463providing these parameters: I<JOIN_COLUMN> is the column joining the two
1464object tables, I<\%terms> and I<\%arguments> have the same meaning as they do
1465in the outer I<load> or I<load_iter> argument lists: they are used to select
1466the objects with which the join is performed.
1467
1468For example, to select the last 10 most recently commmented-upon entries, you
1469could use the following statement:
1470
1471    my @entries = MT::Entry->load(undef, {
1472        'join' => MT::Comment->join_on( 'entry_id',
1473                    { blog_id => $blog_id },
1474                    { 'sort' => 'created_on',
1475                      direction => 'descend',
1476                      unique => 1,
1477                      limit => 10 } )
1478    });
1479
1480In this statement, the I<unique> setting ensures that the I<MT::Entry>
1481objects returned are unique; if this flag were not given, two copies of the
1482same I<MT::Entry> could be returned, if two comments were made on the same
1483entry.
1484
1485=item * unique
1486
1487Ensures that the objects being returned are unique.
1488
1489This is really only useful when used within a I<join>, because when loading
1490data out of a single object datastore, the objects are always going to be
1491unique.
1492
1493=back
1494
1495=head2 Removing an object
1496
1497=over 4
1498
1499=item * $foo->remove()
1500
1501=back
1502
1503To remove an object from the datastore, call the I<remove> method on an
1504object that you have already loaded using I<load>:
1505
1506    $foo->remove();
1507
1508On success, I<remove> will return some true value; on failure, it will return
1509C<undef>, and you can retrieve the error message by calling the I<errstr>
1510method on the object:
1511
1512    $foo->remove
1513        or die "Removing foo failed: ", $foo->errstr;
1514
1515If you are removing objects in a loop, take a look at the
1516L</"Note on object locking">.
1517
1518=head2 Removing select objects of a particular class
1519
1520Combining the syntax of the load and remove methods, you can use the
1521static version of the remove method to remove particular objects:
1522
1523    MT::Foo->remove({ bar => 'baz' });
1524
1525The terms you specify to remove by should be indexed columns. This
1526method will load the object and remove it, firing the callback operations
1527associated with those operations.
1528
1529=head2 Removing all of the objects of a particular class
1530
1531To quickly remove all of the objects of a particular class, call the
1532I<remove_all> method on the class name in question:
1533
1534=over 4
1535
1536=item * MT::Foo->remove_all();
1537
1538=back
1539
1540On success, I<remove_all> will return some true value; on failure, it will
1541return C<undef>, and you can retrieve the error message by calling the
1542I<errstr> method on the class name:
1543
1544    MT::Foo->remove_all
1545        or die "Removing all foo objects failed: ", MT::Foo->errstr;
1546
1547=head2 Removing all the children of an object
1548
1549=over 4
1550
1551=item * $obj->remove_children([ \%param ])
1552
1553=back
1554
1555If your class has registered 'child_classes' as part of it's properties,
1556then this method may be used to remove objects that are associated with
1557the active object.
1558
1559This method is typically used in an overridden 'remove' method.
1560
1561    sub remove {
1562        my $obj = shift;
1563        $obj->remove_children({ key => 'object_id' });
1564        $obj->SUPER::remove(@_);
1565    }
1566
1567The 'key' parameter specified here lets you identify the field name used by
1568the children classes to relate back to the parent class. If unspecified,
1569C<remove_children> will assume the key to be the datasource name of the
1570current class with an '_id' suffix.
1571
1572=head2 Getting the count of a number of objects
1573
1574To determine how many objects meeting a particular set of conditions exist,
1575use the I<count> method:
1576
1577    my $count = MT::Foo->count({ foo => 'bar' });
1578
1579I<count> takes the same arguments (I<\%terms> and I<\%arguments>) as I<load>
1580and I<load_iter>, above.
1581
1582=head2 Determining if an object exists in the datastore
1583
1584To check an object for existence in the datastore, use the I<exists> method:
1585
1586    if ($foo->exists) {
1587        print "Foo $foo already exists!";
1588    }
1589
1590=head2 Counting groups of objects
1591
1592=over 4
1593
1594=item * $obj->count_group_by()
1595
1596=back
1597
1598The count_group_by method can be used to retrieve a list of all the
1599distinct values that appear in a given column along with a count of
1600how many objects carry that value. The routine can also be used with
1601more than one column, in which case it retrieves the distinct pairs
1602(or n-tuples) of values in those columns, along with the counts.
1603Yet more powerful, any SQL expression can be used in place of
1604the column names to count how many object produce any given result
1605values when run through those expressions.
1606
1607  $iter = MT::Foo->count_group_by($terms, {%args, group => $group_exprs});
1608
1609C<$terms> and C<%args> pick out a subset of the MT::Foo objects in the
1610usual way. C<$group_expressions> is an array reference containing the
1611SQL expressions for the values you want to group by. A single row will
1612be returned for each distinct tuple of values resulting from the
1613$group_expressions. For example, if $group_expressions were just a
1614single column (e.g. group => ['created_on']) then a single row would
1615be returned for each distinct value of the 'created_on' column. If
1616$group_expressions were multiple columns, a row would be returned for
1617each distinct pair (or n-tuple) of values found in those columns.
1618
1619Each application of the iterator C<$iter> returns a list in the form:
1620
1621  ($count, $group_val1, $group_val2, ...)
1622
1623Where C<$count> is the number of MT::Foo objects for which the group
1624expressions are the values ($group_val1, $group_val2, ...). These
1625values are in the same order as the corresponding group expressions in
1626the $group_exprs argument.
1627
1628In this example, we load up groups of MT::Pip objects, grouped by the
1629pair (cat_id, invoice_id), and print how many pips have that pair of
1630values.
1631
1632    $iter = MT::Pip->count_group_by(undef,
1633                                    {group => ['cat_id',
1634                                               'invoice_id']});
1635    while (($count, $cat, $inv) = $iter->()) {
1636        print "There are $count Pips with " .
1637            "category $cat and invoice $inv\n";
1638    }
1639
1640=head2 Inspecting and Manipulating Object State
1641
1642=over 4
1643
1644=item * $obj->column_values()
1645
1646=back
1647
1648Use C<column_values> and C<set_values> to get and set the fields of an
1649object I<en masse>. The former returns a hash reference mapping column
1650names to their values in this object. For example:
1651
1652    $values = $obj->column_values()
1653
1654=over 4
1655
1656=item * $obj->set_values()
1657
1658=back
1659
1660C<set_values> accepts a similar hash ref, which need not give a value
1661for every field. For example:
1662
1663    $obj->set_values({col1 => $val1, col2 => $val2});
1664
1665is equivalent to
1666
1667    $obj->col1($val1);
1668    $obj->col2($val2);
1669
1670=head2 Other Methods
1671
1672=over 4
1673
1674=item * $obj->clone([\%param])
1675
1676Returns a clone of C<$obj>. That is, a distinct object which has all
1677the same data stored within it. Changing values within one object does
1678not modify the other.
1679
1680An optional C<except> parameter may be provided to exclude particular
1681columns from the cloning operation. For example, the following would
1682clone the elements of the blog except the name attribute.
1683
1684   $blog->clone({ except => { name => 1 } });
1685
1686=item * $obj->column_names()
1687
1688Returns a list of the names of columns in C<$obj>; includes all those
1689specified to the install_properties method as well as the audit
1690properties (C<created_on>, C<modified_on>, C<created_by>,
1691C<modified_by>), if those were enabled in install_properties.
1692
1693=item * $obj->set_driver()
1694
1695This method sets the object driver to use to link with a database.
1696
1697=item * MT::Foo->driver()
1698
1699=item * $obj->driver()
1700
1701Returns the ObjectDriver object that links this object with a database.
1702
1703=item * $obj->created_on_obj()
1704
1705Returns an MT::DateTime object representing the moment when the
1706object was first saved to the database.
1707
1708=item * MT::Foo->set_by_key($key_terms, $value_terms)
1709
1710A convenience method that loads whatever object matches the C<$key_terms>
1711argument and sets some or all of its fields according to the
1712C<$value_terms>. For example:
1713
1714   MT::Foo->set_by_key({name => 'Thor'},
1715                       {region => 'Norway', gender => 'Male'});
1716
1717This loads the C<MT::Foo> object having 'name' field equal to 'Thor'
1718and sets the 'region' and 'gender' fields appropriately.
1719
1720More than one term is acceptable in the C<$key_terms> argument. The
1721matching object is the one that matches all of the C<$key_terms>.
1722
1723This method only useful if you know that there is a unique object
1724matching the given key. There need not be a unique constraint on the
1725columns named in the C<$key_hash>; but if not, you should be confident
1726that only one object will match the key.
1727
1728=item * MT::Foo->get_by_key($key_terms)
1729
1730A convenience method that loads whatever object matches the C<$key_terms>
1731argument. If no matching object is found, a new object will be constructed
1732and the C<$key_terms> provided will be assigned to it. So regardless of
1733whether the key exists already, this method will return an object with the
1734key requested. Note, however: if a new object is instantiated it is
1735not automatically saved.
1736
1737    my $thor = MT::Foo->get_by_key({name => 'Thor'});
1738    $thor->region('Norway');
1739    $thor->gender('Male');
1740    $thor->save;
1741
1742The fact that it returns a new object if one isn't found is to help
1743optimize this pattern:
1744
1745    my $obj = MT::Foo->load({key => $value});
1746    if (!$obj) {
1747        $obj = new MT::Foo;
1748        $obj->key($value);
1749    }
1750
1751This is equivalent to:
1752
1753    my $obj = MT::Foo->get_by_key({key => $value});
1754
1755If you don't appreciate the autoinstantiation behavior of this method,
1756just use the C<load> method instead.
1757
1758More than one term is acceptable in the C<$key_terms> argument. The
1759matching object is the one that matches all of the C<$key_terms>.
1760
1761This method only useful if you know that there is a unique object
1762matching the given key. There need not be a unique constraint on the
1763columns named in the C<$key_hash>; but if not, you should be confident
1764that only one object will match the key.
1765
1766=item * $obj->cache_property($key, $code)
1767
1768Caches the provided key (e.g. entry, trackback) with the return value
1769of the given code reference (which is often an object load call) so
1770that the value does not have to be recomputed each time.
1771
1772=item * $obj->column_def($name)
1773
1774This method returns the value of the given I<$name> C<column_defs>
1775propery.
1776
1777=item * $obj->column_defs()
1778
1779This method returns all the C<column_defs> of the property of the
1780object.
1781
1782=item * $obj->to_hash()
1783
1784TODO - So far I have not divined what this method actually does. Hints?
1785
1786=item * Class->join_on()
1787
1788This method returns the list of used by the join arguments parameter
1789used by the L<MT::App::CMS/listing> method.
1790
1791=item * $obj->properties()
1792
1793TODO - Return the return properties of the object.
1794
1795=item * $obj->to_xml()
1796
1797TODO - Returns the XML representation of the object.
1798This method is defined in MT/BackupRestore.pm - you must first
1799use MT::BackupRestore to use this method.
1800
1801=item * $obj->restore_parent_ids()
1802
1803TODO - Backup file contains parent objects' ids (foreign keys).  However,
1804when parent objcects are restored, their ids will be changed.  This method
1805is to match the old and new ids of parent objects for children objects to be
1806correctly associated.
1807This method is defined in MT/BackupRestore.pm - you must first
1808use MT::BackupRestore to use this method.
1809
1810=item * $obj->parent_names()
1811
1812TODO - Should be overridden by subclasses to return correct hash
1813whose keys are xml element names of the object's parent objects
1814and values are class names of them.
1815This method is defined in MT/BackupRestore.pm - you must first
1816use MT::BackupRestore to use this method.
1817
1818=back
1819
1820=head1 NOTES
1821
1822=head2 Note on object locking
1823
1824When you read objects from the datastore, the object table is locked with a
1825shared lock; when you write to the datastore, the table is locked with an
1826exclusive lock.
1827
1828Thus, note that saving or removing objects in the same loop where you are
1829loading them from an iterator will not work--the reason is that the datastore
1830maintains a shared lock on the object table while objects are being loaded
1831from the iterator, and thus the attempt to gain an exclusive lock when saving
1832or removing an object will cause deadlock.
1833
1834For example, you cannot do the following:
1835
1836    my $iter = MT::Foo->load_iter({ foo => 'bar' });
1837    while (my $foo = $iter->()) {
1838        $foo->remove;
1839    }
1840
1841Instead you should do either this:
1842
1843    my @foo = MT::Foo->load({ foo => 'bar' });
1844    for my $foo (@foo) {
1845        $foo->remove;
1846    }
1847
1848or this:
1849
1850    my $iter = MT::Foo->load_iter({ foo => 'bar' });
1851    my @to_remove;
1852    while (my $foo = $iter->()) {
1853        push @to_remove, $foo
1854            if SOME CONDITION;
1855    }
1856    for my $foo (@to_remove) {
1857        $foo->remove;
1858    }
1859
1860This last example is useful if you will not be removing every I<MT::Foo>
1861object where I<foo> equals C<bar>, because it saves memory--only the
1862I<MT::Foo> objects that you will be deleting are kept in memory at the same
1863time.
1864
1865=head1 CALLBACKS
1866
1867=over 4
1868
1869=item * $obj->add_callback()
1870
1871=back
1872
1873Most MT::Object operations can trigger callbacks to plugin code. Some
1874notable uses of this feature are: to be notified when a database record is
1875modified, or to pre- or post-process the data being flowing to the
1876database.
1877
1878To add a callback, invoke the C<add_callback> method of the I<MT::Object>
1879subclass, as follows:
1880
1881   MT::Foo->add_callback( "pre_save", <priority>,
1882                          <plugin object>, \&callback_function);
1883
1884The first argument is the name of the hook point. Any I<MT::Object>
1885subclass has a pre_ and a post_ hook point for each of the following
1886operations:
1887
1888    load
1889    save
1890    remove
1891    remove_all
1892    (load_iter operations will call the load callbacks)
1893
1894The second argument, E<lt>priorityE<gt>, is the relative order in
1895which the callback should be called. The value should be between 1 and
189610, inclusive. Callbacks with priority 1 will be called before those
1897with 2, 2 before 3, and so on.
1898
1899Plugins which know they need to run first or last can use the priority
1900values 0 and 11. A callback with priority 0 will run before all
1901others, and if two callbacks try to use that value, an error will
1902result. Likewise priority 11 is exclusive, and runs last.
1903
1904How to remember which callback priorities are special? As you know,
1905most guitar amps have a volume knob that goes from 1 to 10. But, like
1906that of certain rock stars, our amp goes up to 11. A callback with
1907priority 11 is the "loudest" or most powerful callback, as it will be
1908called just before the object is saved to the database (in the case of
1909a 'pre' callback), or just before the object is returned (in the case
1910of a 'post' callback). A callback with priority 0 is the "quietest"
1911callback, as following callbacks can completely overwhelm it. This may
1912be a good choice for your plugin, as you may want your plugin to work
1913well with other plugins. Determining the correct priority is a matter
1914of thinking about your plugin in relation to others, and adjusting the
1915priority based on experience so that users get the best use out of the
1916plugin.
1917
1918The E<lt>plugin objectE<gt> is an object of type MT::Plugin which
1919gives some information about the plugin. This is used to include
1920the plugin's name in any error messages.
1921
1922E<lt>callback functionE<gt> is a code referense for a subroutine that
1923will be called. The arguments to this
1924function vary by operation (see I<MT::Callback> for details),
1925but in each case the first parameter is the I<MT::Callback> object
1926itself:
1927
1928  sub my_callback {
1929      my ($cb, ...) = @_;
1930
1931      if ( <error condition> ) {
1932          return $cb->error("Error message");
1933      }
1934  }
1935
1936Strictly speaking, the return value of a callback is ignored. Calling
1937the error() method of the MT::Callback object (C<$cb> in this case)
1938propagates the error message up to the MT activity log.
1939
1940Another way to handle errors is to call C<die>. If a callback dies,
1941I<MT> will warn the error to the activity log, but will continue
1942processing the MT::Object operation: so other callbacks will still
1943run, and the database operation should still occur.
1944
1945=head2 Any-class Object Callbacks
1946
1947If you add a callback to the MT class with a hook point that begins
1948with C<*::>, such as:
1949
1950    MT->add_callback('*::post_save', 7, $my_plugin, \&code_ref);
1951
1952then it will be called whenever post_save callbacks are called.
1953"Any-class" callbacks are called I<after> all class-specific
1954callbacks. Note that C<add_callback> must be called on the C<MT> class,
1955not on a subclass of C<MT::Object>.
1956
1957=over 4
1958
1959=item * $obj->set_callback_routine()
1960
1961This method just calls the set_callback_routine as defined by the
1962MT::ObjectDriver set with the I<set_driver> method.
1963
1964
1965=back
1966
1967=head2 Caveat
1968
1969Be careful how you handle errors. If you transform data as it goes
1970into and out of the database, and it is possible for one of your
1971callbacks to fail, the data may get saved in an undefined state. It
1972may then be difficult or impossible for the user to recover that data.
1973
1974=head1 AUTHOR & COPYRIGHTS
1975
1976Please see the I<MT> manpage for author, copyright, and license information.
1977
1978=cut
Note: See TracBrowser for help on using the browser.