root/branches/release-38/lib/MT/Object.pm @ 2369

Revision 2369, 74.7 kB (checked in by mpaschal, 19 months ago)

Shorten these column and index names
(Commit Fumiaki's change)
NOTE that manual SQL attached to case #79744 is required to upgrade from beta 4 or 5

http://bugs.movabletype.org/default.asp?79744

BugzID: 79744

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