Skip to content

DevGuide PerlObjectsStorage

mikert edited this page Aug 2, 2011 · 3 revisions

MDG: Manipulating Objects - Saving, Loading and Deleting Data

All Melody Objects support a standard set of calls that can be used to load, save and remove objects from the database. These methods are:

  • new()
  • save()
  • load($terms, $arguments)
  • load_iter($terms, $arguments)
  • remove($terms, $arguments)
  • remove_all
  • count($terms)
  • exists($terms)
  • clone()

Let's look at each in more detail.

new()

The new() method is used to instantiate a new object in the database. The object is not physically created until save() is called subsequently.

my $foo = MT::Foo->new;
$foo->property('bar');
$foo->save();

The new() method takes no arguments, and simply initializes a new in-memory object.

save()

To save an object call the save method:

$foo->save();

On success, save will return some true value; on failure, it will return "undef", and you can retrieve the error message by calling the errstr method on the object:

$foo->save
    or die "Saving foo failed: ", $foo->errstr;

If you are saving objects in a loop, take a look at the "Note on object locking".

load()

The load method can be used to load objects from the database. The load method can be used to compose queries that are both simple extraordinarily powerful. In fact the majority of queries that can be expressed in SQL can be represented by a call to the load() method.

As a result, the syntax for the load method is far too complex to discuss in complete detail here. For a complete description of load and its many facets, please consult Appendix B: MT::Object POD Documentation. What follows are just the basics of the load method.

The load method can be used to load a single object or multiple objects from the database. What is returned depends largely upon the context in which load is called. Take for example the following:

my $object = MT::Foo->load( $id );

my @objects = MT::Foo->load(\%terms, \%arguments);

You will notice that when load is called with a single scalar as input, load will attempt to look up the object in the database corresponding to that ID and return it. If load is called in an array context as in the second example, then load will return an array of objects.

Most commonly load takes two arguments as input:

  • a hash containing the query terms or constraints (e.g. load user whose favorite color is blue)

  • an hash containing the arguments for the query (e.g. limit the results of the query to 10, or sort the results by first name)

Valid arguments are:

  • sort - the column to sort by

  • direction - To be used together with a scalar sort value; specifies the sort order (ascending or descending). The default is "ascend".

  • limit - Rather than loading all of the matching objects (the default), load only "N" objects.

  • offset - To be used together with limit; rather than returning the first "N" matches (the default), return matches "M" through "N + M".

  • lastn

  • start_val - To be used together with limit and sort; rather than returning the first "N" matches, return the first "N" matches where "column" (the sort column) is greater than "value".

  • range - specifies that the specific column should be searched for a range of values, rather than one specific value.

  • range_incl - Like the 'range' attribute, but defines an inclusive range.

  • join - can be used to select a set of objects based on criteria, or sorted by criteria, from another set of objects.

  • unique - Boolean flag that ensures that the objects being returned are unique.

Let's look at a complete example:

my @objects = MT::Foo->load(
    { 
        title => "Hello World",
        foo => "bar",
    }, {
        sort => 'created_on',
        direction => 'ascend',
    }
);

load_iter()

The load_iter method returns an "iterator" that can be invoked to move though a dataset one item at a time. This technique is especially useful when working is large datasets because it progressively loads data from the database as needed.

my $iter = MT::Foo->load_iter({ foo => 'bar' });
while (my $foo = $iter->()) {
    $foo->remove;
}

The load_iter method supports one argument that load does not: window_size.

The window_size parameter is useful because it limits how many objects are loaded from the database at a time and can greatly reduce memory consumption. For example, when calling load_iter in order to iterate over each object in the database, load_iter will by default load 100 records at a time. Doing so limits the number of queries to the database Melody must make to load one item after another. The window_size argument adjusts the number of objects to load at a time.

remove()

To remove an object from the datastore, call the remove method on an object that you have already loaded using load:

$foo->remove();

On success, remove will return some true value; on failure, it will return "undef", and you can retrieve the error message by calling the errstr method on the object:

$foo->remove
    or die "Removing foo failed: ", $foo->errstr;

You can restrict what specific objects you remove or delete by passing to the remove() method a hash containing the constraints for the request. For example:

MT::Foo->remove({ bar => 'baz' });

