root/branches/release-39/lib/MT/Object.pm @ 2453

Revision 2453, 77.3 kB (checked in by fumiakiy, 18 months ago)

Changed the way to generate unique hash key for an object. BugId:79927

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