The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

DBIx::Class::Tutorial::Part3

DESCRIPTION

If you missed the previous parts, go and start reading at DBIx::Class::Tutorial::Part1.

Stay here if you're just after ways to deal with the data you're getting out of DBIx::Class, or adding your own accessors.

GLOSSARY

Inflation and Deflation

The act of converting data coming out of the database, into a useful object in order to call methods on it, is called Inflation. This is usually done for the data from one column of a Row. The classic example is a field containing data representing a specific date and time. The data type for these fields is usually datetime or timestamp. An Inflator can turn the datetime data into a DateTime object.

Deflation is the opposite. Turning a supplied object back into a string or other piece of data suitable for inserting into the database.

Auto-Incrementing and Sequences

Most databases provide a way to auto-increment a numeric column, usually an integer column, to use as a primary key. Some allow you to create a sequence which can be queried for its next value, to use as the primary key. The difficulty with creating new rows using auto-increment primary keys is retrieving the value of the key after a new row has been inserted.

Non-column data

An oft-asked question is: How can I add accessors to my Result classes to store non-column data?

We can easily add new accessors to Row objects to set and retrieve data not stored in the database.

Dealing with Primary Keys

DBIx::Class automatically fetches the primary key from the database for you, and stores it in your new row object.

  ## Fetch a new path and print its ID:
  my $path = $schema->resultset('Path')->create(
    { path => '/support', }
  );
  print $path->id;

This is done using last_insert_id.

Tables using sequences for their primary keys should be updated using a trigger to update the value. The name of the sequence can be set in the add_columns so that the last value can be fetched by DBIx::Class::PK::Auto.

Turning values into useful objects

Just retrieving raw data from the database is only half the battle. You likely want to also do something useful with it, or manipulate it and re-insert. For example, many tables have a field containing the date and time the row was created, or last modified.

If we want to take that date and display, for example, how long since the row was modified in days/hours/minutes, it would be useful to have that datetime value as a DateTime object, then we can use DateTime::Duration or similar to display the elapsed time.

To do this, we can add a new component to the Result classes. In lib/Breadcrumbs/Schema/Path.pm you'll notice a line that says:

  __PACKAGE__->load_components(qw/ Core/);

It may contain other components as well. Add the InflateColumn::DateTime component in front of the existing ones.

  __PACKAGE__->load_components(qw/ InflateColumn::DateTime Core /);

Order is important. Core must go last.

The accessors of any columns of type datetime, timestamp and date will now return DateTime objects when called.

  ## print last_modified as an iso formatted string
  my $dt = $path->last_modified();
  print $dt->iso_string;

You can now also set the value of last_modified using a DateTime object.

  ## Set last_modified
  my $dtnow = DateTime->now();
  $path->last_modified($dtnow);
  $path->update();

To see how to create more inflators and deflators for other types of objects, read DBIx::Class::InflateColumn.

Changing the standard accessors

DBIx::Class creates standard getter/setter accessors for you, for all your values. If you would like to change or manipulate the value of a particular column on the way into or out of the database, you can write your own accessors.

To do this you will first have to edit the Result class, adding the accessor key in your "add_columns" in DBIx::Class::ResultSource call.

  ## add accessor for path column, in 
  ## Breadcrumbs::Schema::Path
  __PACKAGE__->add_columns( ...
                           path => {
                             data_type => 'varchar',
                             size      => 255,
                             accessor  => '_path',
                           }
                           .. 
                           );

DBIx::Class will now create this accessor with the name _path. We can now write our own path method.

  ## Clean extra slashes off paths

  sub path {
    my ($self, $value) = @_;

    if(@_ > 1) {
      $value = s{^/}{};
      $value = s{/$}{};
      $self->_path($value);
    }
    return $self->_path();
  }

Adding your own data and methods

You can add your own accessors for non-column (database) data to your Result classes quite easily. Just edit the Result classes.

  ## Add accessor for storing whether a path has been checked
  ## to Breadcrumbs::Schema::Path

  __PACKAGE__->mk_group_accessors('simple' => qw/is_checked/);

  $path->is_checked(1);

Or, just add an entire method to do the work and return the result.

  ## Add method to check if the path exists:

  sub check {
    my ($self, $root) = @_;

    return 1 if(-d catfile($root, $self->path));

    return 0;
  }

Adding ResultSet methods

Putting methods in your Result classes will make them available to the Row objects. To add methods to entire resultsets, you will first need to subclass DBIx::Class::ResultSet.

  package Breadcrumbs::ResultSet::Path;
  use base 'DBIx::Class::ResultSet';

  sub check_paths {
    ## $self is a resultset object!
    my ($self, $root) = @_;

    my $ok = 1;
    foreach my $path ($self->all) {
       $ok = 0 if(!-d catfile($root, $path->path));
    }

    return $ok; 
  }

To make this module your default resultset for all Path resultsets, call resultset_class in your Result class.

  ## Set the new resultset class, in Breadcrumbs::Schema::Path:
  __PACKAGE__->resultset_class('Breadcrumbs::ResultSet::Path');

Make sure you don't create the new ResultSet class in the namespace/directory underneath the existing Schema. This will cause "load_classes" in DBIx::Class::Schema to attempt to load it as if it were a Result class. The result will not be good.

CONCLUSIONS

EXERCISES

WHERE TO GO NEXT

AUTHOR

Jess Robinson <castaway@desert-island.me.uk>