dbix::class beginners

95
DBIx::Class (aka DBIC) for (advanced) beginners Leo Lapworth @ LPW 2008 http://leo.cuckoo.org/projects/

Upload: leo-lapworth

Post on 05-Dec-2014

48.085 views

Category:

Technology


1 download

DESCRIPTION

Introduction to DBIx::Class.

TRANSCRIPT

Page 1: DBIx::Class beginners

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

Leo Lapworth @ LPW 2008

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

Page 2: DBIx::Class beginners

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 beginners

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 beginners

why this talk?

• Help avoid mistakes I made!

• Help learn DBIx::Class faster

• Make your coding easier

Page 5: DBIx::Class beginners

point of note

"Debugging is twice as hard as writing the code in the first place.

Therefore, if you write the code as cleverly as possible, you are, by

definition, not smart enough to debug it." - Brian W. Kernighan

This talk is about making it easy so we you are less

likely to get confused

Page 6: DBIx::Class beginners

table setup

Page 7: DBIx::Class beginners

example...

Books

Authors

Page 8: DBIx::Class beginners

authors table

CREATE TABLE authors(

id int(8) primary key auto_increment,

name varchar(255)

) engine = InnoDB DEFAULT CHARSET=utf8;

Page 9: DBIx::Class beginners

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 10: DBIx::Class beginners

authors table

CREATE TABLE authors(

id int(8) primary key auto_increment,

name varchar(255)

) engine = InnoDB DEFAULT CHARSET=utf8;

Page 11: DBIx::Class beginners

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 12: DBIx::Class beginners

tips

Name link fields as singular

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

Page 13: DBIx::Class beginners

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 14: DBIx::Class beginners

CRUD comparedC - CreateR - RetrieveU - UpdateD - Delete

Page 15: DBIx::Class beginners

Manual (SQL)

Page 16: DBIx::Class beginners

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

INSERT INTO books

(title, author)

values (?,?)

');

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

Page 17: DBIx::Class beginners

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

INSERT INTO books

(title, author)

values (?,?)

');

$sth->execute(

'A book title',$author_id);

Page 18: DBIx::Class beginners

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

SELECT title,

authors.name as author_name

FROM books, authors

WHERE books.author = authors.id

');

Page 19: DBIx::Class beginners

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

print 'Author of '

. $book->{title}

. ' is '

. $book->{author_name}

. "\n";

}

Page 20: DBIx::Class beginners

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

UPDATE books

SET title = ?

WHERE id = ?

');

$update->execute(

'New title',$book_id);

Page 21: DBIx::Class beginners

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

DELETE FROM books

WHERE id = ?

');

$delete->execute($book_id);

Page 22: DBIx::Class beginners

DBIx::Class

Page 23: DBIx::Class beginners

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 24: DBIx::Class beginners

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

title => 'A book title',

author => $author_id,});

Page 25: DBIx::Class beginners

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

name => 'Terry Pratchett',

});

Page 26: DBIx::Class beginners

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 beginners

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

'books', {

title => 'Another Discworld book',

});

or

my $book = $pratchett->add_to_books({

title => 'Another Discworld book',

});

Page 28: DBIx::Class beginners

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 29: DBIx::Class beginners

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 30: DBIx::Class beginners

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

print 'Author of '

. $book->title()

. ' is '

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

. "\n";

}

Page 31: DBIx::Class beginners

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

author => $author_id,

});

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

Page 32: DBIx::Class beginners

DBIC: update$book->update({

title => 'New title',

});

Page 33: DBIx::Class beginners

DBIC: delete$book->delete();

Page 34: DBIx::Class beginners

Creating models

Page 35: DBIx::Class beginners
Page 36: DBIx::Class beginners
Page 37: DBIx::Class beginners

too much typing!

too much maintenance!

Page 38: DBIx::Class beginners

Schema::Loader

Tip

LPW::DBIC::Result::XXLPW::DBIC::ResultSet::XX

Page 39: DBIx::Class beginners

splitting logic cleanly

LPW::DBIC::Result::XXX = an individual row

LPW::DBIC::ResultSet::XXX = searches / results

Page 40: DBIx::Class beginners

using your Schema

Page 41: DBIx::Class beginners

DEBUGGING

DBIC_TRACE=1 ./your_script.pl

Page 42: DBIx::Class beginners

Schema::LoaderLPW::DBIC::Result::Authors->table("authors");LPW::DBIC::Result::Authors->add_columns( "id", { data_type => "INT", default_value => undef, is_nullable => 0, size => 8 }, "title", { data_type => "VARCHAR", default_value => undef, is_nullable => 1, size => 255, },);LPW::DBIC::Result::Authors->set_primary_key("id");

Page 43: DBIx::Class beginners

LPW::DBIC::Result::Books->table("books");LPW::DBIC::Result::Books->add_columns( "id", { data_type => "INT", default_value => undef, is_nullable => 0, size => 8 }, "name", { data_type => "VARCHAR", default_value => undef, is_nullable => 1, size => 255, }, "author", { data_type => "INT", default_value => undef, is_nullable => 1, size => 8 },);LPW::DBIC::Result::Books->set_primary_key("id");

Schema::Loader

Page 44: DBIx::Class beginners

LPW::DBIC::Result::Authors->has_many("books", "LPW::DBIC::Books", { "foreign.author" => "self.id" });

LPW::DBIC::Result::Books->belongs_to("author", "LPW::DBIC::Authors", { id => "author" });

Schema::Loader

Page 45: DBIx::Class beginners

Schema::Loader

Page 46: DBIx::Class beginners

