DBIX::Class (aka DBIC)

for (advanced) beginners

Contact Info

Authors

Originally Leo Lapworth @ LPW 2009

Amiri Barksdale

Justin D. Hunter

Arthur Axel "fREW" Schmidt

What's up guys?

Purpose

The purpose of this talk is to show you as many features of DBIx::Class in 40 minutes so that when you need to do something with it later you will know what's possible

DBIx::Class?

Meta

These are reasons that are not technical or inherent to the code of DBIC, but are totally awesome things about it.

Large Community

Currently there are 88 people listed as contributors to DBIC. That ranges from documentation help, to test help, to added features, to entire database support.

Active Community

Currently (June 9, 2010) 6 active branches (commited to in the last two weeks) in git. Last release (0.08122) had 14 new features, and 16 bug fixes. Of course that ebbs and flows.

Responsive Community

General ORM

These are things that are in most other ORMs, but are still reasons to use DBIC over raw SQL.

Cross DB

The vast majority of code should run on all databases without needing tweaking

Basic CRUD

SQL: Create

my $sth = $dbh->prepare('
   INSERT INTO books
   (title, author_id)
   values (?,?)
');

$sth->execute(
  'A book title', $author_id
);

DBIC: Create

my $book = $book_rs->create({
   title     => 'A book title',
   author_id => $author_id,
});

SQL: Read

my $sth = $dbh->prepare('
   SELECT title,
   authors.name as author_name
   FROM books, authors
   WHERE books.author = authors.id
');

while( my $book = $sth->fetchrow_hashref() ) {
  print 'Author of '
     . $book->{title}
     . ' is '
     . $book->{author_name}
     . "\n";
}

DBIC: Read

my $book = $book_rs->find($book_id);

my $book = $book_rs->search({
   title => 'A book title',
}, { rows => 1 })->next;

my @books = $book_rs->search({
   author => $author_id,
})->all;

while( my $book = $books_rs->next ) {
 print 'Author of '
    . $book->title
    . ' is '
    . $book->author->name
    . "\n";
}

SQL: Update

my $update = $dbh->prepare('
   UPDATE books
   SET title = ?
   WHERE id = ?
');

$update->execute(
  'New title',$book_id
);

DBIC: Update

$book->update({
  title => 'New title',
});

SQL: Delete

my $delete = $dbh->prepare('
   DELETE FROM books
   WHERE id = ?
');

$delete->execute($book_id);

DBIC: Delete

$book->delete;

SQL: Search

my $sth = $dbh->prepare('
   SELECT title,
   authors.name as author_name
   FROM books
   WHERE books.name LIKE "%monte cristo%" AND
   books.topic = "jailbreak"
');

DBIC: Search

my $book = $book_rs->search({
   'me.name'  => { -like => '%monte cristo%' },
   'me.topic' => 'jailbreak',
})->next;

OO Overidability

I got yelled at about this before by people, so we don't get EVERYTHING from OO, but we do get a lot so :-P

Convenience Methods

Non-column methods

Need a method to get a user's gravatar URL? Add a gravatar_url method to their Result class

RELATIONSHIPS

DBIx::Class Specific Features

These things may be in other ORM's, but they are very specific, so doubtful

->deploy

Perl -> DB

my $schema = Foo::Schema->connect(
   $dsn, $user, $pass
);
$schema->deploy

See also: DBIx::Class::DeploymentHandler

Schema::Loader

DB -> Perl

package Foo::Schema;
use strict; use warnings;
use base 'DBIx::Class::Schema::Loader';
__PACKAGE__->loader_options({
   naming => 'v7',
   debug  => $ENV{DBIC_TRACE},
});
1;

# elsewhere...

my $schema = Foo::Schema->connect(
   $dsn, $user, $pass
);

Populate

Made for inserting lots of rows very quicky into database

$schema->populate([ Users =>
   [qw( username password )],
   [qw( frew     >=4char$ )],
   [qw(      ...          )],
   [qw(      ...          )],
);

Multicreate

Create an object and all of it's related objects all at once

$schema->resultset('Author')->create({
   name => 'Stephen King',
   books => [{ title => 'The Dark Tower' }],
   address => {
      street => '123 Turtle Back Lane',
      state  => { abbreviation => 'ME' },
      city   => { name => 'Lowell'     },
   },
});
  • books is a has_many
  • address is a belongs_to which in turn belongs to state and city each
  • for this to work right state and city must mark abbreviation and name as unique

Extensible

DBIx::Class helped pioneer fast MI in Perl 5 with Class::C3, so it is made to allow extensions to nearly every part of it.

Extensible: DBIC::Helpers

Extensible: DBIC::TimeStamp

Extensible: Kioku

Result vs ResultSet

ResultSet methods

package MyApp::Schema::ResultSet::Book;
use base 'DBIx::Class::ResultSet';
sub good {
   my $self = shift;
   $self->search({
      $self->current_source_alias .
         '.rating' => { '>=' => 4 },
   })
};
sub cheap {
   my $self = shift;
   $self->search({
      $self->current_source_alias .
         '.price' => { '<=' => 5}
   })
};
# ...
1;
      

ResultSet method notes

ResultSet method in Action

$schema->resultset('Book')->good

ResultSet Chaining

$schema->resultset('Book')
   ->good
   ->cheap
   ->recent

search_related

my $score = $schema->resultset('User')
   ->search({'me.userid' => 'frew'})
   ->related_resultset('access')
   ->related_resultset('mgmt')
   ->related_resultset('orders')
   ->telephone
   ->search_related( shops => {
      'shops.datecompleted' => {
         -between => ['2009-10-01','2009-10-08']
      }
   })->completed
   ->related_resultset('rpt_score')
   ->get_column('raw_scores')
   ->first;

bonus rel methods

my $book = $author->create_related(
   books => {
      title => 'Another Discworld book',
   }
);

my $book2 = $pratchett->add_to_books({
   title => 'MOAR Discworld book',
});

Excellent Transaction Support

$schema->txn_do(sub {
   ...
});

my $guard = $schema->txn_scope_guard;
# ...
$guard->commit;

$schema->txn_begin; # <-- low level
# ...
$schema->txn_commit;

InflateColumn

package Foo::Schema::Result::Book;
use base 'DBIx::Class::Core';
use DateTime::Format::MySQL;
# Result code here
__PACKAGE__->load_components('InflateColumn');
__PACKAGE__->inflate_column(
   date_published => {
      inflate => sub {
         DateTime::Format::MySQL->parse_date(
            shift
         )
      },
      deflate => sub { shift->ymd },
   },
);
# Automatic see: DBIC::InflateColumn::DateTime

InflateColumn: deflation

$book->date_published(DateTime->now);
$book->update;

InflateColumn: inflation

say $book->date_published->month_abbr;
Nov

FilterColumn

package Foo::Schema::Result::Book;
use base 'DBIx::Class::Core';
# Result code here
__PACKAGE__->load_components('FilterColumn');

__PACKAGE__->filter_column(
   length => {
      to_storage   => 'to_metric',
      from_storage => 'to_imperial',
   },
);

sub to_metric   { $_[1] * .305 }
sub to_imperial { $_[1] * 3.28 }
   

ResultSetColumn

my $rsc = $schema->resultset('Book')
   ->get_column('price');
$rsc->first;
$rsc->all;
$rsc->min;
$rsc->max;
$rsc->sum;

Aggregates

my @res = $rs->search({}, {
   select   => [
      'price',
      'genre',
      { max => price },
      { avg => price },
   ],
   as       => [
      qw(price genre max_price avg_price)
   ],
   group_by => [qw(price genre)],
});
for (@res) {
   say $_->price . ' ' . $_->genre;
   say $_->get_column('max_price');
   say $_->get_column('min_price');
}

Aggregates Notes

HRI

$rs->search({}, {
  result_class =>
    'DBIx::Class::ResultClass::HashRefInflator',
});

Subquery Support

my $inside_query = $schema->resultset('Artist')
   ->search({
    name => [ 'Billy Joel', 'Brittany Spears' ],
})->get_column('id')->as_query;

my $rs = $schema->resultset('CD')->search({
    artist_id => { -in => $inside_query },
});

Bare SQL w/ Placeholders

$rs->update({
   # !!! SQL INJECTION VECTOR
   price => \"price + $inc",
});

$rs->update({
   price => \['price + ?', [inc => $inc]],
});

Questions?

END