The terms you specify to remove by should be indexed columns. This method will load the object and remove it, and then fire the callback operations associated with those operations.

remove_all()

To quickly remove all of the objects of a particular class, call the remove_all method on the class name in question:

MT::Foo->remove_all();

On success, remove_all will return some true value; on failure, it will return "undef", and you can retrieve the error message by calling the errstr method on the class name:

MT::Foo->remove_all
    or die "Removing all foo objects failed: ", MT::Foo->errstr;

count()

To determine how many objects meeting a particular set of conditions exist, use the count method:

my $count = MT::Foo->count({ foo => 'bar' });

The count method takes the same arguments as load and load_iter.

exist() and exists()

To check and see if an object that you have instantiated already exists in the database, use the exists method:

if ($foo->exists) {
    print "Foo $foo already exists!";
}

To test to see if an object with specific properties exists in the database, use the exist method:

if (MT::Foo->exist( { foo => 'bar' })) {
    print "Already exists!";
}

Tip: Calling exist is faster than issuing a count call.

clone()

Returns a clone of $obj. That is, a distinct object which has all the same data stored within it. Changing values within one object does not modify the other.

An optional "except" parameter may be provided to exclude particular columns from the cloning operation. For example, the following would clone the elements of the blog except the name attribute.

$blog->clone({ except => { name => 1 } });

$obj->clone_all()

Similar to the "clone" method, but also makes a clones the metadata information.

Creating Your First Object

The first step in creating an object is to create a file that will contain all of the information Melody 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, Melody 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, Melody 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 Melody, 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.

Extending Existing Objects

Sometimes a developer wants to associate additional properties with an object without being forced to create a sub-class.

Melody allows any object's schema to be extended. This allows plugins and components to insert and associate additional pieces of data, called "meta data," with a pre-existing data type. Furthermore, these additional pieces of data become a seamless extension of the original data element, allowing that object to be sorted by and filtered by the new data element quickly and easily.

Extending an object is done by declaring the extension within the registry. For example, to add a new "is_featured" field to the core entry object for the purposes of allowing admins to designate if an entry is featured or not, one would use the following config.yaml:

name: Example Plugin for Melody
id: Example
description: This plugin is an example plugin for Melody.
version: 1.0
schema_version: 2
object_types:
    entry:
        is_featured: smallint

This works because whenever a plugin attaches properties to a pre-existing object type, then that object_type declaration acts as an extension to the pre-existing object type. In addition that additional piece of meta data is accessible directly from the associated object. For example:

use MT::Entry;
my $entry = MT::Entry->load($id);
$entry->is_featured(1);
$entry->save;

About Schema Versions

If a developer ever modifies or adds additional meta data fields to an object, then the developer should increment the schema_version attribute of their plugin should be incremented to signal to Melody that some database maintenance may be required. When this happens, next time Melody is accessed the upgrade process will be invoked and Melody will automatically make changes to your database schema as necessary.

Getting and Storing Data for a Custom Data Type

Once you have registered and defined your custom object type. You can easily save instances of the object, or retrieve and/or delete them from the database. Here are some very simple code samples which cover the basics of retrieving and storing these custom objects from the database:

Inserting Data

my $object = TestPlugin::SuperHappyVideo->new;
$object->blog_id(1);
$object->title("My latest video");
$object->length(120);
$object->description("The best video in the world.");
$object->save;

Loading and Updating Data

my $object = TestPlugin::SuperHappyVideo->load({ id => 4 });
$object->title("My latest HAPPY video!");
$object->save;

Deleting Data

my $object = TestPlugin::SuperHappyVideo->load({ id => 4 });
$object->remove;

There are a number of different functions Melody makes available to you to make it easier to query and manipulate data in the database. Some of these functions are:

  • load
  • load_iter
  • get_by_key
  • remove
  • remove_all
  • remove_children
  • save
  • set_by_key
  • init
  • join
  • unique
  • count
  • count_group_by
  • exists

For complete documentation on creating custom objects, please consult MT::Object Reference.

Continue Reading

 


Questions, comments, can't find something? Let us know at our community outpost on Get Satisfaction.

Credits

  • Author: Byrne Reese
  • Edited by: Violet Bliss Dietz
Clone this wiki locally