dbix::class introduction - 2010

Post on 29-Nov-2014

6.596 Views

Category:

Technology

2 Downloads

Preview:

Click to see full reader

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.

TRANSCRIPT

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/

top related