Upload
leo-lapworth
View
6.596
Download
2
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
DBIx::Class (aka DBIC)for (advanced) beginners
Leo Lapworth @ YAPC::EU 2010
http://leo.cuckoo.org/projects/
assumptions
You know a little about Perl and using objects
You know a little bit about databases and using foreign keys
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)
why this talk?
• Help avoid mistakes I made!
• Help learn DBIx::Class faster
• Make your coding easier
table setup
example...
Books
Authors
authors table
CREATE TABLE authors(
id int(8) primary key auto_increment,
name varchar(255)
) engine = InnoDB DEFAULT CHARSET=utf8;
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)
authors table
CREATE TABLE authors(
id int(8) primary key auto_increment,
name varchar(255)
) engine = InnoDB DEFAULT CHARSET=utf8;
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;
tips
Name link fields as singular
Check foreign key is the same field type and size in both tables
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;
CRUD comparedC - CreateR - RetrieveU - UpdateD - Delete
Manual (SQL)
manual: createmy $sth = $dbh->prepare('
INSERT INTO books
(title, author)
values (?,?)
');
$sth->execute( 'A book title',$author_id);
manual: createmy $sth = $dbh->prepare('
INSERT INTO books
(title, author)
values (?,?)
');
$sth->execute(
'A book title',$author_id);
manual: retrievemy $sth = $dbh->prepare('
SELECT title,
authors.name as author_name
FROM books, authors
WHERE books.author = authors.id
');
manual: retrievewhile( my $book = $sth->fetchrow_hashref() ) {
print 'Author of '
. $book->{title}
. ' is '
. $book->{author_name}
. "\n";
}
manual: updatemy $update = $dbh->prepare('
UPDATE books
SET title = ?
WHERE id = ?
');
$update->execute(
'New title',$book_id);
manual: deletemy $delete = $dbh->prepare('
DELETE FROM books
WHERE id = ?
');
$delete->execute($book_id);
DBIx::Class
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.
DBIC: createmy $book = $book_model->create({
title => 'A book title',
author => $author_id,});
DBIC: createmy $pratchett = $author_model->create({
name => 'Terry Pratchett',
});
DBIC: createmy $book = $pratchett->create_related(
'books', {
title => 'Another Discworld book',
});
or
my $book = $pratchett->add_to_books({
title => 'Another Discworld book',
});
DBIC: createmy $book = $pratchett->create_related(
'books', {
title => 'Another Discworld book',
});
or
my $book = $pratchett->add_to_books({
title => 'Another Discworld book',
});
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"
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();
DBIC: retrievewhile( my $book = $books_rs->next() ) {
print 'Author of '
. $book->title()
. ' is '
. $book->author()->name()
. "\n";
}
DBIC: retrievemy $books_rs = $book_model->search({
author => $author_id,
});
Search takes SQL::Abstract formatted queries> perldoc SQL::Abstract
DBIC: update$book->update({
title => 'New title',
});
DBIC: delete$book->delete();
Creating schemas
too much typing!
too much maintenance!
Schema::Loader
Database introspection -> Code
Use namespaces
Use NamespacesSplits logic cleanly
Bookstore::Schema::Result::X
= an individual row
Bookstore::Schema:: ResultSet::X
= searches / results
You can edit this line
Connection details
using your Schema
DEBUGGING
DBIC_TRACE=1 ./your_script.pl
SQL - debugging
INSERT INTO authors (name) VALUES (?): 'Douglas Adams'
INSERT INTO books (author, title) VALUES (?, ?): '1', '42'
overloading
Bookstore::Schema::Result::Books
Bookstore::Schema::ResultSet::Books
Bookstore::Schema::Result::Authors
Bookstore::Schema::ResultSet::Authors
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;
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;
Result::print $book->isbn();
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
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
Result:: (deflating)$book->date_published(DateTime->now);
$book->update();
2008-12-31
Result:: (inflating)
my $date_published = $book->date_published()print $date_published->month_abbr();
Nov
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;
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(), } );}
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;
ResultSets::use Bookstore::Schema;
my $book_model = Bookstore::Schema->resultset('Books');
my $book_rs = $book_model->the_ultimate_books();
my @books = $book_rs->all();
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();
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%'
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();
overloading before new record
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;
relationships
multiple authors
a few relationships
Authors BooksAuthors_and_Books
has_many has_many
belongs_to belongs_to
many_to_many
a few relationships
!
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`;
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
has_many
Books Authors_and_Books
has_many
belongs_to
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
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!));
belongs_to
Books Authors_and_Books
has_many
belongs_to
belongs_topackage Bookstore::Schema::Result::AuthorAndBooks;
__PACKAGE__->belongs_to( "book", "Bookstore::Schema::Result::Books", { id => "book" });
# This is auto generated by Schema::Loader
belongs_topackage Bookstore::Schema::Result::AuthorAndBooks;
__PACKAGE__->belongs_to( "book", # Accessor name "Bookstore::Schema::Result::Books",
# Related class { id => "book" } # Relationship);
same for Authors
Authors Authors_and_Books
has_many
belongs_to
with no coding...
Authors BooksAuthors_and_Books
has_many has_many
belongs_to belongs_to
many_to_many
Authors BooksAuthors_and_Books
has_many has_many
belongs_to belongs_to
many_to_many
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
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;
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
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',
});
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';
using many_to_many$author->add_to_books($book);
$book->add_to_authors($author_1);
$book->add_to_authors($author_2);
in 16 lines of code
Authors BooksAuthors_and_Books
has_many has_many
belongs_to belongs_to
many_to_many
errors
Read them closely!
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.
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.
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
thanks
http://leo.cuckoo.org/projects/
Time for bonus slides?
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
Catalystpackage Your::App::Model::Bookstore;use base qw(Catalyst::Model::DBIC::Schema);
use strict;use warnings;
__PACKAGE__->config( schema_class => 'Bookstore::Schema',);
1;
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
Catalystsub action_name : Local { my ($self, $c) = @_;
my $model = $c->model('Bookstore'); my $author_model = $model->resultset('Authors'); }
1;
thanks!
http://leo.cuckoo.org/projects/