root/branches/release-40/lib/MT/Object.pm @ 2615

Revision 2615, 77.5 kB (checked in by mpaschal, 18 months ago)

Remove single id_field index
(tests show no performance difference against mysql)
BugzID: 80212

  • 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 %meta;
56
57    my $super_props = $class->SUPER::properties();
58    $props->{meta} = 1 if $super_props && $super_props->{meta};
59
60    if ($props->{meta}) {
61        # yank out any meta columns before we start working on column_defs
62        $meta{$_} = delete $props->{column_defs}{$_}
63            for grep { $props->{column_defs}{$_} =~ m/\bmeta\b/ }
64            keys %{ $props->{column_defs} };
65    }
66
67    if ($super_props) {
68        # subclass; merge hash
69        for (qw(primary_key class_column datasource driver audit)) {
70            $props->{$_} = $super_props->{$_}
71                if exists $super_props->{$_} && !(exists $props->{$_});
72        }
73        for my $p (qw(column_defs defaults indexes)) {
74            if (exists $super_props->{$p}) {
75                foreach my $k (keys %{ $super_props->{$p} }) {
76                    if (!exists $props->{$p}{$k}) {
77                        $props->{$p}{$k} = $super_props->{$p}{$k};
78                    }
79                }
80                if ($p eq 'column_defs') {
81                    $class->__parse_defs($props->{column_defs});
82                }
83            }
84        }
85        if ($super_props->{class_type}) {
86            # copy reference of class_to_type/type_to_class hashes
87            $props->{__class_to_type} = $super_props->{__class_to_type};
88            $props->{__type_to_class} = $super_props->{__type_to_class};
89        }
90    }
91
92    # Legacy MT::Object types only define 'columns'; we still support that
93    # but they aren't handled properly with the upgrade system as a result.
94    if (! exists $props->{column_defs}) {
95        map { $props->{column_defs}{$_} = () } @{ $props->{columns} };
96    }
97    $props->{columns} = [ keys %{ $props->{column_defs} } ];
98
99    # Support audit flags
100    if ($props->{audit}) {
101        unless (exists $props->{column_defs}{created_on}) {
102            $props->{column_defs}{created_on} = 'datetime';
103            $props->{column_defs}{created_by} = 'integer';
104            $props->{column_defs}{modified_on} = 'datetime';
105            $props->{column_defs}{modified_by} = 'integer';
106            push @{ $props->{columns} }, qw( created_on created_by modified_on modified_by );
107        }
108    }
109
110    # Classed object types
111    $props->{class_column} ||= 'class' if exists $props->{class_type};
112    if (my $col = $props->{class_column}) {
113        if (!$props->{column_defs}{$col}) {
114            $props->{column_defs}{$col} = 'string(255)';
115            push @{$props->{columns}}, $col;
116            $props->{indexes}{$col} = 1;
117        }
118        if (!$super_props || !$super_props->{class_column}) {
119            $class->add_trigger( pre_search => \&_pre_search_scope_terms_to_class );
120            $class->add_trigger( post_load => \&_post_load_rebless_object );
121        }
122        if (my $type = $props->{class_type}) {
123            $props->{defaults}{$col} = $type;
124            $props->{__class_to_type}{$class} = $type;
125            $props->{__type_to_class}{$type} = $class;
126        }
127    }
128
129    my $type_id;
130    if ($type_id = $props->{class_type}) {
131        if ($type_id ne $props->{datasource}) {
132            $type_id = $props->{datasource} . '.' . $type_id;
133        }
134    } else {
135        $type_id = $props->{datasource};
136    }
137
138    $class->SUPER::install_properties($props);
139
140    # check for any supplemental columns from other components
141    my $more_props = MT->registry('object_types', $type_id);
142    if ($more_props && (ref($more_props) eq 'ARRAY')) {
143        my $cols = {};
144        for my $prop (@$more_props) {
145            next if ref($prop) ne 'HASH';
146            MT::__merge_hash($cols, $prop, 1);
147        }
148        my @classes = grep { !ref($_) } @$more_props;
149        foreach my $isa_class (@classes) {
150            next if UNIVERSAL::isa($class, $isa_class);
151            eval "# line " . __LINE__ . " " . __FILE__ . "\nno warnings 'all';require $isa_class;" or die;
152            no strict 'refs'; ## no critic
153            push @{$class . '::ISA'}, $isa_class;
154        }
155        if (%$cols) {
156            # special case for 'plugin' key...
157            delete $cols->{plugin} if exists $cols->{plugin};
158            for my $name (keys %$cols) {
159                next if exists $props->{column_defs}{$name};
160                if ($cols->{$name} =~ m/\bmeta\b/) {
161                    $meta{$name} = $cols->{$name};
162                    next;
163                }
164
165                $class->install_column($name, $cols->{$name});
166                $props->{indexes}{$name} = 1
167                    if $cols->{$name} =~ m/\bindexed\b/;
168                if ($cols->{$name} =~ m/\bdefault (?:'([^']+?)'|(\d+))\b/) {
169                    $props->{defaults}{$name} = defined $1 ? $1 : $2;
170                }
171            }
172        }
173    }
174
175    my $pk = $props->{primary_key} || '';
176    @{$props->{columns}} = sort { $a eq $pk ? -1 : $b eq $pk ? 1 : $a cmp $b }
177        @{$props->{columns}};
178
179    # Child classes are declared as an array;
180    # convert them to a hashref for easier lookup.
181    if ((ref $props->{child_classes}) eq 'ARRAY') {
182        my $classes = $props->{child_classes};
183        $props->{child_classes} = {};
184        @{$props->{child_classes}}{@$classes} = ();
185    }
186
187    # We're declared as a child of some other class; associate ourselves
188    # with that package (the invoking class should have already use'd it.)
189    if (exists $props->{child_of}) {
190        my $parent_classes = $props->{child_of};
191        if (!ref $parent_classes) {
192            $parent_classes = [ $parent_classes ];
193        }
194        foreach my $pc (@$parent_classes) {
195            my $pp = $pc->properties;
196            $pp->{child_classes} ||= {};
197            $pp->{child_classes}{$class} = ();
198        }
199    }
200
201    # Special handling for 'Taggable' objects; automatic saving
202    # and removal of tags.
203    my @isa;
204    {
205        no strict 'refs';
206        @isa = @{ $class . '::ISA' };
207    }
208    foreach my $isa_pkg ( @isa ) {
209        next unless $isa_pkg =~ /able$/;
210        next if $isa_pkg eq $class;
211        if ($isa_pkg->can('install_properties')) {
212            $isa_pkg->install_properties($class);
213        }
214    }
215
216    # install legacy date translation
217    if (0 < scalar @{ $class->columns_of_type('datetime', 'timestamp') }) {
218        if ($props->{audit}) {
219            $class->add_trigger( pre_save  => \&_assign_audited_fields);
220            $class->add_trigger( post_save => \&_translate_audited_fields );
221        }
222
223        $class->add_trigger( pre_save  => _get_date_translator(\&_ts2db, 1) );
224        $class->add_trigger( post_load => _get_date_translator(\&_db2ts, 0) );
225    }
226
227    if ( exists($props->{cacheable}) && !$props->{cacheable} ) {
228        no warnings 'redefine';
229        no strict 'refs'; ## no critic
230        *{$class . '::driver'} = sub { $_[0]->dbi_driver(@_) };
231    }
232
233    # inherit parent's metadata setup
234    if ($props->{meta}) { # if ($super_props && $super_props->{meta_installed}) {
235        $class->install_meta({ ( %meta ? ( column_defs => \%meta ) : ( columns => [] ) ) });
236        $class->add_trigger( post_remove => \&remove_meta );
237    }
238
239    return $props;
240}
241
242# A post-load trigger for classed objects
243sub _post_load_rebless_object {
244    my $obj = shift;
245    my $props = $obj->properties;
246    if (my $col = $props->{class_column}) {
247        my $type = $obj->column($col);
248        my $pkg = ref($obj);
249        if ($pkg->class_type ne $type) {
250            if (my $class = $props->{__type_to_class}{$type}) {
251                bless $obj, $class;
252            } else {
253                my %models = map { $_ => 1 } MT->models($props->{datasource});
254                if (exists $models{ $props->{datasource} . '.' . $type}) {
255                    $class = MT->model($props->{datasource} . '.' . $type);
256                } elsif (exists $models{$type}) {
257                    $class = MT->model($type);
258                }
259                bless $obj, $class if $class;
260            }
261        }
262    }
263}
264
265# A pre-search trigger for classed objects
266sub _pre_search_scope_terms_to_class {
267    my ($class, $terms, $args) = @_;
268    # scope search terms to class
269
270    $terms ||= {};
271    return if (ref $terms eq 'HASH') && exists($terms->{id});
272
273    my $props = $class->properties;
274    my $col = $props->{class_column}
275        or return;
276    if (ref $terms eq 'HASH') {
277        my $no_class = 0;
278        if ($args->{no_class}) {
279            delete $args->{no_class};
280            $no_class = 1;
281        }
282        if (exists $terms->{$col}) {
283            if ( ( $terms->{$col} eq '*' ) || $no_class ) {
284                # class term is '*', which signifies filtering for all classes.
285                # simply delete the term in this case.
286                delete $terms->{$col} ;
287            } elsif ($terms->{$col} =~ m/^(\w+:)\*$/) {
288                # class term is in form "foo:*"; translate to a sql-compatible
289                # syntax of "like 'foo:%'"
290                $terms->{$col} = \"like '$1%'";
291            }
292            # term has been explicitly given or explictly removed. make
293            # no further changes.
294            return;
295        }
296        $terms->{$col} = $props->{class_type}
297            unless $no_class;
298    }
299    elsif (ref $terms eq 'ARRAY') {
300        if (my @class_terms = grep { ref $_ eq 'HASH' && 1 == scalar keys %$_ && $_->{$col} } @$terms) {
301            # Filter out any unlimiting class terms (class = *).
302            @$terms = grep { ref $_ ne 'HASH' || 1 != scalar keys %$_ || !$_->{$col} || $_->{$col} ne '*' } @$terms;
303
304            # The class column has been explicitly given or removed, so don't
305            # add one.
306            return;
307        }
308        @$terms = ( { $col => $props->{class_type} } => 'AND' => [ @$terms ] );
309    }
310}
311
312sub class_label {
313    my $pkg = shift;
314    return MT->translate($pkg->datasource);
315}
316
317sub class_label_plural {
318    my $pkg = shift;
319    my $label = $pkg->datasource;
320    $label =~ s/y$/ie/;
321    $label .= 's';
322    return MT->translate($label);
323}
324
325sub class_labels {
326    my $pkg = shift;
327    my @all_types = MT->models($pkg->properties->{datasource});
328    my %names;
329    foreach my $type (@all_types) {
330        my $class = $pkg->class_handler($type);
331        $names{$type} = $class->class_label;
332    }
333    return \%names;
334}
335
336# Returns a hashref of asset identifiers mapped to the localized string
337# used to name them. (Ie, image => 'Image').
338sub class_type {
339    my $pkg = shift;
340    if (ref $pkg) {
341        return $pkg->column($pkg->properties->{class_column});
342    } else {
343        return $pkg->properties->{class_type};
344    }
345}
346
347sub class_handler {
348    my $pkg = shift;
349    my $props = $pkg->properties;
350    my ($type) = @_;
351    my $package = $props->{__type_to_class}{$type};
352    unless ($package) {
353        my $ds = $props->{datasource};
354        if (($type eq $ds) || ($type =~ m/\./)) {
355            $package = MT->model($type);
356        } else {
357            $package = MT->model($ds . '.' . $type);
358        }
359    }
360    if ($package) {
361        if (defined *{$package.'::new'}) {
362            return $package;
363        } else {
364            eval "# line " . __LINE__ . " " . __FILE__ . "\nno warnings 'all';use $package;";
365            return $package unless $@;
366            eval "# line " . __LINE__ . " " . __FILE__ . "\nno warnings 'all';use $pkg; $package->new;";
367            return $package unless $@;
368        }
369    }
370    return $pkg;
371}
372
373sub add_class {
374    my $pkg = shift;
375    my ($type, $class) = @_;
376    my $props = $pkg->properties;
377    if ($type =~ m/::/) {
378        ($type, $class) = ($class, $type);
379    }
380
381    if (my $old_class = $props->{__type_to_class}{$type}) {
382        delete $props->{__class_to_type}{$old_class};
383    }
384    $props->{__type_to_class}{$type} = $class;
385    $props->{__class_to_type}{$class} = $type;
386}
387
388# 'meta' metadata column support
389
390sub new {
391    my $class = shift;
392    my $obj = $class->SUPER::new(@_);
393    if ($obj->properties->{meta_installed}) {
394        $obj->init_meta();
395    }
396    return $obj;
397}
398
399sub init_meta {
400    my $obj = shift;
401    require MT::Meta::Proxy;
402    $obj->{__meta} = MT::Meta::Proxy->new($obj);
403}
404
405sub install_meta {
406    my $class = shift;
407    my ($params) = @_;
408    if ( ( $class ne 'MT::Config' ) && (!$MT::plugins_installed) ) {
409        push @PRE_INIT_META, [$class, $params];
410        return;
411    }
412
413    require MT::Meta;
414    my $pkg = ref $class || $class;
415    if (!$pkg->SUPER::properties->{meta_installed}) {
416        $pkg->add_trigger( post_save => \&_post_save_save_metadata );
417        $pkg->add_trigger( post_load => \&_post_load_initialize_metadata );
418    }
419
420    my $props = $class->properties;
421
422    if (!$params->{columns} && !$params->{fields} && !$params->{column_defs}) {
423        return $class->error('No meta fields specified to install_meta');
424    }
425
426    $params->{fields} ||= [];
427    if (my $cols = delete $params->{columns}) {
428        foreach my $col (@$cols) {
429            push @{ $params->{fields} }, {
430                name => $col,
431                type => 'vblob',
432            };
433            # $props->{fields}{$col} = 'vblob';
434        }
435    }
436    if (my $cols = delete $params->{column_defs}) {
437        foreach my $col ( keys %$cols ) {
438            my $type = $cols->{$col};
439            $type =~ s/\s.*//; # take first keyword, ignoring anything after
440            $type .= '_indexed'
441                if $cols->{$col} =~ m/\bindexed\b/;
442            $type = MT::Meta->normalize_type($type);
443
444            push @{ $params->{fields} }, {
445                name => $col,
446                type => $type,
447            };
448            # $props->{fields}{$col} = $type;
449        }
450    }
451
452    $params->{datasource} ||= $class->datasource . '_meta';
453
454    if ($props->{meta_installed} && !@{ $params->{fields} }) {
455        return 1;
456    }
457
458    if (my $fields = MT::Meta->install($pkg, $params)) {
459        # we may have inherited meta fields so lets update with
460        # the fields returned by MT::Meta
461        $props->{fields}->{$_} = $fields->{$_} for keys %$fields;
462    }
463
464    return $props->{meta_installed} = 1;
465}
466
467sub meta_args {
468    my $class = shift;
469    my $id_field = $class->datasource . '_id';
470    return {
471        key         => $class->datasource,
472        column_defs => {
473            $id_field         => 'integer not null',
474            type              => 'string(75) not null',
475            vchar             => 'string(255)',
476            vchar_idx         => 'string(255)',
477            vdatetime         => 'datetime',
478            vdatetime_idx     => 'datetime',
479            vinteger          => 'integer',
480            vinteger_idx      => 'integer',
481            vfloat            => 'float',
482            vfloat_idx        => 'float',
483            vblob             => 'blob',
484            vclob             => 'text',
485        },
486        columns => [ $id_field, qw(
487            type
488            vchar
489            vchar_idx
490            vdatetime
491            vdatetime_idx
492            vinteger
493            vinteger_idx
494            vfloat
495            vfloat_idx
496            vblob
497            vclob
498        ) ],
499        indexes => {
500            id_type    => { columns => [ $id_field, 'type' ] },
501            type_vchar => { columns => [ 'type', 'vchar_idx'     ] },
502            type_vdt   => { columns => [ 'type', 'vdatetime_idx' ] },
503            type_vint  => { columns => [ 'type', 'vinteger_idx'  ] },
504            type_vflt  => { columns => [ 'type', 'vfloat_idx'    ] },
505        },
506        primary_key => [ $id_field, 'type' ],
507    };
508}
509
510sub has_meta {
511    my $obj = shift;
512    return $obj->is_meta_column(@_) if @_;
513    return $obj->properties->{meta_installed} ? 1 : 0;
514}
515
516sub _post_load_initialize_metadata {
517    my $obj = shift;
518    if (defined $obj && $obj->properties->{meta_installed}) {
519        $obj->init_meta();
520        $obj->{__meta}->set_primary_keys($obj);
521    }
522}
523
524sub is_meta_column {
525    my $obj = shift;
526    my ($field) = @_;
527
528    my $props = $obj->properties;
529    return unless $props->{meta_installed};
530
531    my $meta = $obj->meta_pkg;
532    return 1 if $props->{fields}{$field};
533
534    return;
535}
536
537sub meta_pkg {
538    my $class = shift;
539    my $props = $class->properties;
540    return unless $props->{meta}; # this only works for meta-enabled classes
541
542    return $props->{meta_pkg} if $props->{meta_pkg};
543
544    my $meta = ref $class || $class;
545    $meta .= '::Meta';
546    return $props->{meta_pkg} = $meta;
547}
548
549sub has_column {
550    my $obj = shift;
551    return 1 if $obj->SUPER::has_column(@_);
552    return 1 if $obj->is_meta_column(@_);
553    return;
554}
555
556sub _post_save_save_metadata {
557    my $obj = shift;
558    if (defined $obj && exists $obj->{__meta}) {
559        $obj->{__meta}->set_primary_keys($obj);
560        $obj->{__meta}->save;
561    }
562}
563
564sub meta {
565    my $obj = shift;
566    my ( $name, $value ) = @_;
567
568    return !$obj->{__meta} ? undef
569         : 2 == scalar @_  ? $obj->{__meta}->set( $name, $value )
570         : 1 == scalar @_  ? (
571           ref($name) eq 'HASH' ? $obj->{__meta}->set_hash(@_)
572             :                    $obj->{__meta}->get($name) )
573         :                   $obj->{__meta}->get_hash;
574}
575
576sub meta_obj {
577    my $obj = shift;
578    return $obj->{__meta};
579}
580
581sub column_func {
582    my $obj = shift;
583    my ($col) = @_;
584    return if !$col;
585
586    return $obj->SUPER::column_func(@_)
587        if !$obj->is_meta_column($col);
588
589    return sub {
590        my $obj = shift;
591        if (@_) {
592            $obj->{__meta}->set($col, @_);
593        }
594        else {
595            $obj->{__meta}->get($col);
596        }
597    };
598}
599
600sub _ts2db { 
601    return unless $_[0]; 
602    if($_[0] =~ m{ \A \d{4} - }xms) { 
603        return $_[0]; 
604    } 
605    my $ret = sprintf '%04d-%02d-%02d %02d:%02d:%02d', unpack 'A4A2A2A2A2A2', $_[0]; 
606    return $ret; 
607}
608 
609sub _db2ts { 
610    my $ts = $_[0];
611    $ts =~ s/(?:\+|-)\d{2}$//;
612    $ts =~ tr/\- ://d;
613    return $ts;
614}
615
616sub _get_date_translator {
617    my $translator = shift;
618    my $change = shift;
619    return sub {
620        my $obj = shift;
621        my $dbd = $obj->driver->dbd;
622        FIELD: for my $field (@{$obj->columns_of_type('datetime', 'timestamp')}) {
623            my $value = $obj->column($field);
624            next FIELD if !defined $value;
625            my $new_val = $translator->($value); 
626            if((defined $new_val) && ($new_val ne $value)) {
627                $obj->column($field, $new_val, { no_changed_flag => !$change });
628            }
629        }
630        if ( $obj->has_meta ) {
631            my @meta_columns = MT::Meta->metadata_by_class( ref $obj );
632            my @date_meta = grep {
633                   $_->{type} eq 'vdatetime'
634                || $_->{type} eq 'vdatetime_idx'
635            } @meta_columns;
636            META_FIELD: for my $f (@date_meta) {
637                my $field = $f->{name};
638                my $value = $obj->$field;
639                next META_FIELD if !defined $value;
640                my $new_val = $translator->($value); 
641                if((defined $new_val) && ($new_val ne $value)) {
642                    $obj->$field( $new_val );
643                }
644            }
645        }
646    };
647}
648
649sub _translate_audited_fields {
650    my ($obj, $orig_obj) = @_;
651    my $dbd = $obj->driver->dbd;
652    FIELD: for my $field (qw( created_on modified_on )) {
653        my $value = $orig_obj->column($field);
654        next FIELD if !defined $value;
655        my $new_val = _db2ts($value); 
656        if((defined $new_val) && ($new_val ne $value)) {
657            $orig_obj->column($field, $new_val);
658        }
659    }
660    return;
661}
662
663sub nextprev {
664    my $obj = shift;
665    my $class = ref($obj);
666    my %param = @_;
667    my ($direction, $terms, $args, $by_field)
668        = @param{qw( direction terms args by )};
669    return undef unless ($direction eq 'next' || $direction eq 'previous');
670    my $next = $direction eq 'next';
671
672    if (!$by_field) {
673        return if !$class->properties->{audit};
674        $by_field = 'created_on';
675    }
676
677    # Selecting the adjacent object can be tricky since timestamps
678    # are not necessarily unique for entries. If we find that the
679    # next/previous object has a matching timestamp, keep selecting entries
680    # to select all entries with the same timestamp, then compare them using
681    # id as a secondary sort column.
682
683    my ($id, $ts) = ($obj->id, $obj->$by_field());
684    local @$args{qw( sort range_incl )}
685        = ( [ { column => $by_field, desc => $next ? 'ASC' : 'DESC' },
686            { column => 'id', desc => $next ? 'ASC' : 'DESC' } ],
687            { $by_field => 1 });
688
689    my $sibling = $class->load({
690        $by_field => ($next ? [ $ts, undef ] : [ undef, $ts ]),
691        'id' => $id,
692        %{$terms}
693    }, { not => { 'id' => 1 }, limit => 1, %$args });
694
695    return $sibling;
696}
697
698## Drivers.
699
700sub count          { shift->_proxy('count',          @_) }
701sub exist          { shift->_proxy('exist',          @_) }
702sub count_group_by { shift->_proxy('count_group_by', @_) }
703sub sum_group_by   { shift->_proxy('sum_group_by',   @_) }
704sub avg_group_by   { shift->_proxy('avg_group_by',   @_) }
705sub max_group_by   { shift->_proxy('max_group_by',   @_) }
706sub remove_all     { shift->_proxy('remove_all',     @_) }
707
708sub save {
709    my $obj = shift;
710    my $res = eval {
711        my $dbh = $obj->driver->rw_handle;
712        local $dbh->{RaiseError} = 1;
713        $obj->SUPER::save(@_);
714    };
715    if (my $err = $@) {
716        return $obj->error($err);
717    }
718    return $res;
719}
720
721sub remove {
722    my $obj = shift;
723    my(@args) = @_;
724    if (!ref $obj) {
725        $obj->remove_meta( @args ) if $obj->has_meta;
726        $obj->remove_scores( @args ) if $obj->isa('MT::Scorable');
727        return $obj->driver->direct_remove($obj, @args);
728    } else {
729        return $obj->driver->remove($obj, @args);
730    }
731}
732
733sub load {
734    my $self = shift;
735    if (defined $_[0] && (!ref $_[0] || (ref $_[0] ne 'HASH' && ref $_[0] ne 'ARRAY'))) {
736        return $self->lookup($_[0]);
737    } else {
738        if (wantarray) {
739            ## MT::Object::load returns a list in list context, just like
740            ## a D::OD search.
741            return $self->search(@_);
742        } else {
743            ## MT::Object::load returns the first result in scalar context.
744            my ($terms, $args) = @_;
745            $args ||= {};
746            local $args->{limit} = 1;
747            my $iter = $self->search($terms, $args);
748            return if !defined $iter;
749            return $iter->();
750        }
751    }
752}
753
754sub load_iter {
755    my $class = shift;
756    my($terms, $args) = @_;
757    $args ||= {};
758    local $args->{window_size} = 100
759        unless defined($args->{window_size});
760    return scalar $class->search($terms, $args);
761}
762
763## Callbacks
764
765sub _assign_audited_fields {
766    my ($obj, $orig_obj) = @_;
767    if ($obj->properties->{audit}) {
768        my $blog_id;
769        if ($obj->has_column('blog_id')) {
770            $blog_id = $obj->blog_id;
771        }
772        my @ts = offset_time_list(time, $blog_id);
773        my $ts = sprintf '%04d%02d%02d%02d%02d%02d',
774            $ts[5]+1900, $ts[4]+1, @ts[3,2,1,0];
775
776        my $app = MT->instance;
777        if ($app && $app->can('user')) {
778            if (my $user = $app->user) {
779                if (!defined $obj->created_on) {
780                    $obj->created_by($user->id);
781                    $orig_obj->created_by($obj->created_by);
782                }
783            }
784        }
785        unless ($obj->created_on) {
786            $obj->created_on($ts);
787            $orig_obj->created_on($ts);
788            # intentionally not calling modified_by to distinguish
789            $obj->modified_on($ts);
790            $orig_obj->modified_on($ts);
791        }
792    }
793}
794
795sub modified_by {
796    my $obj = shift;
797    my ($user_id) = @_;
798    if ($user_id) {
799        if ($obj->properties->{audit}) {
800            my $res = $obj->SUPER::modified_by($user_id);
801
802            my $blog_id;
803            if ($obj->has_column('blog_id')) {
804                $blog_id = $obj->blog_id;
805            }
806            my @ts = offset_time_list(time, $blog_id);
807            my $ts = sprintf '%04d%02d%02d%02d%02d%02d',
808                $ts[5]+1900, $ts[4]+1, @ts[3,2,1,0];
809            $obj->modified_on($ts);
810            return $res;
811        }
812    }
813    return $obj->SUPER::modified_by(@_);
814}
815
816# D::OD uses Class::Trigger. Map the call_trigger calls to also invoke
817# MT's callbacks (but internal Class::Trigger routines should be invoked
818# first in the case of pre-triggers, and last in the case of post-triggers).
819
820sub call_trigger {
821    my $obj = shift;
822    my $name = shift;
823    my $class = ref $obj || $obj;
824    my $pre_trigger = $name =~ m/^pre_/;
825    $obj->SUPER::call_trigger($name, @_) if $pre_trigger;
826    MT->run_callbacks($class . '::' . $name, $obj, @_);
827    $obj->SUPER::call_trigger($name, @_) unless $pre_trigger;
828}
829
830# Support for MT-based callbacks.
831
832sub add_callback {
833    my $class = shift;
834    my $meth = shift;
835    MT->add_callback($class . '::' . $meth, @_);
836}
837
838## Construction/initialization.
839
840sub init {
841    my $obj = shift;
842    $obj->SUPER::init(@_);
843    $obj->set_defaults();
844    return $obj;
845}
846
847sub set_defaults {
848    my $obj = shift;
849    my $defaults = $obj->properties->{'defaults'};
850    $obj->{'column_values'} = $defaults ? {%$defaults} : {};
851}
852
853sub __properties { }
854
855our $DRIVER;
856sub driver {
857    require MT::ObjectDriverFactory;
858    return $DRIVER ||= MT::ObjectDriverFactory->new;
859}
860
861# ref to the fallback driver for non-cacheable classes
862our $DBI_DRIVER;
863sub dbi_driver {
864    unless ($DBI_DRIVER) {
865        my $driver = driver(@_);
866        while ( $driver->can('fallback') ) {
867            if ($driver->fallback) {
868                $driver = $driver->fallback;
869            } else {
870                last;
871            }
872        }
873        $DBI_DRIVER = $driver;
874    }
875    return $DBI_DRIVER;
876}
877
878sub table_name {
879    my $obj = shift;
880    return $obj->driver->table_for($obj);
881}
882
883sub clone_all {
884    my $obj = shift;
885    my $clone = $obj->SUPER::clone_all();
886    if ($clone->properties->{meta_installed}) {
887        $clone->init_meta();
888        $clone->meta( $obj->meta );
889    }
890    return $clone;
891}
892
893sub clone {
894    my $obj = shift;
895    my($param) = @_;
896    my $clone = $obj->clone_all();
897
898    ## If the caller has listed a set of columns not to copy to the clone,
899    ## delete them from the clone.
900    if ($param && ($param->{Except} || $param->{except})) {
901        for my $col (keys %{ $param->{Except} || $param->{except} }) {
902            $clone->$col(undef);
903        }
904    }
905    return $clone;
906}
907
908sub columns_of_type {
909    my $obj = shift;
910    my(@types) = @_;
911    my $props = $obj->properties;
912    my $cols = $props->{columns};
913    my $col_defs = $obj->column_defs;
914    my @cols;
915    my %types = map { $_ => 1 } @types;
916    for my $col (@$cols) {
917        push @cols, $col
918            if $col_defs->{$col} && exists $types{$col_defs->{$col}{type}};
919    }
920    \@cols;
921}
922
923sub created_on_obj {
924    my $obj = shift;
925    return $obj->column_as_datetime('created_on');
926}
927
928sub column_as_datetime {
929    my $obj = shift;
930    my ($col) = @_;
931    if (my $ts = $obj->column($col)) {
932        my $blog;
933        if ($obj->isa('MT::Blog')) {
934            $blog = $obj;
935        } else {
936            if (my $blog_id = $obj->blog_id) {
937                require MT::Blog;
938                $blog = MT::Blog->lookup($blog_id);
939            }
940        }
941        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)/;
942        require MT::DateTime;
943        my $four_digit_offset;
944        if ($blog) {
945            $four_digit_offset = sprintf('%.02d%.02d', int($blog->server_offset),
946                                        60 * abs($blog->server_offset
947                                                 - int($blog->server_offset)));
948        }
949        return new MT::DateTime(year => $y, month => $mo, day => $d,
950                                hour => $h, minute => $m, second => $s,
951                                time_zone => $four_digit_offset);
952    }
953    undef;
954}
955
956sub join_on {
957    return [ @_ ];
958}
959
960sub remove_meta {
961    my $obj = shift;
962    my $mpkg = $obj->meta_pkg or return;
963    if ( ref $obj ) {
964        my $id_field = $obj->datasource . '_id';
965        return $mpkg->remove({ $id_field => $obj->id });
966    } else {
967        # static invocation
968        my ($terms, $args) = @_;
969        $args = { %$args } if $args; # copy so we can alter
970        my $meta_id = $obj->datasource . '_id';
971        my $offset = 0;
972        $args ||= {};
973        $args->{fetchonly} = [ 'id' ];
974        $args->{join} = [ $mpkg, $meta_id ];
975        $args->{no_triggers} = 1;
976        $args->{limit} = 50;
977        while ( $offset >= 0 ) {
978            $args->{offset} = $offset;
979            if (my @list = $obj->load( $terms, $args )) {
980                my @ids = map { $_->id } @list;
981                $mpkg->driver->direct_remove( $mpkg, { $meta_id => \@ids });
982                if ( scalar @list == 50 ) {
983                    $offset += 50;
984                } else {
985                    $offset = -1; # break loop
986                }
987            } else {
988                $offset = -1;
989            }
990        }
991        return 1;
992    }
993}
994
995sub remove_scores {
996    my $class = shift;
997    require MT::ObjectScore;
998    my ($terms, $args) = @_;
999    $args = { %$args } if $args; # copy so we can alter
1000    my $offset = 0;
1001    $args ||= {};
1002    $args->{fetchonly} = [ 'id' ];
1003    $args->{join} = [ 'MT::ObjectScore', 'object_id', {
1004        object_ds => $class->datasource } ];
1005    $args->{no_triggers} = 1;
1006    $args->{limit} = 50;
1007    while ( $offset >= 0 ) {
1008        $args->{offset} = $offset;
1009        if (my @list = $class->load( $terms, $args )) {
1010            my @ids = map { $_->id } @list;
1011            MT::ObjectScore->driver->direct_remove( 'MT::ObjectScore', {
1012                object_ds => $class->datasource, 'object_id' => \@ids });
1013            if ( scalar @list == 50 ) {
1014                $offset += 50;
1015            } else {
1016                $offset = -1; # break loop
1017            }
1018        } else {
1019            $offset = -1;
1020        }
1021    }
1022    return 1;
1023}
1024
1025sub remove_children {
1026    my $obj = shift;
1027    return 1 unless ref $obj;
1028
1029    my ($param) = @_;
1030    my $child_classes = $obj->properties->{child_classes} || {};
1031    my @classes = keys %$child_classes;
1032    return 1 unless @classes;
1033
1034    $param ||= {};
1035    my $key = $param->{key} || $obj->datasource . '_id';
1036    my $obj_id = $obj->id;
1037    for my $class (@classes) {
1038        eval "# line " . __LINE__ . " " . __FILE__ . "\nno warnings 'all';use $class;";
1039        $class->remove({ $key => $obj_id });
1040    }
1041    1;
1042}
1043
1044sub get_by_key {
1045    my $class = shift;
1046    my ($key) = @_;
1047    my($obj) = $class->search($key);
1048    $obj ||= new $class;
1049    $obj->set_values($key);
1050    return $obj;
1051}
1052
1053sub set_by_key {
1054    my $class = shift;
1055    my ($key, $value) = @_;
1056    my ($obj) = $class->search($key);
1057    unless ($obj) {
1058        $obj = new $class;
1059        $obj->set_values($key);
1060    }
1061    $obj->set_values($value) if $value;
1062    $obj->save or return $class->error($obj->errstr);
1063    return $obj;
1064}
1065
1066sub deflate {
1067    my $obj = shift;
1068    my $data = $obj->SUPER::deflate();
1069    if ($obj->has_meta()) {
1070        $data->{meta} = $obj->{__meta}->deflate_meta();
1071    }
1072    return $data;
1073}
1074
1075sub inflate {
1076    my $class = shift;
1077    my ($data) = @_;
1078    my $obj = $class->SUPER::inflate(@_);
1079    if ($class->has_meta()) {
1080        $obj->{__meta}->inflate_meta($data->{meta});
1081    }
1082    return $obj;
1083}
1084
1085# We override D::OD's set_values method here only allowing the
1086# assignment of a column if the value given is defined. There are
1087# some legacy reasons for doing this, mostly for backward
1088# compatibility.
1089sub set_values {
1090    my $obj = shift;
1091    my ($values) = @_;
1092    for my $col (keys %$values) {
1093        unless ( $obj->has_column($col) ) {
1094            Carp::croak("You tried to set inexistent column $col to value $values->{$col} on " . ref($obj));
1095        }
1096        $obj->$col($values->{$col}) if defined $values->{$col};
1097    }
1098}
1099
1100sub column_def {
1101    my $obj = shift;
1102    my ($name) = @_;
1103    my $defs = $obj->column_defs;
1104    my $def = $defs->{$name};
1105    if (!ref($def)) {
1106        $defs->{$name} = $def = $obj->__parse_def($name, $def);
1107    }
1108    return $def;
1109}
1110
1111sub index_defs {
1112    my $obj = shift;
1113    my $props = $obj->properties;
1114    $props->{indexes};
1115}
1116
1117sub column_defs {
1118    my $obj = shift;
1119    my $props = $obj->properties;
1120    my $defs = $props->{column_defs};
1121    return undef if !$defs;
1122    my ($key) = keys %$defs;
1123    if (!(ref $defs->{$key})) {
1124        $obj->__parse_defs($props->{column_defs});
1125    }
1126    $props->{column_defs};
1127}
1128
1129sub __parse_defs {
1130    my $obj = shift;
1131    my ($defs) = @_;
1132    foreach my $col ( keys %$defs ) {
1133        next if ref($defs->{$col});
1134        $defs->{$col} = $obj->__parse_def($col, $defs->{$col});
1135    }
1136}
1137
1138sub __parse_def {
1139    my $obj = shift;
1140    my ($col, $def) = @_;
1141    return undef if !defined $def;
1142    my $props = $obj->properties;
1143    my %def;
1144    if ($def =~ s/^([^( ]+)\s*//) {
1145        $def{type} = $1;
1146    }
1147    if ($def =~ s/^\((\d+)\)\s*//) {
1148        $def{size} = $1;
1149    }
1150    $def{not_null} = 1 if $def =~ m/\bnot null\b/i;
1151    $def{key} = 1 if $def =~ m/\bprimary key\b/i;
1152    $def{key} = 1 if ($props->{primary_key}) && ($props->{primary_key} eq $col);
1153    $def{auto} = 1 if $def =~ m/\bauto[_ ]increment\b/i;
1154    $def{default} = $props->{defaults}{$col}
1155        if exists $props->{defaults}{$col};
1156    \%def;
1157}
1158
1159sub cache_property {
1160    my $obj = shift;
1161    my $key = shift;
1162    my $code = shift;
1163    if (ref $key eq 'CODE') {
1164        ($key, $code) = ($code, $key);
1165    }
1166    $key ||= (caller(1))[3];
1167
1168    my $r = MT->request;
1169    my $oc = $r->cache('object_cache');
1170    unless ($oc) {
1171        $oc = {};
1172        $r->cache('object_cache', $oc);
1173    }
1174
1175    my $pk = $obj->primary_key;
1176    return $code->($obj, @_) unless defined $pk;
1177    $pk = join ":", @$pk if ref $pk;
1178    $oc = $oc->{ref($obj). ':' . $pk} ||= {};
1179
1180    if (@_) {
1181        $oc->{$key} = $_[0];
1182    } else {
1183        if ((!exists $oc->{$key}) && $code) {
1184            $oc->{$key} = $code->($obj, @_);
1185        }
1186    }
1187    return exists $oc->{$key} ? $oc->{$key} : undef;
1188}
1189
1190sub clear_cache {
1191    my $obj = shift;
1192    my $oc = MT->request('object_cache') or return;
1193
1194    my $pk = $obj->primary_key;
1195    $pk = join ":", @$pk if ref $pk;
1196    my $key = ref($obj). ':' . $pk;
1197
1198    if (@_) {
1199        $oc = $oc->{$key};
1200        delete $oc->{shift} if $oc;
1201    } else {
1202        delete $oc->{$key};
1203    }
1204    1;
1205}
1206
1207sub to_hash {
1208    my $obj = shift;
1209    my $hash = {};
1210    my $props = $obj->properties;
1211    my $pfx = $obj->datasource;
1212    my $values = $obj->column_values;
1213    foreach (keys %$values) {
1214        $hash->{"${pfx}.$_"} = $values->{$_};
1215    }
1216    if (my $meta = $props->{meta_columns}) {
1217        foreach (keys %$meta) {
1218            $hash->{"${pfx}.$_"} = $obj->meta($_);
1219        }
1220    }
1221    if ($obj->has_column('blog_id')) {
1222        my $blog_id = $obj->blog_id;
1223        require MT::Blog;
1224        if (my $blog = MT::Blog->lookup($blog_id)) {
1225            my $blog_hash = $blog->to_hash;
1226            $hash->{"${pfx}.$_"} = $blog_hash->{$_} foreach keys %$blog_hash;
1227        }
1228    }
1229    $hash;
1230}
1231
1232sub search_by_meta {
1233    my $class = shift;
1234    my($key, $value, $terms, $args) = @_;
1235    $terms ||= {}; $args ||= {};
1236    return unless $class->properties->{meta_installed};
1237    return $class->error("Unknown meta '$key' on $class")
1238        unless $class->is_meta_column($key);
1239
1240    my $meta_rec = MT::Meta->metadata_by_name($class, $key);
1241    my $type_col = $meta_rec->{type};
1242    my $type_id  = $meta_rec->{name};
1243    my $meta_terms = {
1244        $type_col => $value,
1245        type      => $type_id,
1246        %$terms,
1247    };
1248    my $meta_class = $class->meta_pkg;
1249    my $meta_pk = $meta_class->primary_key_tuple;
1250    my @metaobjs = $meta_class->search(
1251        $meta_terms, { %$args, fetchonly => $meta_pk }
1252    );
1253
1254    my $pk = $class->primary_key_tuple;
1255    my $get_pk = sub { 
1256        my $meta = shift;
1257        [ map { $meta->$_ } @$meta_pk ];
1258    };
1259
1260    return unless @metaobjs;
1261    return grep defined, @{ $class->lookup_multi([ map { $get_pk->($_) } @metaobjs ]) };
1262}
1263
1264package MT::Object::Meta;
1265
1266use base qw( Data::ObjectDriver::BaseObject );
1267
1268sub install_properties {
1269    my $class = shift;
1270    my ($props) = @_;
1271    $props->{column_defs}->{$_} ||= 'string'
1272        for @{ $props->{columns} };
1273    $class->SUPER::install_properties(@_);
1274}
1275
1276sub meta_pkg { undef }
1277
1278*table_name = \&MT::Object::table_name;
1279*column_defs = \&MT::Object::column_defs;
1280*column_def = \&MT::Object::column_def;
1281*index_defs = \&MT::Object::index_defs;
1282*__parse_defs = \&MT::Object::__parse_defs;
1283*__parse_def = \&MT::Object::__parse_def;
1284*count = \&MT::Object::count;
1285*columns_of_type = \&MT::Object::columns_of_type;
1286
1287*driver = \&MT::Object::dbi_driver;
1288
1289# TODO: copy this too
1290sub blob_requires_zip {}
1291
12921;
1293__END__
1294
1295=head1 NAME
1296
1297MT::Object - Movable Type base class for database-backed objects
1298
1299=head1 SYNOPSIS
1300
1301Creating an I<MT::Object> subclass:
1302
1303    package MT::Foo;
1304    use strict;
1305
1306    use base 'MT::Object';
1307
1308    __PACKAGE__->install_properties({
1309        columns_defs => {
1310            'id'  => 'integer not null auto_increment',
1311            'foo' => 'string(255)',
1312        },
1313        indexes => {
1314            foo => 1,
1315        },
1316        primary_key => 'id',
1317        datasource => 'foo',
1318    });
1319
1320Using an I<MT::Object> subclass:
1321
1322    use MT;
1323    use MT::Foo;
1324
1325    ## Create an MT object to load the system configuration and
1326    ## initialize an object driver.
1327    my $mt = MT->new;
1328
1329    ## Create an MT::Foo object, fill it with data, and save it;
1330    ## the object is saved using the object driver initialized above.
1331    my $foo = MT::Foo->new;
1332    $foo->foo('bar');
1333    $foo->save
1334        or die $foo->errstr;
1335
1336=head1 DESCRIPTION
1337
1338I<MT::Object> is the base class for all Movable Type objects that will be
1339serialized/stored to some location for later retrieval.
1340
1341Movable Type objects know nothing about how they are stored--they know only
1342of what types of data they consist, the names of those types of data (their
1343columns), etc. The actual storage mechanism is in the L<Data::ObjectDriver>
1344class and its driver subclasses; I<MT::Object> subclasses, on the other hand,
1345are essentially just standard in-memory Perl objects, but with a little extra
1346self-knowledge.
1347
1348This distinction between storage and in-memory representation allows objects
1349to be serialized to disk in many different ways. Adding a new storage method
1350is as simple as writing an object driver--a non-trivial task, to be sure, but
1351one that will not require touching any other Movable Type code.
1352
1353=head1 SUBCLASSING
1354
1355Creating a subclass of I<MT::Object> is very simple; you simply need to
1356define the properties and metadata about the object you are creating. Start
1357by declaring your class, and inheriting from I<MT::Object>:
1358
1359    package MT::Foo;
1360    use strict;
1361
1362    use base 'MT::Object';
1363
1364=item * __PACKAGE__->install_properties($args)
1365
1366Then call the I<install_properties> method on your class name; an easy way
1367to get your class name is to use the special I<__PACKAGE__> variable:
1368
1369    __PACKAGE__->install_properties({
1370        column_defs => {
1371            'id' => 'integer not null auto_increment',
1372            'foo' => 'string(255)',
1373        },
1374        indexes => {
1375            foo => 1,
1376        },
1377        primary_key => 'id',
1378        datasource => 'foo',
1379    });
1380
1381I<install_properties> performs the necessary magic to install the metadata
1382about your new class in the MT system. The method takes one argument, a hash
1383reference containing the metadata about your class. That hash reference can
1384have the following keys:
1385
1386=over 4
1387
1388=item * column_defs
1389
1390The definition of the columns (fields) in your object. Column names are also
1391used for method names for your object, so your column name should not
1392contain any strange characters. (It could also be used as part of the name of
1393the column in a relational database table, so that is another reason to keep
1394column names somewhat sane.)
1395
1396The value for the I<columns> key should be a reference to an hashref
1397containing the key/value pairs that are names of your columns matched with
1398their schema definition.
1399
1400The type declaration of a column is pseudo-SQL. The data types loosely match
1401SQL types, but are vendor-neutral, and each MT::ObjectDriver will map these
1402to appropriate types for the database it services. The format of a column
1403type is as follows:
1404
1405    'column_name' => 'type(size) options'
1406
1407The 'type' part of the declaration can be any one of:
1408
1409=over 4
1410
1411=item * string
1412
1413For storing string data, typically up to 255 characters, but assigned a length identified by '(size)'.
1414
1415=item * integer
1416
1417For storing integers, maybe limited to 32 bits.
1418
1419=item * boolean
1420
1421For storing boolean values (numeric values of 1 or 0).
1422
1423=item * smallint
1424
1425For storing small integers, typically limited to 16 bits.
1426
1427=item * datetime
1428
1429For storing a full date and time value.
1430
1431=item * timestamp
1432
1433For storing a date and time that automatically updates upon save.
1434
1435=item * blob
1436
1437For storing binary data.
1438
1439=item * text
1440
1441For storing text data.
1442
1443=item * float
1444
1445For storing floating point values.
1446
1447=back
1448
1449Note: The physical data storage capacity of these types will vary depending on
1450the driver's implementation.
1451
1452The '(size)' element of the declaration is only valid for the 'string' type.
1453
1454The 'options' element of the declaration is not required, but is used to
1455specify additional attributes of the column. Such as:
1456
1457=over 4
1458
1459=item * not null
1460
1461Specify 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.
1462
1463=item * auto_increment
1464
1465Specify for integer columns (typically the primary key) to automatically assign a value.
1466
1467=item * primary key
1468
1469Specify for identifying the column as the primary key (only valid for a single column).
1470
1471=item * indexed
1472
1473Identifies that this column should also be individually indexed.
1474
1475=item * meta
1476
1477Declares the column as a meta column, which means it is stored in a separate
1478table that is used for storing metadata. See L<Metadata> for more information.
1479
1480=back
1481
1482=item * indexes
1483
1484Specifies the column indexes on your objects.
1485
1486The value for the I<indexes> key should be a reference to a hash containing
1487column names as keys, and the value C<1> for each key--each key represents
1488a column that should be indexed:
1489
1490    indexes => {
1491        'column_1' => 1,
1492        'column_2' => 1,
1493    },
1494
1495For multi-column indexes, you must declare the individual columns as the
1496value for the index key:
1497
1498    indexes => {
1499        'column_catkey' => {
1500            columns => [ 'column_1', 'column_2' ],
1501        },
1502    },
1503
1504For declaring a unique constraint, add a 'unique' element to this hash:
1505
1506    indexes => {
1507        'column_catkey' => {
1508            columns => [ 'column_1', 'column_2' ],
1509            unique => 1,
1510        },
1511    },
1512
1513=item * audit
1514
1515Automatically adds bookkeeping capabilities to your class--each object will
1516take on four new columns: I<created_on>, I<created_by>, I<modified_on>, and
1517I<modified_by>. The created_on, created_by columns will be populated
1518automatically (if they have not already been assigned at the time of saving
1519the object). Your application is responsible for updating the modified_on,
1520modified_by columns as these may require explicit application-specific
1521assignments (ie, your application may only want them updated during explicit
1522user interaction with the object, as opposed to cases where the object is
1523being changed and saved for mechanical purposes like upgrading a table).
1524
1525=item * datasource
1526
1527The name of the datasource for your class. The datasource is a name uniquely
1528identifying your class--it is used by the object drivers to construct table
1529names, file names, etc. So it should not be specific to any one driver.
1530
1531Please note: the length of the datasource name should be conservative; some
1532drivers place limits on the length of table and column names.
1533
1534=item * meta
1535
1536Specify this property if you wish to support the storage of additional
1537metadata for this class. By doing so, a second table will be declared to
1538MT's registry, one that is designed to hold any metadata associated
1539with your class.
1540
1541=item * class_type
1542
1543If class_type is declared, an additional 'class' column is added to the
1544object properties. This column is then used to differentiate between
1545multiple object types that share the same physical table.
1546
1547Note that if this is used, all searches will be constrained to match
1548the class type of the package.
1549
1550=item * class_column
1551
1552Defines the name of the class column (default is 'class') for storing
1553classed objects (see 'class_type' above).
1554
1555=back
1556
1557=head1 USAGE
1558
1559=head2 System Initialization
1560
1561Before using (loading, saving, removing) an I<MT::Object> class and its
1562objects, you must always initialize the Movable Type system. This is done
1563with the following lines of code:
1564
1565    use MT;
1566    my $mt = MT->new;
1567
1568Constructing a new I<MT> objects loads the system configuration from the
1569F<mt.cfg> configuration file, then initializes the object driver that will
1570be used to manage serialized objects.
1571
1572=head2 Creating a new object
1573
1574To create a new object of an I<MT::Object> class, use the I<new> method:
1575
1576    my $foo = MT::Foo->new;
1577
1578I<new> takes no arguments, and simply initializes a new in-memory object.
1579In fact, you need not ever save this object to disk; it can be used as a
1580purely in-memory object.
1581
1582=head2 Setting and retrieving column values
1583
1584To set the column value of an object, use the name of the column as a method
1585name, and pass in the value for the column:
1586
1587    $foo->foo('bar');
1588
1589The return value of the above call will be C<bar>, the value to which you have
1590set the column.
1591
1592To retrieve the existing value of a column, call the same method, but without
1593an argument:
1594
1595    $foo->foo
1596
1597This returns the value of the I<foo> column from the I<$foo> object.
1598
1599=over 4
1600
1601=item * $obj->init()
1602
1603=back
1604
1605This method is used to initialize the object upon construction.
1606
1607=over 4
1608
1609=item * $obj->set_defaults()
1610
1611=back
1612
1613This method is used by the I<init> method to set the object defaults.
1614
1615=head2 Saving an object
1616
1617To save an object using the object driver, call the I<save> method:
1618
1619=over 4
1620
1621=item * $foo->save();
1622
1623=back
1624
1625On success, I<save> will return some true value; on failure, it will return
1626C<undef>, and you can retrieve the error message by calling the I<errstr>
1627method on the object:
1628
1629    $foo->save
1630        or die "Saving foo failed: ", $foo->errstr;
1631
1632If you are saving objects in a loop, take a look at the
1633L</"Note on object locking">.
1634
1635=head2 Loading an existing object or objects
1636
1637=over 4
1638
1639=item * $obj->load()
1640
1641=item * $obj->load_iter()
1642
1643=back
1644
1645You can load an object from the datastore using the I<load> method. I<load>
1646is by far the most complicated method, because there are many different ways
1647to load an object: by ID, by column value, by using a join with another type
1648of object, etc.
1649
1650In addition, you can load objects either into an array (I<load>), or by using
1651an iterator to step through the objects (I<load_iter>).
1652
1653I<load> has the following general form:
1654
1655    my $object = MT::Foo->load( $id );
1656
1657    my @objects = MT::Foo->load(\%terms, \%arguments);
1658
1659    my @objects = MT::Foo->load(\@terms, \%arguments);
1660
1661I<load_iter> has the following general form:
1662
1663    my $iter = MT::Foo->load_iter(\%terms, \%arguments);
1664
1665    my $iter = MT::Foo->load_iter(\@terms, \%arguments);
1666
1667Both methods share the same parameters; the only difference is the manner in
1668which they return the matching objects.
1669
1670If you call I<load> in scalar context, only the first row of the array
1671I<@objects> will be returned; this works well when you know that your I<load>
1672call can only ever result in one object returned--for example, when you load
1673an object by ID.
1674
1675I<\%terms> should be either:
1676
1677=over 4
1678
1679=item * The numeric ID of an object in the datastore.
1680
1681=item * A reference to a hash.
1682
1683The hash should have keys matching column names and the values are the
1684values for that column.
1685
1686For example, to load an I<MT::Foo> object where the I<foo> column is
1687equal to C<bar>, you could do this:
1688
1689    my @foo = MT::Foo->load({ foo => 'bar' });
1690
1691In addition to a simple scalar, the hash value can be a reference to an array;
1692combined with the I<range> setting in the I<\%arguments> list, you can use
1693this to perform range searches. If the value is a reference, the first element
1694in the array specifies the low end of the range, and the second element the
1695high end.
1696
1697=item * A reference to an array.
1698
1699In this form, the arrayref contains a list of selection terms for more
1700complex selections.
1701
1702    my @foo = MT::Foo->load( [ { foo => 'bar' }
1703        => -or => { foo => 'baz' } ] );
1704
1705The separating operator keywords inbetween terms can be any of C<-or>,
1706C<-and>, C<-or_not>, C<-and_not> (the leading '-' is not required, and the
1707operator itself is case-insensitive).
1708
1709=back
1710
1711Values assigned to terms for selecting data can be either simple or complex
1712in nature. Simple scalar values require an exact match. For instance:
1713
1714    my @foo = MT::Foo->load( { foo => 'bar' });
1715
1716This selects all I<MT::Foo> objects where foo == 'bar'. But you can provide
1717a hashref value to provide more options:
1718
1719    my @foo = MT::Foo->load( { foo => { like => 'bar%' } });
1720
1721This selects all I<MT::Foo> objects where foo starts with 'bar'. Other
1722possibilities include 'not_like', 'not_null', 'not', 'between', '>',
1723'>=', '<', '<=', '!='. Note that 'not' and 'between' both accept an
1724arrayref for their value; 'between' expects a two element array, and
1725'not' will accept an array of 1 or more values which translates to
1726a 'NOT IN (...)' SQL clause.
1727
1728I<\%arguments> should be a reference to a hash containing parameters for the
1729search. The following parameters are allowed:
1730
1731=over 4
1732
1733=item * sort => "column"
1734
1735Sort the resulting objects by the column C<column>; C<column> must be an
1736indexed column (see L</"indexes">, above).
1737
1738Sort may also be specified as an arrayref of multiple columns to sort on.
1739For example:
1740
1741    sort => [
1742        { column => "column_1", desc => "DESC" },
1743        { column => "column_2", }   # default direction is 'ascend'
1744    ]
1745
1746=item * direction => "ascend|descend"
1747
1748To be used together with a scalar I<sort> value; specifies the sort
1749order (ascending or descending). The default is C<ascend>.
1750
1751=item * limit => "N"
1752
1753Rather than loading all of the matching objects (the default), load only
1754C<N> objects.
1755
1756=item * offset => "M"
1757
1758To be used together with I<limit>; rather than returning the first C<N>
1759matches (the default), return matches C<M> through C<N + M>.
1760
1761=item * start_val => "value"
1762
1763To be used together with I<limit> and I<sort>; rather than returning the
1764first C<N> matches, return the first C<N> matches where C<column> (the sort
1765column) is greater than C<value>.
1766
1767=item * range
1768
1769To be used together with an array reference as the value for a column in
1770I<\%terms>; specifies that the specific column should be searched for a range
1771of values, rather than one specific value.
1772
1773The value of I<range> should be a hash reference, where the keys are column
1774names, and the values are all C<1>; each key specifies a column that should
1775be interpreted as a range.
1776
1777    MT::Foo->load( { created_on => [ '20011008000000', undef ] },
1778        { range => { created_on => 1 } } );
1779
1780This selects C<MT::Foo> objects whose created_on date is greater than
17812001-10-08 00:00:00.
1782
1783=item * range_incl
1784
1785Like the 'range' attribute, but defines an inclusive range.
1786
1787=item * join
1788
1789Can be used to select a set of objects based on criteria, or sorted by
1790criteria, from another set of objects. An example is selecting the C<N>
1791entries most recently commented-upon; the sorting is based on I<MT::Comment>
1792objects, but the objects returned are actually I<MT::Entry> objects. Using
1793I<join> in this situation is faster than loading the most recent
1794I<MT::Comment> objects, then loading each of the I<MT::Entry> objects
1795individually.
1796
1797Note that I<join> is not a normal SQL join, in that the objects returned are
1798always of only one type--in the above example, the objects returned are only
1799I<MT::Entry> objects, and cannot include columns from I<MT::Comment> objects.
1800
1801I<join> has the following general syntax:
1802
1803    join => MT::Foo->join_on( JOIN_COLUMN, I<\%terms>, I<\%arguments> )
1804
1805Use the actual MT::Object-descended package name and the join_on static method
1806providing these parameters: I<JOIN_COLUMN> is the column joining the two
1807object tables, I<\%terms> and I<\%arguments> have the same meaning as they do
1808in the outer I<load> or I<load_iter> argument lists: they are used to select
1809the objects with which the join is performed.
1810
1811For example, to select the last 10 most recently commmented-upon entries, you
1812could use the following statement:
1813
1814    my @entries = MT::Entry->load(undef, {
1815        'join' => MT::Comment->join_on( 'entry_id',
1816                    { blog_id => $blog_id },
1817                    { 'sort' => 'created_on',
1818                      direction => 'descend',
1819                      unique => 1,
1820                      limit => 10 } )
1821    });
1822
1823In this statement, the I<unique> setting ensures that the I<MT::Entry>
1824objects returned are unique; if this flag were not given, two copies of the
1825same I<MT::Entry> could be returned, if two comments were made on the same
1826entry.
1827
1828=item * unique
1829
1830Boolean flag that ensures that the objects being returned are unique.
1831
1832This is really only useful when used within a I<join>, because when loading
1833data out of a single object datastore, the objects are always going to be
1834unique.
1835
1836=item * window_size => "N"
1837
1838An optional attribute used only with the load_iter method. This attribute
1839is used when requesting a result set of large or unknown size. If the
1840load_iter method is called without any I<limit> attribute, this is set to
1841a default value of 100 (meaning, load 100 objects per SELECT). The iterator
1842will yield the specified number of objects and then issue an additional
1843select operation, using the same terms and attributes, adjusting for
1844a new offset for the next set of objects.
1845
1846=back
1847
1848=head2 Removing an object
1849
1850=over 4
1851
1852=item * $foo->remove()
1853
1854=back
1855
1856To remove an object from the datastore, call the I<remove> method on an
1857object that you have already loaded using I<load>:
1858
1859    $foo->remove();
1860
1861On success, I<remove> will return some true value; on failure, it will return
1862C<undef>, and you can retrieve the error message by calling the I<errstr>
1863method on the object:
1864
1865    $foo->remove
1866        or die "Removing foo failed: ", $foo->errstr;
1867
1868If you are removing objects in a loop, take a look at the
1869L</"Note on object locking">.
1870
1871=head2 Removing select objects of a particular class
1872
1873Combining the syntax of the load and remove methods, you can use the
1874static version of the remove method to remove particular objects:
1875
1876    MT::Foo->remove({ bar => 'baz' });
1877
1878The terms you specify to remove by should be indexed columns. This
1879method will load the object and remove it, firing the callback operations
1880associated with those operations.
1881
1882=head2 Removing all of the objects of a particular class
1883
1884To quickly remove all of the objects of a particular class, call the
1885I<remove_all> method on the class name in question:
1886
1887=over 4
1888
1889=item * MT::Foo->remove_all();
1890
1891=back
1892
1893On success, I<remove_all> will return some true value; on failure, it will
1894return C<undef>, and you can retrieve the error message by calling the
1895I<errstr> method on the class name:
1896
1897    MT::Foo->remove_all
1898        or die "Removing all foo objects failed: ", MT::Foo->errstr;
1899
1900=head2 Removing all the children of an object
1901
1902=over 4
1903
1904=item * $obj->remove_children([ \%param ])
1905
1906=back
1907
1908If your class has registered 'child_classes' as part of it's properties,
1909then this method may be used to remove objects that are associated with
1910the active object.
1911
1912This method is typically used in an overridden 'remove' method.
1913
1914    sub remove {
1915        my $obj = shift;
1916        $obj->remove_children({ key => 'object_id' });
1917        $obj->SUPER::remove(@_);
1918    }
1919
1920The 'key' parameter specified here lets you identify the field name used by
1921the children classes to relate back to the parent class. If unspecified,
1922C<remove_children> will assume the key to be the datasource name of the
1923current class with an '_id' suffix.
1924
1925=over 4
1926
1927=item * $obj->remove_scores( \%terms, \%args );
1928
1929=back
1930
1931For object classes that also have the L<MT::Scorable> class in their
1932C<@ISA> list, this method will remove any related score objects they
1933are associated with. This method is invoked automatically when
1934C<Class-E<gt>remove> or C<$obj-E<gt>remove> is invoked.
1935
1936=head2 Getting the count of a number of objects
1937
1938To determine how many objects meeting a particular set of conditions exist,
1939use the I<count> method:
1940
1941    my $count = MT::Foo->count({ foo => 'bar' });
1942
1943I<count> takes the same arguments as I<load> and I<load_iter>.
1944
1945=head2 Determining if an object exists in the datastore
1946
1947To check an object for existence in the datastore, use the I<exists> method:
1948
1949=over 4
1950
1951=item * $obj->exists()
1952
1953=back
1954
1955    if ($foo->exists) {
1956        print "Foo $foo already exists!";
1957    }
1958
1959To test for the existence of an unloaded object, use the 'exist' method:
1960
1961=over 4
1962
1963=item * Class->exist( \%terms )
1964
1965=back
1966
1967    if (MT::Foo->exist( { foo => 'bar' })) {
1968        print "Already exists!";
1969    }
1970
1971This is typically faster than issuing a L<count> call.
1972
1973=head2 Counting groups of objects
1974
1975=over 4
1976
1977=item * Class->count_group_by()
1978
1979=back
1980
1981The count_group_by method can be used to retrieve a list of all the
1982distinct values that appear in a given column along with a count of
1983how many objects carry that value. The routine can also be used with
1984more than one column, in which case it retrieves the distinct pairs
1985(or n-tuples) of values in those columns, along with the counts.
1986Yet more powerful, any SQL expression can be used in place of
1987the column names to count how many object produce any given result
1988values when run through those expressions.
1989
1990  $iter = MT::Foo->count_group_by($terms, {%args, group => $group_exprs});
1991
1992C<$terms> and C<%args> pick out a subset of the MT::Foo objects in the
1993usual way. C<$group_expressions> is an array reference containing the
1994SQL expressions for the values you want to group by. A single row will
1995be returned for each distinct tuple of values resulting from the
1996$group_expressions. For example, if $group_expressions were just a
1997single column (e.g. group => ['created_on']) then a single row would
1998be returned for each distinct value of the 'created_on' column. If
1999$group_expressions were multiple columns, a row would be returned for
2000each distinct pair (or n-tuple) of values found in those columns.
2001
2002Each application of the iterator C<$iter> returns a list in the form:
2003
2004  ($count, $group_val1, $group_val2, ...)
2005
2006Where C<$count> is the number of MT::Foo objects for which the group
2007expressions are the values ($group_val1, $group_val2, ...). These
2008values are in the same order as the corresponding group expressions in
2009the $group_exprs argument.
2010
2011In this example, we load up groups of MT::Pip objects, grouped by the
2012pair (cat_id, invoice_id), and print how many pips have that pair of
2013values.
2014
2015    $iter = MT::Pip->count_group_by(undef,
2016                                    {group => ['cat_id',
2017                                               'invoice_id']});
2018    while (($count, $cat, $inv) = $iter->()) {
2019        print "There are $count Pips with " .
2020            "category $cat and invoice $inv\n";
2021    }
2022
2023=head2 Averaging by Group
2024
2025=over 4
2026
2027=item * Class->avg_group_by()
2028
2029=back
2030
2031Like the count_group_by method, you can select groups of averages from
2032a MT::Object store.
2033
2034    my $iter = MT::Foo->avg_group_by($terms, {%args, group => $group_exprs,
2035        avg => 'property_to_average' })
2036
2037=head2 Max by Group
2038
2039=over 4
2040
2041=item * Class->max_group_by()
2042
2043=back
2044
2045Like the count_group_by method, you can select objects from a MT::Object
2046store using a SQL 'MAX' operator.
2047
2048    my $iter = MT::Foo->max_group_by($terms, {%args, group => $group_exprs,
2049        max => 'column_name' })
2050
2051=head2 Sum by Group
2052
2053=over 4
2054
2055=item * Class->sum_group_by()
2056
2057=back
2058
2059Like the count_group_by method, you can select groups of sums from
2060a MT::Object store.
2061
2062    my $iter = MT::Foo->sum_group_by($terms, {%args, group => $group_exprs,
2063        avg => 'property_to_sum' })
2064
2065=head2 Inspecting and Manipulating Object State
2066
2067=over 4
2068
2069=item * $obj->column_values()
2070
2071=back
2072
2073Use C<column_values> and C<set_values> to get and set the fields of an
2074object I<en masse>. The former returns a hash reference mapping column
2075names to their values in this object. For example:
2076
2077    $values = $obj->column_values()
2078
2079=over 4
2080
2081=item * $obj->set_values()
2082
2083=back
2084
2085C<set_values> accepts a similar hash ref, which need not give a value
2086for every field. For example:
2087
2088    $obj->set_values({col1 => $val1, col2 => $val2});
2089
2090is equivalent to
2091
2092    $obj->col1($val1);
2093    $obj->col2($val2);
2094
2095=head2 Other Methods
2096
2097=over 4
2098
2099=item * $obj->clone([\%param])
2100
2101Returns a clone of C<$obj>. That is, a distinct object which has all
2102the same data stored within it. Changing values within one object does
2103not modify the other.
2104
2105An optional C<except> parameter may be provided to exclude particular
2106columns from the cloning operation. For example, the following would
2107clone the elements of the blog except the name attribute.
2108
2109   $blog->clone({ except => { name => 1 } });
2110
2111=item * $obj->clone_all()
2112
2113Similar to the C<clone> method, but also makes a clones the metadata
2114information.
2115
2116=item * $obj->column_names()
2117
2118Returns a list of the names of columns in C<$obj>; includes all those
2119specified to the install_properties method as well as the audit
2120properties (C<created_on>, C<modified_on>, C<created_by>,
2121C<modified_by>), if those were enabled in install_properties.
2122
2123=item * MT::Foo->driver()
2124
2125=item * $obj->driver()
2126
2127Returns the ObjectDriver object that links this object with a database.
2128This is a subclass of L<Data::ObjectDriver>.
2129
2130=item * $obj->dbi_driver()
2131
2132This method is similar to the 'driver' method, but will always return
2133a DBI driver (a subclass of the L<Data::ObjectDriver::Driver::DBI>
2134class) and not a caching driver.
2135
2136=item * $obj->created_on_obj()
2137
2138Returns a MT::DateTime object representing the moment when the
2139object was first saved to the database.
2140
2141=item * $obj->column_as_datetime( $column )
2142
2143Returns a MT::DateTime object for the specified datetime/timestamp
2144column specified.
2145
2146=item * MT::Foo->set_by_key($key_terms, $value_terms)
2147
2148A convenience method that loads whatever object matches the C<$key_terms>
2149argument and sets some or all of its fields according to the
2150C<$value_terms>. For example:
2151
2152   MT::Foo->set_by_key({name => 'Thor'},
2153                       {region => 'Norway', gender => 'Male'});
2154
2155This loads the C<MT::Foo> object having 'name' field equal to 'Thor'
2156and sets the 'region' and 'gender' fields appropriately.
2157
2158More than one term is acceptable in the C<$key_terms> argument. The
2159matching object is the one that matches all of the C<$key_terms>.
2160
2161This method only useful if you know that there is a unique object
2162matching the given key. There need not be a unique constraint on the
2163columns named in the C<$key_hash>; but if not, you should be confident
2164that only one object will match the key.
2165
2166=item * MT::Foo->get_by_key($key_terms)
2167
2168A convenience method that loads whatever object matches the C<$key_terms>
2169argument. If no matching object is found, a new object will be constructed
2170and the C<$key_terms> provided will be assigned to it. So regardless of
2171whether the key exists already, this method will return an object with the
2172key requested. Note, however: if a new object is instantiated it is
2173not automatically saved.
2174
2175    my $thor = MT::Foo->get_by_key({name => 'Thor'});
2176    $thor->region('Norway');
2177    $thor->gender('Male');
2178    $thor->save;
2179
2180The fact that it returns a new object if one isn't found is to help
2181optimize this pattern:
2182
2183    my $obj = MT::Foo->load({key => $value});
2184    if (!$obj) {
2185        $obj = new MT::Foo;
2186        $obj->key($value);
2187    }
2188
2189This is equivalent to:
2190
2191    my $obj = MT::Foo->get_by_key({key => $value});
2192
2193If you don't appreciate the autoinstantiation behavior of this method,
2194just use the C<load> method instead.
2195
2196More than one term is acceptable in the C<$key_terms> argument. The
2197matching object is the one that matches all of the C<$key_terms>.
2198
2199This method only useful if you know that there is a unique object
2200matching the given key. There need not be a unique constraint on the
2201columns named in the C<$key_hash>; but if not, you should be confident
2202that only one object will match the key.
2203
2204=item * $obj->cache_property($key, $code)
2205
2206Caches the provided key (e.g. entry, trackback) with the return value
2207of the given code reference (which is often an object load call) so
2208that the value does not have to be recomputed each time.
2209
2210=item * $obj->clear_cache()
2211
2212Clears any object-level cache data (from the C<cache_property> method)
2213that may existing.
2214
2215=item * $obj->column_def($name)
2216
2217This method returns the value of the given I<$name> C<column_defs>
2218propery.
2219
2220=item * $obj->column_defs()
2221
2222This method returns all the C<column_defs> of the property of the
2223object.
2224
2225=item Class->index_defs()
2226
2227This method returns all the index definitions assigned to this class.
2228This is the 'indexes' member of the properties installed for the class.
2229
2230=item * $obj->to_hash()
2231
2232Returns a hashref containing column and metadata key/value pairs for
2233the object. If the object has a blog relationship, it also populates
2234data from that blog. For example:
2235
2236    my $entry_hash = $entry->to_hash();
2237    # returns: { entry.title => "Title", entry.blog.name => "Foo", ... }
2238
2239=item * Class->join_on( $join_column, \%join_terms, \%join_args )
2240
2241A simple helper method that returns an arrayref of join terms suitable
2242for the C<load> and C<load_iter> methods.
2243
2244=item * $obj->properties()
2245
2246Returns a hashref of the object properties that were declared with the
2247I<install_properties> method.
2248
2249=item * $obj->to_xml()
2250
2251Returns an XML representation of the object.
2252This method is defined in MT/BackupRestore.pm - you must first
2253use MT::BackupRestore to use this method.
2254
2255=item * $obj->restore_parent_ids()
2256
2257TODO - Backup file contains parent objects' ids (foreign keys).  However,
2258when parent objcects are restored, their ids will be changed.  This method
2259is to match the old and new ids of parent objects for children objects to be
2260correctly associated.
2261This method is defined in MT/BackupRestore.pm - you must first
2262use MT::BackupRestore to use this method.
2263
2264=item * $obj->parent_names()
2265
2266TODO - Should be overridden by subclasses to return correct hash
2267whose keys are xml element names of the object's parent objects
2268and values are class names of them.
2269This method is defined in MT/BackupRestore.pm - you must first
2270use MT::BackupRestore to use this method.
2271
2272=item * Class->class_handler($type)
2273
2274Returns the appropriate Perl package name for the given type identifier.
2275For example,
2276
2277    # Yields MT::Asset::Image
2278    MT::Asset->class_handler('asset.image');
2279
2280=item * Class->class_label
2281
2282Provides a descriptive name for the requested class package.
2283This is a localized name, using the currently assigned language.
2284
2285=item * Class->class_label_plural
2286
2287Returns a descriptive pluralized name for the requested class package.
2288This is a localized name, using the currently assigned language.
2289
2290=item * Class->class_labels
2291
2292Returns a hashref of type identifiers to class labels for all subclasses
2293associated with a multiclassed object type. For instance:
2294
2295    # returns { 'asset' => 'Asset', 'asset.video' => 'Video', ... }
2296    my $labels = MT::Asset->class_labels;
2297
2298=item * Class->columns_of_type(@types)
2299
2300Returns an arrayref of column names that are of the requested type.
2301
2302    my @dates = MT::Foo->columns_of_type('datetime', 'timestamp')
2303
2304=item * Class->has_column( $name )
2305
2306Returns a boolean as to whether the column C<$name> is defined for
2307this class.
2308
2309=item * Class->table_name()
2310
2311Returns the database table name (including any prefix) for the class.
2312
2313=item * $obj->column_func( $column )
2314
2315Creates an accessor/mutator method for column C<$column>, returning it as a
2316coderef. This method overrides the one in L<Data::ObjectDriver::BaseObject>,
2317by supporting metadata column as well.
2318
2319=item * $obj->call_trigger( 'trigger_name', @params )
2320
2321Issues a call to any Class::Trigger triggers installed for the given object.
2322Also invokes any MT callbacks that are registered using MT's callback
2323system. "pre" callbacks are invoked prior to triggers; "post" callbacks
2324are invoked after triggers are called.
2325
2326=item * $obj->deflate
2327
2328Returns a minimal representation of the object, including any metadata.
2329See also L<Data::ObjectDriver::BaseObject>.
2330
2331=item * Class->inflate( $deflated )
2332
2333Inflates the deflated representation of the object I<$deflated> into a proper
2334object in the class I<Class>. That is, undoes the operation C<$deflated =
2335$obj-E<gt>deflate()> by returning a new object equivalent to C<$obj>.
2336
2337=item * Class->install_pre_init_properties
2338
2339This static method is used to install any class properties that were
2340registered prior to the bootstrapping of MT plugins.
2341
2342=item * $obj->modified_by
2343
2344A modified getter/setter accessor method for audited classes with a
2345'modified_by', 'modified_on' columns. In the event this method is called
2346to assign a 'modified_by' value, it automatically updates the 'modified_on'
2347column as well.
2348
2349=item * $obj->nextprev( %params )
2350
2351Method to determine adjancent objects, based on a date column and/or id.
2352The C<%params> hash provides the following elements:
2353
2354=over 4
2355
2356=item * direction
2357
2358Either "next" or "previous".
2359
2360=item * terms
2361
2362Any additional terms to supply to the C<load> method.
2363
2364=item * args
2365
2366Any additional arguments to supply to the C<load> method (such as a join).
2367
2368=item * by
2369
2370The column to use to determine the next/previous object. By default for
2371audited classes, this is 'created_on'.
2372
2373=back
2374
2375=back
2376
2377=head1 NOTES
2378
2379=head2 Note on object locking
2380
2381When you read objects from the datastore, the object table is locked with a
2382shared lock; when you write to the datastore, the table is locked with an
2383exclusive lock.
2384
2385Thus, note that saving or removing objects in the same loop where you are
2386loading them from an iterator will not work--the reason is that the datastore
2387maintains a shared lock on the object table while objects are being loaded
2388from the iterator, and thus the attempt to gain an exclusive lock when saving
2389or removing an object will cause deadlock.
2390
2391For example, you cannot do the following:
2392
2393    my $iter = MT::Foo->load_iter({ foo => 'bar' });
2394    while (my $foo = $iter->()) {
2395        $foo->remove;
2396    }
2397
2398Instead you should do either this:
2399
2400    my @foo = MT::Foo->load({ foo => 'bar' });
2401    for my $foo (@foo) {
2402        $foo->remove;
2403    }
2404
2405or this:
2406
2407    my $iter = MT::Foo->load_iter({ foo => 'bar' });
2408    my @to_remove;
2409    while (my $foo = $iter->()) {
2410        push @to_remove, $foo
2411            if SOME CONDITION;
2412    }
2413    for my $foo (@to_remove) {
2414        $foo->remove;
2415    }
2416
2417This last example is useful if you will not be removing every I<MT::Foo>
2418object where I<foo> equals C<bar>, because it saves memory--only the
2419I<MT::Foo> objects that you will be deleting are kept in memory at the same
2420time.
2421
2422=head1 SUBCLASSING
2423
2424It is possible to declare a subclass of an existing MT::Object class,
2425one that shares the same table storage as the parent class. Examples of
2426this include L<MT::Log>, L<MT::Entry>, L<MT::Category>. In these cases,
2427the subclass identifies a 'class_type' property. The parent class must also
2428have a column where this identifier is stored. Upon loading records from the
2429table, the object is reblessed into the appropriate package.
2430
2431=over 4
2432
2433=item Class->add_class( $type_id, $class )
2434
2435This method can be called directly to register a new subclass type
2436and package for the base class.
2437
2438    MT::Foo->add_class( 'foochild' => 'MT::Foo::Subclass' );
2439
2440=back
2441
2442=head1 METADATA
2443
2444The following methods facilitate the storage and management of metadata;
2445available when the 'meta' key is included in the installed properties for
2446the class.
2447
2448=over 4
2449
2450=item * $obj->init_meta()
2451
2452For object classes that have metadata storage, this method will initialize
2453the metadata member.
2454
2455=item * Class->install_meta( \%meta_properties )
2456
2457Called to register metadata properties on a particular class. The
2458C<%meta_properties> may contain an arrayref of 'columns', or a hashref
2459of 'column_defs' (similar to the C<install_properties> method):
2460
2461    MT::Foo->install_meta( { column_defs => {
2462        'metadata1' => 'integer indexed',
2463        'metadata2' => 'string indexed',
2464    } });
2465
2466In this form, the storage type is explicitly declared, so the metadata
2467is stored into the appropriate column (vinteger_idx and vchar_idx
2468respectively).
2469
2470    MT::Foo->install_meta( { columns => [ 'metadata1', 'metadata2' ] } )
2471
2472In this form, the metadata properties store their data into a 'blob'
2473column in the meta table. This type of metadata cannot be used to sort
2474or filter on. This form is supported for backward compatibility and is
2475considered deprecated.
2476
2477=item * $obj->remove_meta()
2478
2479Deletes all related metadata for the given object.
2480
2481=item * Class->search_by_meta( $key, $value, [ \%terms [, \%args ] ] )
2482
2483Returns objects that have a C<$key> metadata value of C<$value>. Further
2484restrictions on the class may be applied through the optional C<%terms>
2485and C<%args> parameters.
2486
2487=item * $obj->meta_obj()
2488
2489Returns the L<MT::Object> class
2490
2491=item * Class->meta_pkg()
2492
2493Returns the Perl package name for storing it's metadata objects.
2494
2495=item * Class->meta_args
2496
2497Returns the source of a Perl package declaration that is loaded to
2498declare and process metadata objects for the C<Class>.
2499
2500=item * Class->has_meta( [ $name ] )
2501
2502Returns a boolean as to whether the class has metadata when called
2503without a parameter, or whether there exists a metadata column
2504of the given C<$name>.
2505
2506=item * Class->is_meta_column( $name )
2507
2508Returns a boolean as to whether the class has a meta column named
2509C<$name>.
2510
2511=back
2512
2513=head1 CALLBACKS
2514
2515=over 4
2516
2517=item * $obj->add_callback()
2518
2519=back
2520
2521Most MT::Object operations can trigger callbacks to plugin code. Some
2522notable uses of this feature are: to be notified when a database record is
2523modified, or to pre- or post-process the data being flowing to the
2524database.
2525
2526To add a callback, invoke the C<add_callback> method of the I<MT::Object>
2527subclass, as follows:
2528
2529   MT::Foo->add_callback( "pre_save", <priority>,
2530                          <plugin object>, \&callback_function);
2531
2532The first argument is the name of the hook point. Any I<MT::Object>
2533subclass has a pre_ and a post_ hook point for each of the following
2534operations:
2535
2536    load
2537    save
2538    update (issued for save on existing objects)
2539    insert (issued for save on new objects)
2540    remove
2541    remove_all
2542    (load_iter operations will call the load callbacks)
2543
2544The second argument, E<lt>priorityE<gt>, is the relative order in
2545which the callback should be called. The value should be between 1 and
254610, inclusive. Callbacks with priority 1 will be called before those
2547with 2, 2 before 3, and so on.
2548
2549Plugins which know they need to run first or last can use the priority
2550values 0 and 11. A callback with priority 0 will run before all
2551others, and if two callbacks try to use that value, an error will
2552result. Likewise priority 11 is exclusive, and runs last.
2553
2554How to remember which callback priorities are special? As you know,
2555most guitar amps have a volume knob that goes from 1 to 10. But, like
2556that of certain rock stars, our amp goes up to 11. A callback with
2557priority 11 is the "loudest" or most powerful callback, as it will be
2558called just before the object is saved to the database (in the case of
2559a 'pre' callback), or just before the object is returned (in the case
2560of a 'post' callback). A callback with priority 0 is the "quietest"
2561callback, as following callbacks can completely overwhelm it. This may
2562be a good choice for your plugin, as you may want your plugin to work
2563well with other plugins. Determining the correct priority is a matter
2564of thinking about your plugin in relation to others, and adjusting the
2565priority based on experience so that users get the best use out of the
2566plugin.
2567
2568The E<lt>plugin objectE<gt> is an object of type MT::Plugin which
2569gives some information about the plugin. This is used to include
2570the plugin's name in any error messages.
2571
2572E<lt>callback functionE<gt> is a code referense for a subroutine that
2573will be called. The arguments to this
2574function vary by operation (see I<MT::Callback> for details),
2575but in each case the first parameter is the I<MT::Callback> object
2576itself:
2577
2578  sub my_callback {
2579      my ($cb, ...) = @_;
2580
2581      if ( <error condition> ) {
2582          return $cb->error("Error message");
2583      }
2584  }
2585
2586Strictly speaking, the return value of a callback is ignored. Calling
2587the error() method of the MT::Callback object (C<$cb> in this case)
2588propagates the error message up to the MT activity log.
2589
2590Another way to handle errors is to call C<die>. If a callback dies,
2591I<MT> will warn the error to the activity log, but will continue
2592processing the MT::Object operation: so other callbacks will still
2593run, and the database operation should still occur.
2594
2595=head2 Any-class Object Callbacks
2596
2597If you add a callback to the MT class with a hook point that begins
2598with C<*::>, such as:
2599
2600    MT->add_callback('*::post_save', 7, $my_plugin, \&code_ref);
2601
2602then it will be called whenever post_save callbacks are called.
2603"Any-class" callbacks are called I<after> all class-specific
2604callbacks. Note that C<add_callback> must be called on the C<MT> class,
2605not on a subclass of C<MT::Object>.
2606
2607=head2 Caveat
2608
2609Be careful how you handle errors. If you transform data as it goes
2610into and out of the database, and it is possible for one of your
2611callbacks to fail, the data may get saved in an undefined state. It
2612may then be difficult or impossible for the user to recover that data.
2613
2614=head1 AUTHOR & COPYRIGHTS
2615
2616Please see the I<MT> manpage for author, copyright, and license information.
2617
2618=cut
Note: See TracBrowser for help on using the browser.