Nov 29, 2014
Mystic?
Why are ORMs scary?
Why are ORMs scary?
• Enterprise-y
Why are ORMs scary?
• Enterprise-y
• Loss of control
Why are ORMs scary?
• Enterprise-y
• Loss of control
• History (Class::DBI)
Why are ORMs scary?
• Enterprise-y
• Loss of control
• History (Class::DBI)
• (A triumph of multiple inheritance)
Retort.
Enterprise-y
Enterprise-y
• Enterprise = Java
Enterprise-y
• Enterprise = Java
• DBIx::Class is written in perl
Loss of Control
Loss of Control
• Still programmatic
Loss of Control
• Still programmatic
• Use SQL::Abstract rather than SQL
Loss of Control
• Still programmatic
• Use SQL::Abstract rather than SQL
• Same thing
Loss of Control
• Still programmatic
• Use SQL::Abstract rather than SQL
• Same thing
• (except patches welcome)
Class::DBI
Class::DBI
Sorry Schwern
Why DBIx::Class?
• I like it. You’ll see why.
• TIMTOWDI:
• Rose::DB
• Alzabo
Objects
• Relations are only a third of an ORM
Objects
• Relations are only a third of an ORM
• What’s an object?
Objects
• Relations are only a third of an ORM
• What’s an object?
• Database columns
Objects
• Relations are only a third of an ORM
• What’s an object?
• Database columns
• Table
Objects
• Relations are only a third of an ORM
• What’s an object?
• Database columns
• Table
• Indexes
A Use Case
Beer
Yes, Beer.
Or...
• Beer has many distributers
• Beer has many reviews
• Beer belongs to a brewer
Which means...package Beer::Schema::Beer;
use base 'DBIx::Class';
__PACKAGE__->load_components( qw|Core| );
__PACKAGE__->table('beer');
__PACKAGE__->add_columns(
'pk1' =>
{ data_type => 'integer', size => 16, is_nullable => 0, is_auto_increment => 1 },
'name' =>
{ data_type => 'varchar', size => 128, is_nullable => 0 },
'brewer_pk1' =>
{ data_type => 'integer', size => 16, is_nullable => 0, is_foreign_key => 1 },
);
__PACKAGE__->set_primary_key('pk1');
1;
And indexes:
__PACKAGE__->add_unique_index(...);
That gives you:
• Deployable SQL (CREATE TABLE, etc)
• The foundation for relationships:
•$beer->brewer # DTRT
Managing Relations
• A beer is:
• made by a brewer
• distributed by distributers
• reviewed by people
Simple Relationships
belongs_to is the opposite end of has_many
Simple Relationships
belongs_to is the opposite end of has_many
has_one, might_have means just that
Simple Relationships
belongs_to is the opposite end of has_many
has_one, might_have means just that
many_to_many gets complicated
Creating a relation
__PACKAGE__->belongs_to( ‘brewer’, # Accessor
‘Beer::Schema::Brewer’, # Related Class
‘brewer_pk1’ # My Column);
For simplicity...
Brewers, Distributors and Beers are all easy
All the same, except Beer has a brewer (brewer_pk1)
Using it (Manager)
use Beer::Schema;
my $schema = Beer::Schema ->connect( $dsn );
$schema
# Fetch a result set
my $rs = $schema->resultset(‘Beer’);
Result Sets
# Everything is a result set.
$rs->count; # How many Beers?
Everything is aResult Set
$rs2 = $rs->search({ name => ‘Stout’ }); $rs2->count; # It chains together.
Chained Result Setsare what makeDBIC Great
Result Sets return Result Sets
$rs->search->search->search->search->search->search ->search->search->search->search->search->search ->search->search->search->search->search->search ->search->search->search->search->search->search ->search->search->search->search->search->search ->search->search->search->search->search->search;
Why?
$rs ->search({ $long_query }) ->search({ $more_filters }) ->search({ $even_more });
Actual Use:sub active_members { # All profiles that have purchased a membership. my $query = $rs->search( { 'purchase.saved_object_key' => 'membership', 'membership.expiration_date' => \'>= NOW()' }, { join => { profile_transactions => { 'transaction' => { 'link_transaction_purchase' => { 'purchase' => 'membership' } } }, }, prefetch => [ 'state', 'country', { profile_transactions => { 'transaction' => { 'link_transaction_purchase' => { 'purchase' => 'membership' } } }, } ],
group_by => [ qw/membership_id/ ] } );}
In SQL:
SELECT ... FROM table_profiles me LEFT JOIN profile_transaction profile_transactions ON ( profile_transactions.profile_id = me.profile_id ) JOIN nasa_transactions transaction ON ( transaction.transaction_id = profile_transactions.transaction_id ) LEFT JOIN link_trans_pp link_transaction_purchase ON ( link_transaction_purchase.transaction_id = transaction.transaction_id ) JOIN purchased_products purchase ON ( purchase.purchased_id = link_transaction_purchase.purchased_id ) JOIN nasa_membership membership ON ( membership.membership_id = purchase.saved_product_id ) JOIN state_lookup state ON ( state.state_lookup_id = me.state ) JOIN country_lookup country ON ( country.country_lookup_id = me.country_id ) WHERE ( membership.expiration_date >= NOW() AND purchase.saved_object_key = 'membership' )
Now:
my $query = $schema->resultset(‘Profile’)->active_members;
$query->count; # How many?$query->search({ first_name => ‘Bob’ }); # All matching members named Bob
$query->search({ first_name => ‘Bob’ })->count;
while ( my $profile = $query->next ) { $profile->cars; # Get all of this persons cars}
# Clean, no ugly SQL
Pretty.
Even More:Managing your schema
Create Table Statements
$schema->create_ddl_dir(
[ 'SQLite', 'MySQL', ‘PostgreSQL’ ],
$VERSION,
"$destination"
);
SQLite
CREATE TABLE beer (
pk1 INTEGER PRIMARY KEY NOT NULL,
name varchar(128) NOT NULL,
brewer_pk1 integer(16) NOT NULL
);
MySQLDROP TABLE IF EXISTS `beer`;
--
-- Table: `beer`
--
CREATE TABLE `beer` (
`pk1` integer(16) NOT NULL auto_increment,
`name` varchar(128) NOT NULL,
`brewer_pk1` integer(16) NOT NULL,
INDEX (`pk1`),
INDEX (`brewer_pk1`),
PRIMARY KEY (`pk1`),
CONSTRAINT `beer_fk_brewer_pk1` FOREIGN KEY (`brewer_pk1`) REFERENCES `brewer` (`pk1`) ON DELETE CASCADE ON UPDATE CASCADE
) Type=InnoDB;
PostgreSQL--
-- Table: beer
--
DROP TABLE beer CASCADE;
CREATE TABLE beer (
pk1 bigserial NOT NULL,
name character varying(128) NOT NULL,
brewer_pk1 bigint NOT NULL,
PRIMARY KEY (pk1)
);
Get a working database
$schema->deploy; # Yes, it is this simple.
And now for tests