98
DBIx::Class (aka DBIC) for (advanced) beginners Leo Lapworth @ YAPC::EU 2010 http://leo.cuckoo.org/projects/

DBIx::Class introduction - 2010

Embed Size (px)

DESCRIPTION

If your not using an ORM (object relational mapper) and are still writing SQL by hand, here's what you need to know. An introduction into DBIx::Class and some of the concepts and goodies you should be aware off.

Citation preview

Page 1: DBIx::Class introduction - 2010

DBIx::Class (aka DBIC)for (advanced) beginners

Leo Lapworth @ YAPC::EU 2010

http://leo.cuckoo.org/projects/

Page 2: DBIx::Class introduction - 2010

assumptions

You know a little about Perl and using objects

You know a little bit about databases and using foreign keys

Page 3: DBIx::Class introduction - 2010

DBIx::Class?

• ORM (object relational mapper)

• SQL <-> OO (using objects instead of SQL)

• Simple, powerful, complex, fab and confusing

• There are many ORMs, DBIx::Class just happens to be the best in Perl (personal opinion)

Page 4: DBIx::Class introduction - 2010

why this talk?

• Help avoid mistakes I made!

• Help learn DBIx::Class faster

• Make your coding easier

Page 5: DBIx::Class introduction - 2010

table setup

Page 6: DBIx::Class introduction - 2010

example...

Books

Authors

Page 7: DBIx::Class introduction - 2010

authors table

CREATE TABLE authors(

id int(8) primary key auto_increment,

name varchar(255)

) engine = InnoDB DEFAULT CHARSET=utf8;

Page 8: DBIx::Class introduction - 2010

tips

Name tables as simple plurals (add an S) - makes relationships easier to understand

(issue: Matt Trout "Tables should not be plural as gives you plurals for Result:: package names which represent a single row" - talk may be rewritten in future to reflect this as this is better once you understand the relationship setup - either way, consistency is important)

Use a character set (UTF8) from the start (for international characters)

Page 9: DBIx::Class introduction - 2010

authors table

CREATE TABLE authors(

id int(8) primary key auto_increment,

name varchar(255)

) engine = InnoDB DEFAULT CHARSET=utf8;

Page 10: DBIx::Class introduction - 2010

books tableCREATE TABLE books(

id int(8) primary key auto_increment,

title varchar(255),

author int(8),

foreign key (author)

references authors(id)

) engine = InnoDB DEFAULT CHARSET=utf8;

Page 11: DBIx::Class introduction - 2010

tips

Name link fields as singular

Check foreign key is the same field type and size in both tables

Page 12: DBIx::Class introduction - 2010

books tableCREATE TABLE books(

id int(8) primary key auto_increment,

title varchar(255),

author int(8),

foreign key (author)

references authors(id)) engine = InnoDB DEFAULT CHARSET=utf8;

Page 13: DBIx::Class introduction - 2010

CRUD comparedC - CreateR - RetrieveU - UpdateD - Delete

Page 14: DBIx::Class introduction - 2010

Manual (SQL)

Page 15: DBIx::Class introduction - 2010

