Not a developer? Go to MovableType.com

Documentation

Creating Your First Object

The first step in creating an object is to create a file that will contain all of the information Movable Type needs to know about the object, and that will also provide developers with any additional interfaces for interacting with that object. Let’s do that now.

Create a file called MyObject.pm in the following path:

/path/to/mt/plugins/MyPluginName/lib/MyPluginName/

Now, let’s add a really simple code stub that you can edit and customize for your own purposes. As always: don’t worry, we will deconstruct what all of this does in a moment.

Open MyObject.pm in a text editor and copy and paste the following into it:

package Example::MyObject;

use strict;
use base qw( MT::Object );

__PACKAGE__->install_properties({
    column_defs => {
        'id'            => 'integer not null auto_increment',
        'blog_id'       => 'integer',
        'some_property' => 'string(100) not null',
    },
    audit => 1,
    indexes => {
        id => 1,
    },
    datasource => 'myplugin_myobject',
    primary_key => 'id',
});
sub class_label {
    MT->translate("My Object");
}
sub class_label_plural {
    MT->translate("My Objects");
}

1;

A look inside install_properties

MT::Object’s install_properties method does most of the work when defining a new MT::Object. It takes as input a single argument, a hash containing one or more of the following keys:

  • column_defs - The data structure of your object. This is a collection of your object’s property names and their corresponding data types. See “Defining Your Schema” below.

  • audit - This is a boolean flag. When set to true, Movable Type will automatically create all of the database columns to track whenever the object is modified and by whom.

  • indexes - This is a hash containing a list of the indexes to add to the database for this object. See “Indexes.”

  • datasource - The table name to store the object’s data in.

  • meta - This is a boolean flag. When set to true, Movable Type will maintain a separate table to store additional meta data for the object as defined by plugins and third parties.

  • class_type - If declared MT will define an additional column in the database called ‘class’ to differentiate between different types of objects that share the same physical table (like entries and pages, or the various types of assets that MT supports)

  • class_column - If specified will use this as the column name for the ‘class_type’ field above.

Tip: See Appendix B: MT::Object POD Documentation for a more thorough explanation of install_properties.

Indexes

Database indexes are used to increase the speed and efficiency of database queries. Indexes are specified by identifying the columns by which queries are likely to be constrained. In Movable Type, this is how you specify two indexes, one on column_1 and the other on column_2.

The value for the indexes key should be a reference to a hash containing column names as keys, and the value 1 for each key - each key represents a column that should be indexed:

indexes => {
    'column_1' => 1,
    'column_2' => 1,
},

For multi-column indexes, you must declare the individual columns as the value for the index key:

indexes => {
    'column_catkey' => {
        columns => [ 'column_1', 'column_2' ],
    },
},

For declaring a unique constraint, add a ‘unique’ element to this hash:

indexes => {
    'column_catkey' => {
        columns => [ 'column_1', 'column_2' ],
        unique => 1,
    },
},

Defining Your Schema: column_defs

The definition of the columns (fields) in your object. Column names are also used for method names for your object, so your column name should not contain any strange characters. (It could also be used as part of the name of the column in a relational database table, so that is another reason to keep column names somewhat sane.)

The value for the columns key should be a reference to a hashref containing the key/value pairs that are names of your columns matched with their schema definition.

The type declaration of a column is pseudo-SQL. The data types loosely match SQL types, but are vendor-neutral, and each MT::ObjectDriver will map these to appropriate types for the database it services. The format of a column type is as follows:

'column_name' => 'type(size) options'

The ‘type’ part of the declaration can be any one of:

  • string - For storing string data, typically up to 255 characters, but assigned a length identified by ‘(size)’.

  • integer - For storing integers, maybe limited to 32 bits.

  • boolean - For storing boolean values (numeric values of 1 or 0).

  • smallint - For storing small integers, typically limited to 16 bits.

  • datetime - For storing a full date and time value.

  • timestamp - For storing a date and time that automatically updates upon save.

  • blob - For storing binary data.

  • text - For storing text data.

  • float - For storing floating point values.

Note: The physical data storage capacity of these types will vary depending on the driver’s implementation.

The ‘(size)’ element of the declaration is only valid for the ‘string’ type.

The ‘options’ element of the declaration is not required, but is used to specify additional attributes of the column. Such as:

  • not null - Specify 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.

  • auto_increment - Specify for integer columns (typically the primary key) to automatically assign a value.

  • primary key - Specify for identifying the column as the primary key (only valid for a single column).

  • indexed - Identifies that this column should also be individually indexed.

  • meta - Declares the column as a meta column, which means it is stored in a separate table that is used for storing metadata. See Metadata for more information.

Back

3 Comments

Dan Wolfgang

Dan Wolfgang on September 30, 2008, 11:45 a.m. Reply

Note: when defining column_defs, the column_name key should be kept to 30 characters or less. MySQL can deal with longer names, but Oracle is limited. (Thanks to Jay Allen for pointing this out.)

Jay Allen

Jay Allen on October 1, 2008, 1:31 p.m. Reply

Actually, it’s worse than that because the limitation also extends to indexed names which are formed by joining mt_ and the table name, a period and then the column name.

So, in the example above, with a datasource of myplugin_myobject and a column some_property, you would have an index of mt_myplugin_myobject.some_property

At 34 characters, your plugin would have fatal errors on Oracle.

The solution is two-part:

  • Be concise in your datasource and column naming
  • Resist the urge to put your whole plugin name in the table name opting instead for just a unique abbreviation to prevent conflicts with MT or other plugins (e.g. mlp_object)
indigirl

indigirl on August 24, 2009, 12:45 p.m. Reply

MT allows you to initially define a schema with a primary key column that can be null.

For example, the following chunk of code will install properly:

__PACKAGE__->install_properties({
    column_defs => {
        'id'   => 'integer auto_increment',
        'key'   => 'string(50)',
        'status' => 'string(100)',
    },
    audit => 1,
    datasource => 'task_status',
    primary_key => 'id',
indexes => {
        id => 1,
        key => 1,
    status => 1,
    },
});

However, the next time mt-upgrade.cgi runs - for ANY plugin - it throws an error:

Error during upgrade: failed to execute statement ALTER TABLE    
mt_task_status MODIFY task_status_id integer PRIMARY KEY 
auto_increment: Multiple primary key defined at lib/MT/Upgrade.pm 
line 2553.

This can be resolved by ensuring that your primary key does not allow NULL values via the schema:

__PACKAGE__->install_properties({
    column_defs => {
        'id'   => 'integer not null auto_increment',
        'key'   => 'string(50)',
        'status' => 'string(100)',
    },
    audit => 1,
    datasource => 'task_status',
    primary_key => 'id',
indexes => {
        id => 1,
        key => 1,
    status => 1,
    },
});