root/branches/release-32/lib/MT/Object.pm @ 1608

Revision 1608, 59.0 kB (checked in by bchoate, 20 months ago)

Fix for initial assignment to meta column.

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