(LITERALLY)
Matt S. Trout (MST) - Project Founder
Peter Rabbitson (RIBASUSHI) - Maintainer
Jess Robinson (CASTAWAY) - Documentation
Mailing List
IRC: irc.perl.org#dbix-class
... We need an interface / abstraction layer
Enter ORMs
Column
Row
Table
Table <==>
Row
Column <==>
<==>
Object
Class
Attribute
Table <==> Result Class
<==>
Row
Result
Column <==> Result Class 'column'
Result of a query <==> DBIx::Class::ResultSet
DB definition <==> DBIx::Class::Schema
Create, Retrieve, Update & Delete operations
$schema->resultset('Artists')->create({
id => 1234,
name => 'The Rolling Stones',
});
$schema->resultset('Albums')->create({
id => 123,
name => 'Beggars Banquet',
artist => 1234, # artist id
year => 1968,
});
my $albums_rs = $schema->resultset('Albums')->search({
year => '2010',
});
$albums_rs = $albums_rs->search({
name => { '!=' => 'Greatest Hits' },
});
# Returns ResultSet in scalar context
my $albums = $schema->resultset('Albums')->search({
name => { '!=' => 'Greatest Hits' },
});
# Returns a list of rows in list context
my @albums = $schema->resultset('Albums')->search({
name => { '!=' => 'Greatest Hits' },
});
my @albums = $albums->all;
my $first_album = $albums->first;
my $count = $albums->count;
while (my $album = $schema->resultset('Albums')->next) {
print "title: ".$album->title."(" . $album->year.")\n";
}
Watch out !
# Iterating over a ResultSet
while (my $album = $albums->next) {
print "title: ".$album->title."(" . $album->year.")\n";
}
==> Infinite loop
my $row = $schema->resultset('Albums')->find({
name => 'Greatest Hits',
});
my $row = $schema->resultset('People')->find({
name => 'Juan',
dni => '11223344A',
});
# returns MyApp::Schema::Result::People
DBIx::Class::Storage::DBI::select_single():
Query returned more than one row
my $result = $schema->resultset('People')->find({
dni => '11223344A',
});
$result->update({ age => 15 });
$result->email('juan@juan.com');
$result->age(15);
$result->update;
my $rs = $schema->resultset('People')->search({
name => 'juan',
});
$rs->update({ age => 15 });
my $row = $schema->resultset('People')->find({
dni=> '12341234A',
});
$row->delete;
my $rs = $schema->resultset('People')->search({
name => 'juan',
});
$rs->delete;
Defining your DBIx::Class Schema
package MyMusic::Schema;
use strict;
use warnings;
use base 'DBIx::Class::Schema';
# Loads all 'Result' & 'ResultSet' classes
# ...if well namespaced!
__PACKAGE__->load_namespaces;
1;
# Schema class:
<app_name>::Schema
# Result classes:
<app_name>::Schema::Result::<table_name>
# ResultSet classes:
<app_name>::Schema::ResultSet::<table_name>
# Examples:
MyMusic::Schema; # schema
MyMusic::Schema::Result::Albums;
MyMusic::Schema::ResultSet::Albums;
use MyMusic::Schema;
my $schema = MyMusic::Schema->connect(
'dbi:mysql:dbname=my_music;host=localhost','uree','1234'
);
my $artists = $schema->resultset('Artist')->search();
package MyMusic::Schema;
use strict;
use warnings;
use base 'DBIx::Class::Schema';
# Loads all 'Result' & 'ResultSet' classes
__PACKAGE__->load_namespaces;
# Convenience connection method
sub connect_from_config {
return __PACKAGE__->connect(
"dbi:mysql:database=my_music;host=localhost",
"uree",
"1234",
);
}
1;
package MyMusic::Schema;
use strict;
use warnings;
use base 'DBIx::Class::Schema';
# Loads all 'Result' & 'ResultSet' classes
__PACKAGE__->load_namespaces;
# Convenience connection method
sub connect_from_config {
return __PACKAGE__->connect(
"dbi:mysql:database=my_music;host=localhost;"
."mysql_read_default_file=/home/uree/.my.cnf;"
."mysql_read_default_group=application;",
undef,
undef,
);
}
1;
use MyMusic::Schema;
my $schema = MyMusic::Schema->connect(
'dbi:mysql:dbname=my_music;host=localhost','uree','1234'
);
use MyMusic::Schema;
my $schema = MyMusic::Schema->connect_from_config();
VS
package MyMusic::Schema::Result::Artist;
use base 'DBIx::Class::Core';
__PACKAGE__->table("artists");
__PACKAGE__->add_columns(
"id", {
data_type => "mediumint",
extra => { unsigned => 1 },
is_auto_increment => 1,
is_nullable => 0,
},
"name", {
data_type => "varchar",
is_nullable => 0,
size => 50
},
});
__PACKAGE__->set_primary_key("id");
__PACKAGE__->add_unique_constraint("name", ["name"]); # name, [cols]
Define Result Classes
... because wait; wasnt all this about relational world?
Artists
Albums
1 - N
1 - 1
People
couple
N - M
Artists
Festivals
Important: Relationships in DBIx::Class are defined from the perspective of each one of the tables.
.... Exactly, they are defined @ the Result Class :D
Artists
Albums
1 - N
package MyMusic::Schema::Result::Album;
__PACKAGE__->belongs_to(
"artist",
"MyMusic::Schema::Result::Artist",
{ "foreign.id" => "self.artist" },
);
package MyMusic::Schema::Result::Artist;
__PACKAGE__->has_many(
"albums",
"MyMusic::Schema::Result::Album",
{ "foreign.artist" => "self.id" },
);
People
1 - 1
couple
package MyApp::Schema::Result::People;
__PACKAGE__->has_one(
"couple",
"MyApp::Schema::Result::People",
{ "foreign.couple" => "self.id" },
);
package MyApp::Schema::Result::People;
__PACKAGE__->belongs_to(
"couple",
"MyApp::Schema::Result::People",
{ "foreign.id" => "self.couple" },
);
Artists
Festivals
N - M
package MyMusic::Schema::Result::ArtistsFestivals;
__PACKAGE__->belongs_to(
"festival",
"MyApp::Schema::Result::Festival",
{ "foreign.id" => "self.festival" },
);
__PACKAGE__->belongs_to(
"artist",
"MyApp::Schema::Result::Artist",
{ "foreign.id" => "self.artist" },
);
package MyMusic::Schema::Result::Artist;
__PACKAGE__->has_many(
"artist_festivals",
"MyApp::Schema::Result::ArtistsFestivals",
{ "foreign.artist" => "self.id" },
);
package MyMusic::Schema::Result::Festivals;
__PACKAGE__->has_many(
"artist_festivals",
"MyApp::Schema::Result::ArtistsFestivals",
{ "foreign.festival" => "self.id" },
);
package MyMusic::Schema::Albums;
use strict;
use warnings;
use base 'DBIx::Class::Core';
__PACKAGE__->table("albums");
__PACKAGE__->add_columns(...)
__PACKAGE__->set_primary_key("id");
__PACKAGE__->belongs_to(
"artist",
"MyMusic::Schema::Albums",
{id => "artist" }
);
package MyMusic::Schema::Festivals;
use strict;
use warnings;
use base 'DBIx::Class::Core';
__PACKAGE__->table("festivals");
__PACKAGE__->add_columns(...)
__PACKAGE__->set_primary_key("id");
__PACKAGE__->has_many(
"artists_festivals",
"MyMusic::Schema::ArtistsFestivals",
{"foreign.festival" => "self.id"},
);
__PACKAGE__->many_to_many("artists", "artists_festivals", "artist");
package MyMusic::Schema::Result::ArtistsFestivals;
use base 'DBIx::Class::Core';
__PACKAGE__->table("artists_festivals");
__PACKAGE__->add_columns(...)
__PACKAGE__->set_primary_key("artist","festival");
__PACKAGE__->belongs_to(
"festival",
"MyApp::Schema::Result::Festival",
{ "foreign.id" => "self.festival" },
);
__PACKAGE__->belongs_to(
"artist",
"MyApp::Schema::Result::Artist",
{ "foreign.id" => "self.artist" },
);
"well, my DB's got 93 tables..."
"let me handle this for ya"
Dagfinn Ilmari Mannsåker
(ILMARI)
(Separate distribution from DBIx::Class)
AUTOMATES the definition of a DBIx::Class::Schema
EVERYTHING :DD
... HOW DOES THAT WORK?!
#!/usr/bin/env perl
use strict;
use warnings;
use DBIx::Class::Schema::Loader qw/make_schema_at/;
make_schema_at(
'MyMusic::Schema',
{
debug => 1,
dump_directory => './lib'
},
[
'dbi:mysql:dbname=my_music;host=localhost','uree','1234',
],
);
my $albums = $artist->albums; # Rset containing all of the artist's albums
my @albums = $artist->albums; # in list ctxt: returns a list of rows
# VS
my $albums = $schema->resultset('Albums')->search({
artist => $artist->id,
});
$artist->create_related('albums', {
name => 'Black Sands',
year => '2010',
});
$artist->add_to_albums({
name => 'With Teeth',
year => '2005',
});
my $album = $schema->resultset('Albums')->find({ id => 123 });
$artist->add_to_albums($album);
[% artist.albums.count %]
[% artist.albums_rs.count %]
my $artists_albums = $schema->resultset('Artists')->search({
name => {'like' => 'the%'}
},{
join => ['albums'] # relationship name
});
while (my $a = $artists_albums->next) {
print $a->name ." - " . $a->album->title."\n";
}
Wont work as expected
SELECT ar.id, ar.name FROM artists ar
JOIN albums al on ar.id=al.artist;
WHERE ar.name LIKE 'the%';
We need to specify desired columns to retrieve
my $artists_albums = $schema->resultset('Artists')->search({
name => {'like' => 'the%'}
},{
join => ['albums'] # relationship name
'+select' => ['albums.title'],
'+as' => ['albums_title'], # NOT A SQL 'AS' !
});
while (my $a = $artists_albums->next) {
print $a->name ." - " . $a->get_column('albums_title')."\n";
}
SELECT ar.id, ar.name, al.title FROM artists ar
JOIN albums al on ar.id=al.artist;
WHERE ar.name LIKE 'the%';
Works! YAY :)
my $artists_festivals = $schema->resultset('Artists')->search({
name => {'like' => 'the%'}
},{
join => ['festivals'] # many_to_many
'+select' => ['festivals.name'],
'+as' => ['festivals_name'],
});
my $artists_festivals = $schema->resultset('Artists')->search({
name => {'like' => 'the%'}
},{
join => [{ 'artists_festivals' => 'festival'}],
'+select' => ['festival.name'],
'+as' => ['festival_name'],
});
Works! Multi-step join
my $artists_festivals = $schema->resultset('Artists')->search({
name => {'like' => 'the%'}
},{
join => [{ 'artists_festivals' => 'festival'}, 'albums'],
'+select' => ['festival.name'],
'+as' => ['festival_name'],
});
package MyMusic::Schema::Result::Album;
# We could search for albums with NO artist..
__PACKAGE__->belongs_to(
'artist_left',
'MyMusic::Schema::Result::Artist',
{'foreign.id' => 'self.artist'},
{'join_type' => 'left'},
);
my $albums = $schema->resultset('Albums')->search({
-or => [
year => '1968',
year => '2005',
],
},{
order_by => [{-desc => 'year'}],
});
my $album_cnt = $schema->resultset('Albums')->search({},{
group_by => ['artist'],
'+select' => [{count => 'id', -as => 'amount_of_albums'}], #SQL 'AS'
'+as' => ['num_albums'],
order_by => [{-desc => 'amount_of_albums'}],
});
while (my $a = $album_cnt->next) {
print $a->artist->name . " - " . $a->get_column('num_albums')."\n";
}
# $a is an 'album'. Has a relationship 'belongs_to' to artist
# we can navigate $a->artist (returns a result)
# Not the case with a has many (would return a resultset)
my $albums = $schema->resultset('Albums')->search();
while (my $a = $albums->next) {
print $a->artist->name."\n"; # One extra query per iteration!
}
my $albums = $schema->resultset('Albums')->search({},{
prefetch => 'artist', # performs 1 query to retrieve all data
});
while (my $a = $albums->next) {
print $a->artist->name."\n"; # No more extra queries
}
How many queries?
ONE single query
package MyMusic::Schema::Result::Album;
# ...
__PACKAGE__->belongs_to(
"artist",
"MyMusic::Schema::Result::Artist",
{ id => "artist" },
);
# Created by DBIx::Class::Schema::Loader v0.07042 @ 2014-11-08 04:11:58
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:7c+0vxCsC6BigZr2ws9igw
sub title_year {
my($self) = @_;
return $self->title . '-' . $self->year;
}
# You can replace this text with custom code or comments,
# and it will be preserved on regeneration
1;
package MyMusic::Schema::ResultSet::Album;
sub seventies {
my($self) = @_;
return $self->search({
year => {
-between => [1970, 1979],
}
});
}
1;
DBIC_TRACE=1
... Or versioning your DB schema
... For real!