manual: createmy $sth = $dbh->prepare('

INSERT INTO books

(title, author)

values (?,?)

');

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

Page 16: DBIx::Class introduction - 2010

manual: createmy $sth = $dbh->prepare('

INSERT INTO books

(title, author)

values (?,?)

');

$sth->execute(

'A book title',$author_id);

Page 17: DBIx::Class introduction - 2010

manual: retrievemy $sth = $dbh->prepare('

SELECT title,

authors.name as author_name

FROM books, authors

WHERE books.author = authors.id

');

Page 18: DBIx::Class introduction - 2010

manual: retrievewhile( my $book = $sth->fetchrow_hashref() ) {

print 'Author of '

. $book->{title}

. ' is '

. $book->{author_name}

. "\n";

}

Page 19: DBIx::Class introduction - 2010

manual: updatemy $update = $dbh->prepare('

UPDATE books

SET title = ?

WHERE id = ?

');

$update->execute(

'New title',$book_id);

Page 20: DBIx::Class introduction - 2010

manual: deletemy $delete = $dbh->prepare('

DELETE FROM books

WHERE id = ?

');

$delete->execute($book_id);

Page 21: DBIx::Class introduction - 2010

DBIx::Class

Page 22: DBIx::Class introduction - 2010

DBIC: createmy $book = $book_model->create({

title => 'A book title',

author => $author_id,

});

Look ma, no SQL!

Tip: do not pass in primary_key field, even if its empty/undef as the object returned will have an empty id, even if your field is auto increment.

Page 23: DBIx::Class introduction - 2010

DBIC: createmy $book = $book_model->create({

title => 'A book title',

author => $author_id,});

Page 24: DBIx::Class introduction - 2010

DBIC: createmy $pratchett = $author_model->create({

name => 'Terry Pratchett',

});

Page 25: DBIx::Class introduction - 2010

DBIC: createmy $book = $pratchett->create_related(

'books', {

title => 'Another Discworld book',

});

or

my $book = $pratchett->add_to_books({

title => 'Another Discworld book',

});

Page 26: DBIx::Class introduction - 2010

DBIC: createmy $book = $pratchett->create_related(

'books', {

title => 'Another Discworld book',

});

or

my $book = $pratchett->add_to_books({

title => 'Another Discworld book',

});

Page 27: DBIx::Class introduction - 2010

DBIC: retrieve

DBIx::Class - Lots of ways to do the same thing...

"There is more than one way to do it (TIMTOWTDI, usually pronounced "Tim Toady") is a Perl motto"

Page 28: DBIx::Class introduction - 2010

DBIC: retrievemy $book = $book_model->find($book_id);

my $book = $book_model->search({

title => 'A book title',

})->single();

my @books = $book_model->search({

author => $author_id,

})->all();

Page 29: DBIx::Class introduction - 2010

DBIC: retrievewhile( my $book = $books_rs->next() ) {

print 'Author of '

. $book->title()

. ' is '

. $book->author()->name()

. "\n";

}

Page 30: DBIx::Class introduction - 2010

DBIC: retrievemy $books_rs = $book_model->search({

author => $author_id,

});

Search takes SQL::Abstract formatted queries> perldoc SQL::Abstract

Page 31: DBIx::Class introduction - 2010

DBIC: update$book->update({

title => 'New title',

});

Page 32: DBIx::Class introduction - 2010

DBIC: delete$book->delete();

Page 33: DBIx::Class introduction - 2010

Creating schemas

Page 34: DBIx::Class introduction - 2010
Page 35: DBIx::Class introduction - 2010
Page 36: DBIx::Class introduction - 2010

too much typing!

too much maintenance!

Page 37: DBIx::Class introduction - 2010

Schema::Loader

Database introspection -> Code

Page 38: DBIx::Class introduction - 2010

Use namespaces

Page 39: DBIx::Class introduction - 2010

Use NamespacesSplits logic cleanly

Bookstore::Schema::Result::X

= an individual row

Bookstore::Schema:: ResultSet::X

= searches / results

Page 40: DBIx::Class introduction - 2010
Page 41: DBIx::Class introduction - 2010
Page 42: DBIx::Class introduction - 2010
Page 43: DBIx::Class introduction - 2010

You can edit this line

Page 44: DBIx::Class introduction - 2010

Connection details

Page 45: DBIx::Class introduction - 2010

using your Schema

Page 46: DBIx::Class introduction - 2010
Page 47: DBIx::Class introduction - 2010
Page 48: DBIx::Class introduction - 2010

DEBUGGING

DBIC_TRACE=1 ./your_script.pl

Page 49: DBIx::Class introduction - 2010

SQL - debugging

INSERT INTO authors (name) VALUES (?): 'Douglas Adams'

INSERT INTO books (author, title) VALUES (?, ?): '1', '42'

Page 50: DBIx::Class introduction - 2010

overloading

Bookstore::Schema::Result::Books

Bookstore::Schema::ResultSet::Books

Bookstore::Schema::Result::Authors

Bookstore::Schema::ResultSet::Authors

Page 51: DBIx::Class introduction - 2010

Result::package Bookstore::Schema::Result::Books;use base 'DBIx::Class';

#...

# Created by DBIx::Class::Schema::Loader v0.04005 @ 2010-08-01 09:19:14# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ta+cEh31lDfqcue3OmUCfQ

sub isbn {my $self = shift;

# search amazon or somethingmy $api = Amazon::API->book({ title => $self->title() });

return $api->isbn();}

1;

Page 52: DBIx::Class introduction - 2010

Result::package Bookstore::Schema::Result::Books;use base 'DBIx::Class';

#...

# Created by DBIx::Class::Schema::Loader v0.04005 @ 2010-08-01 09:19:14# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ta+cEh31lDfqcue3OmUCfQ

sub isbn {my $self = shift;

# search amazon or somethingmy $api = Amazon::API->book({ title => $self->title() });

return $api->isbn();}

1;

Page 53: DBIx::Class introduction - 2010

Result::print $book->isbn();

Page 54: DBIx::Class introduction - 2010

Result:: (inflating)package Bookstore::Schema::Result::Books;use base 'DBIx::Class';

#...

use DateTime::Format::MySQL;

__PACKAGE__->inflate_column( 'date_published', { inflate => sub { DateTime::Format::MySQL->parse_date(shift); }, deflate => sub { shift->ymd(); }, });# Automatic see: DBIx::Class::InflateColumn::DateTime

Page 55: DBIx::Class introduction - 2010

Result:: (inflating)package Bookstore::Schema::Result::Books;use base 'DBIx::Class';

#...

use DateTime::Format::MySQL;

__PACKAGE__->inflate_column( 'date_published', { inflate => sub {

DateTime::Format::MySQL->parse_date(shift); }, deflate => sub { shift->ymd(); }, });# Automatic see: DBIx::Class::InflateColumn::DateTime

Page 56: DBIx::Class introduction - 2010

Result:: (deflating)$book->date_published(DateTime->now);

$book->update();

2008-12-31

Page 57: DBIx::Class introduction - 2010

Result:: (inflating)

my $date_published = $book->date_published()print $date_published->month_abbr();

Nov

Page 58: DBIx::Class introduction - 2010

ResultSets::package Bookstore::Schema::ResultSet::Books;use base 'DBIx::Class::ResultSet';

#...

sub the_ultimate_books { my $self = shift;

return $self->search( { title => { 'like', '%42%' } });}

sub by_author { my ( $self, $author ) = @_;

return $self->search( { author => $author->id(), } );}

1;

Page 59: DBIx::Class introduction - 2010

ResultSets::package Bookstore::Schema::ResultSet::Books;use base 'DBIx::Class::ResultSet';#...sub the_ultimate_books { my $self = shift;

return $self->search( { title => { 'like', '%42%' } });}

sub by_author { my ( $self, $author ) = @_;

return $self->search( { author => $author->id(), } );}

Page 60: DBIx::Class introduction - 2010

ResultSets::package Bookstore::Schema::ResultSet::Books;use base 'DBIx::Class::ResultSet';#...sub the_ultimate_books { my $self = shift;

return $self->search( { title => { 'like', '%42%' } });}

sub by_author { my ( $self, $author ) = @_;

return $self->search( { author => $author->id(),

} );}

1;

Page 61: DBIx::Class introduction - 2010

ResultSets::use Bookstore::Schema;

my $book_model = Bookstore::Schema->resultset('Books');

my $book_rs = $book_model->the_ultimate_books();

my @books = $book_rs->all();

Page 62: DBIx::Class introduction - 2010

ResultSets::chaininguse Bookstore::Schema;

my $book_model = Bookstore::Schema->resultset('Books');my $author_model = Bookstore::Schema->resultset('Authors');

my $author = $author_model->search({ name => 'Douglas Adams',})->single();

my $book_rs = $book_model->the_ultimate_books() ->by_author($author);

my @books = $book_rs->all();

Page 63: DBIx::Class introduction - 2010

ResultSets::chainingmy $book_rs = $book_model

->the_ultimate_books() ->by_author($author);

or

my $book_rs = $book_model ->the_ultimate_books();$book_rs = $book_rs->by_author($author);

# Debug (SQL):

# SELECT me.id, me.title, me.date_published, me.author # FROM books me # WHERE ( ( ( author = ? ) AND ( title LIKE ? ) ) ): '1', '%42%'

Page 64: DBIx::Class introduction - 2010

ResultSets::chainingmy $rs = $book_model

->category('childrens') ->by_author($author) ->published_after('1812') ->first_page_contains('once upon') ->rating_greater_than(4);

my @books = $rs->all();

Page 65: DBIx::Class introduction - 2010

overloading before new record

Page 66: DBIx::Class introduction - 2010

overloading before new record

package Bookstore::Schema::Result::Authors;use base 'DBIx::Class';

sub new { my ( $class, $attrs ) = @_;

# Mess with $attrs

my $new = $class->next::method($attrs); return $new;}

1;

Page 67: DBIx::Class introduction - 2010

relationships

Page 68: DBIx::Class introduction - 2010

multiple authors

Page 69: DBIx::Class introduction - 2010

a few relationships

Authors BooksAuthors_and_Books

has_many has_many

belongs_to belongs_to

many_to_many

Page 70: DBIx::Class introduction - 2010

a few relationships

!

Page 71: DBIx::Class introduction - 2010

new join tableCREATE TABLE author_and_books( id int(8) primary key auto_increment, book ! int(8), author int(8),

foreign key (book) references books(id), foreign key (author) references authors(id)

) engine = InnoDB DEFAULT CHARSET=utf8;

ALTER TABLE `books` DROP COLUMN `author`;

Page 72: DBIx::Class introduction - 2010

CREATE TABLE author_and_books( id int(8) primary key auto_increment, book ! int(8), author int(8),

foreign key (book) references books(id), foreign key (author) references authors(id)

) engine = InnoDB DEFAULT CHARSET=utf8;

new join table

Page 73: DBIx::Class introduction - 2010

has_many

Books Authors_and_Books

has_many

belongs_to

Page 74: DBIx::Class introduction - 2010

has_manypackage Bookstore::Schema::Result::Books;

__PACKAGE__->has_many(

"author_and_books", "Bookstore::Schema::Result::AuthorAndBooks",

{ "foreign.book" => "self.id" },

);

# This is auto generated by Schema::Loader

Page 75: DBIx::Class introduction - 2010

has_manypackage Bookstore::Schema::Result::Books;

__PACKAGE__->has_many(

"author_and_books", # Name of accessor "Bookstore::Schema::Result::AuthorAndBooks", # Related class { "foreign.book" => "self.id" }, # Relationship (magic often works if not # specified, but avoid!));

Page 76: DBIx::Class introduction - 2010

belongs_to

Books Authors_and_Books

has_many

belongs_to

Page 77: DBIx::Class introduction - 2010

belongs_topackage Bookstore::Schema::Result::AuthorAndBooks;

__PACKAGE__->belongs_to( "book", "Bookstore::Schema::Result::Books", { id => "book" });

# This is auto generated by Schema::Loader

Page 78: DBIx::Class introduction - 2010

belongs_topackage Bookstore::Schema::Result::AuthorAndBooks;

__PACKAGE__->belongs_to( "book", # Accessor name "Bookstore::Schema::Result::Books",

# Related class { id => "book" } # Relationship);

Page 79: DBIx::Class introduction - 2010

same for Authors

Authors Authors_and_Books

has_many

belongs_to

Page 80: DBIx::Class introduction - 2010

with no coding...

Authors BooksAuthors_and_Books

has_many has_many

belongs_to belongs_to

Page 81: DBIx::Class introduction - 2010

many_to_many

Authors BooksAuthors_and_Books

has_many has_many

belongs_to belongs_to

many_to_many

Page 82: DBIx::Class introduction - 2010

many_to_manypackage Bookstore::Schema::Result::Books;use base 'DBIx::Class';

__PACKAGE__->many_to_many( "authors"

=> "author_and_books",

'author');

1;

# This is NOT auto generated by Schema::Loader

Page 83: DBIx::Class introduction - 2010

many_to_manypackage Bookstore::Schema::Result::Books;use base 'DBIx::Class';

__PACKAGE__->many_to_many( "authors" # Accessor Name => "author_and_books", # has_many accessor_name 'author' # foreign relationship name);

1;

Page 84: DBIx::Class introduction - 2010

many_to_manypackage Bookstore::Schema::Result::Authors;use base 'DBIx::Class';

__PACKAGE__->many_to_many( "books" # Accessor Name => "author_and_books", # has_many accessor_name 'book' # foreign relationship name);

1;

# This is NOT auto generated by Schema::Loader

Page 85: DBIx::Class introduction - 2010

using many_to_many#!/usr/bin/perl

use Bookstore::Schema;

my $author_model = Bookstore::Schema->resultset('Authors');

my $author = $author_model->search({name => 'Douglas Adams',

})->single();

$author->add_to_books({title => 'A new book',

});

Page 86: DBIx::Class introduction - 2010

using many_to_manymy $author = $author_model->search({name => 'Douglas Adams',

})->single();

$author->add_to_books({title => 'A new book',

});

# SELECT me.id, me.name FROM authors me # WHERE ( name = ? ): 'Douglas Adams';

# INSERT INTO books (title) VALUES (?): 'A new book';

# INSERT INTO author_and_books (author, book) # VALUES (?, ?): '5', '2';

Page 87: DBIx::Class introduction - 2010

using many_to_many$author->add_to_books($book);

$book->add_to_authors($author_1);

$book->add_to_authors($author_2);

Page 88: DBIx::Class introduction - 2010

in 16 lines of code

Authors BooksAuthors_and_Books

has_many has_many

belongs_to belongs_to

many_to_many

Page 89: DBIx::Class introduction - 2010

errors

Read them closely!

Page 90: DBIx::Class introduction - 2010

error messagesDBIx::Class::Schema::Loader::connection(): Failed to load external class definition for 'Bookstore::Schema::Result::Authors': Can't locate object method "many_to_many" via package "Bookstore::Schema::Result::Author" at lib/Bookstore/Schema/Result/Authors.pm line 9.Compilation failed in require at /Library/Perl/5.8.8/DBIx/Class/Schema/Loader/Base.pm line 292.

Page 91: DBIx::Class introduction - 2010

error messagesDBIx::Class::Schema::Loader::connection(): Failed to load external class definition for 'Bookstore::Schema::Result::Authors': Can't locate object method "many_to_many" via package "Bookstore::Schema::Result::Author" at lib/Bookstore/Schema/Result/Authors.pm line 9.Compilation failed in require at /Library/Perl/5.8.8/DBIx/Class/Schema/Loader/Base.pm line 292.

Page 92: DBIx::Class introduction - 2010

errors

• Turn on debugging

• Read error messages (sometimes useful!)

• Check field names

• Check package names

• Check which database you are connected to (development/test/live?) - repeat above

Page 93: DBIx::Class introduction - 2010

thanks

http://leo.cuckoo.org/projects/

Time for bonus slides?

Page 94: DBIx::Class introduction - 2010

Template Toolkit

• [% author.books.count %] not working?

• TT all methods are called in list context

• [% author.books_rs.count %] scalar context

Available for all relationships

Page 95: DBIx::Class introduction - 2010

Catalystpackage Your::App::Model::Bookstore;use base qw(Catalyst::Model::DBIC::Schema);

use strict;use warnings;

__PACKAGE__->config( schema_class => 'Bookstore::Schema',);

1;

Page 96: DBIx::Class introduction - 2010

Catalystpackage Your::App::Model::Bookstore;use base qw(Catalyst::Model::DBIC::Schema);

use strict;use warnings;

__PACKAGE__->config( schema_class => 'Bookstore::Schema',);

1;

Keep your Scheme in a separate package to your Catalyst application

Page 97: DBIx::Class introduction - 2010

Catalystsub action_name : Local { my ($self, $c) = @_;

my $model = $c->model('Bookstore'); my $author_model = $model->resultset('Authors'); }

1;

Page 98: DBIx::Class introduction - 2010

thanks!

http://leo.cuckoo.org/projects/