SQL - debugging

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

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

Page 47: DBIx::Class beginners

overloading

LPW::DBIC::Result::Books

LPW:: DBIC::ResultSet::Books

LPW:: DBIC::Result::Authors

LPW:: DBIC::ResultSet::Authors

Page 48: DBIx::Class beginners

Result::package LPW::DBIC::Result::Books;use base 'DBIx::Class';

use strict;use warnings;

sub isbn {my $self = shift;

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

return $api->isbn();}

1;

Page 49: DBIx::Class beginners

Result::package LPW::DBIC::Result::Books;use base 'DBIx::Class';

use strict;use warnings;

sub isbn {my $self = shift;

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

return $api->isbn();}

1;

Page 50: DBIx::Class beginners

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

Page 51: DBIx::Class beginners

Result:: (inflating)package LPW::DBIC::Result::Books;use base 'DBIx::Class';

use strict;use warnings;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 52: DBIx::Class beginners

Result:: (inflating)package LPW::DBIC::Result::Books;use base 'DBIx::Class';

use strict;use warnings;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 53: DBIx::Class beginners

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

$book->update();

2008-11-29

Page 54: DBIx::Class beginners

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

Nov

Page 55: DBIx::Class beginners

ResultSets::package LPW::DBIC::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 56: DBIx::Class beginners

ResultSets::package LPW::DBIC::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 57: DBIx::Class beginners

ResultSets::package LPW::DBIC::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 58: DBIx::Class beginners

ResultSets::use LPW::DBIC;

my $book_model = LPW::DBIC->resultset('Books');

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

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

Page 59: DBIx::Class beginners

ResultSets::chaininguse LPW::DBIC;

my $book_model = LPW::DBIC->resultset('Books');my $author_model = LPW::DBIC->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 60: DBIx::Class beginners

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 ? ) ) ): '5', '%42%'

Page 61: DBIx::Class beginners

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 62: DBIx::Class beginners

overloading before new record

Page 63: DBIx::Class beginners

overloading before new record

package LPW::DBIC::Result::Authors;use base 'DBIx::Class';

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

# Mess with $attrs

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

1;

Page 64: DBIx::Class beginners

relationships

Page 65: DBIx::Class beginners

multiple authors

Page 66: DBIx::Class beginners

a few relationships

Authors BooksAuthors_and_Books

has_many has_many

belongs_to belongs_to

many_to_many

Page 67: DBIx::Class beginners

a few relationships

!

Page 68: DBIx::Class beginners

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 `author`

Page 69: DBIx::Class beginners

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 70: DBIx::Class beginners

has_many

Books Authors_and_Books

has_many

belongs_to

Page 71: DBIx::Class beginners

has_manypackage LPW::DBIC::Result::Books;

__PACKAGE__->has_many(

"author_and_books", "LPW::DBIC::Result::AuthorAndBooks",

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

);

# This is auto generated by Schema::Loader

Page 72: DBIx::Class beginners

has_manypackage LPW::DBIC::Result::Books;

__PACKAGE__->has_many(

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

Page 73: DBIx::Class beginners

belongs_to

Books Authors_and_Books

has_many

belongs_to

Page 74: DBIx::Class beginners

belongs_topackage LPW::DBIC::Result::AuthorAndBooks;

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

# This is auto generated by Schema::Loader

Page 75: DBIx::Class beginners

belongs_topackage LPW::DBIC::Result::AuthorAndBooks;

__PACKAGE__->belongs_to( "book", # Accessor name "LPW::DBIC::Result::Books", # Related class { id => "book" } # Relationship);

Page 76: DBIx::Class beginners

same for Authors

Authors Authors_and_Books

has_many

belongs_to

Page 77: DBIx::Class beginners

with no coding...

Authors BooksAuthors_and_Books

has_many has_many

belongs_to belongs_to

Page 78: DBIx::Class beginners

many_to_many

Authors BooksAuthors_and_Books

has_many has_many

belongs_to belongs_to

many_to_many

Page 79: DBIx::Class beginners

many_to_manypackage LPW::DBIC::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 80: DBIx::Class beginners

many_to_manypackage LPW::DBIC::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 81: DBIx::Class beginners

many_to_manypackage LPW::DBIC::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 82: DBIx::Class beginners

using many_to_many#!/usr/bin/perl

use LPW::DBIC;

my $author_model = LPW::DBIC->resultset('Authors');

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

})->single();

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

});

Page 83: DBIx::Class beginners

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 84: DBIx::Class beginners

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

$book->add_to_authors($author_1);

$book->add_to_authors($author_2);

Page 85: DBIx::Class beginners

in 16 lines of code

Authors BooksAuthors_and_Books

has_many has_many

belongs_to belongs_to

many_to_many

Page 86: DBIx::Class beginners

errors

Read them closely!

Page 87: DBIx::Class beginners

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

Page 88: DBIx::Class beginners

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

Page 89: DBIx::Class beginners

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 90: DBIx::Class beginners

thanks

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

Time for bonus slides?

Page 91: DBIx::Class beginners

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 92: DBIx::Class beginners

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

use strict;use warnings;

__PACKAGE__->config( schema_class => 'LPW::DBIC',);

1;

Page 93: DBIx::Class beginners

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

use strict;use warnings;

__PACKAGE__->config( schema_class => 'LPW::DBIC',);

1;

Keep your Scheme in a separate package to your Catalyst application

Page 94: DBIx::Class beginners

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

my $model = $c->model('DBIC::LPW'); my $author_model = $model->resultset('Authors'); }

1;

Page 95: DBIx::Class beginners

thanks!

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