X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FManual%2FCookbook.pod;h=bde68eae14f95cbcee2e93a1140c677fccee3b29;hb=cd87b928aef68bba9cff5a681c571939aa07fb0e;hp=110091fc3d9c107b0905ca44a2a15d8af8a661f7;hpb=bc96f2606c021439a25e1376db53fe418cf4bb49;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/Manual/Cookbook.pod b/lib/DBIx/Class/Manual/Cookbook.pod index 110091f..bde68ea 100644 --- a/lib/DBIx/Class/Manual/Cookbook.pod +++ b/lib/DBIx/Class/Manual/Cookbook.pod @@ -19,19 +19,8 @@ paged resultset, which will fetch only a defined number of records at a time: return $rs->all(); # all records for page 1 -The C attribute does not have to be specified in your search: - - my $rs = $schema->resultset('Artist')->search( - undef, - { - rows => 10, - } - ); - - return $rs->page(1); # DBIx::Class::ResultSet containing first 10 records - -In either of the above cases, you can get a L object for the -resultset (suitable for use in e.g. a template) using the C method: +You can get a L object for the resultset (suitable for use +in e.g. a template) using the C method: return $rs->pager(); @@ -116,7 +105,7 @@ reference (this is a feature of L). Say you want to run a complex custom query on your user data, here's what you have to add to your User class: - package My::Schema::User; + package My::Schema::Result::User; use base qw/DBIx::Class/; @@ -160,10 +149,10 @@ files (instead of stuffing all of them into the same resultset class), you can achieve the same with subclassing the resultset class and defining the ResultSource there: - package My::Schema::UserFriendsComplex; + package My::Schema::Result::UserFriendsComplex; - use My::Schema::User; - use base qw/My::Schema::User/; + use My::Schema::Result::User; + use base qw/My::Schema::Result::User/; __PACKAGE__->table('dummy'); # currently must be called before anything else @@ -248,29 +237,49 @@ any of your aliases using either of these: =head2 SELECT DISTINCT with multiple columns - my $rs = $schema->resultset('Foo')->search( + my $rs = $schema->resultset('Artist')->search( {}, { - select => [ - { distinct => [ $source->columns ] } - ], - as => [ $source->columns ] # remember 'as' is not the same as SQL AS :-) + columns => [ qw/artist_id name rank/ ], + distinct => 1 + } + ); + + my $rs = $schema->resultset('Artist')->search( + {}, + { + columns => [ qw/artist_id name rank/ ], + group_by => [ qw/artist_id name rank/ ], } ); + # Equivalent SQL: + # SELECT me.artist_id, me.name, me.rank + # FROM artist me + # GROUP BY artist_id, name, rank + =head2 SELECT COUNT(DISTINCT colname) - my $rs = $schema->resultset('Foo')->search( + my $rs = $schema->resultset('Artist')->search( {}, { - select => [ - { count => { distinct => 'colname' } } - ], - as => [ 'count' ] + columns => [ qw/name/ ], + distinct => 1 + } + ); + + my $rs = $schema->resultset('Artist')->search( + {}, + { + columns => [ qw/name/ ], + group_by => [ qw/name/ ], } ); - my $count = $rs->next->get_column('count'); + my $count = $rs->count; + + # Equivalent SQL: + # SELECT COUNT( * ) FROM (SELECT me.name FROM artist me GROUP BY me.name) count_subq: =head2 Grouping results @@ -295,6 +304,58 @@ Please see L documentation if you are in any way unsure about the use of the attributes above (C< join >, C< select >, C< as > and C< group_by >). +=head2 Subqueries (EXPERIMENTAL) + +You can write subqueries relatively easily in DBIC. + + my $inside_rs = $schema->resultset('Artist')->search({ + name => [ 'Billy Joel', 'Brittany Spears' ], + }); + + my $rs = $schema->resultset('CD')->search({ + artist_id => { 'IN' => $inside_rs->get_column('id')->as_query }, + }); + +The usual operators ( =, !=, IN, NOT IN, etc) are supported. + +B: You have to explicitly use '=' when doing an equality comparison. +The following will B work: + + my $rs = $schema->resultset('CD')->search({ + artist_id => $inside_rs->get_column('id')->as_query, + }); + +=head3 Support + +Subqueries are supported in the where clause (first hashref), and in the +from, select, and +select attributes. + +=head3 Correlated subqueries + + my $cdrs = $schema->resultset('CD'); + my $rs = $cdrs->search({ + year => { + '=' => $cdrs->search( + { artist_id => { '=' => \'me.artist_id' } }, + { alias => 'inner' } + )->get_column('year')->max_rs->as_query, + }, + }); + +That creates the following SQL: + + SELECT me.cdid, me.artist, me.title, me.year, me.genreid, me.single_track + FROM cd me + WHERE year = ( + SELECT MAX(inner.year) + FROM cd inner + WHERE artist_id = me.artist_id + ) + +=head3 EXPERIMENTAL + +Please note that subqueries are considered an experimental feature. + =head2 Predefined searches You can write your own L class by inheriting from it @@ -368,15 +429,20 @@ C attributes: =head2 Using joins and prefetch You can use the C attribute to allow searching on, or sorting your -results by, one or more columns in a related table. To return all CDs matching -a particular artist name: +results by, one or more columns in a related table. + +This requires that you have defined the L. For example : + + My::Schema::CD->has_many( artists => 'My::Schema::Artist', 'artist_id'); + +To return all CDs matching a particular artist name, you specify the name of the relationship ('artists'): my $rs = $schema->resultset('CD')->search( { - 'artist.name' => 'Bob Marley' + 'artists.name' => 'Bob Marley' }, { - join => 'artist', # join the artist table + join => 'artists', # join the artist table } ); @@ -385,16 +451,19 @@ a particular artist name: # JOIN artist ON cd.artist = artist.id # WHERE artist.name = 'Bob Marley' +In that example both the join, and the condition use the relationship name rather than the table name +(see DBIx::Class::Manual::Joining for more details on aliasing ). + If required, you can now sort on any column in the related tables by including -it in your C attribute: +it in your C attribute, (again using the aliased relation name rather than table name) : my $rs = $schema->resultset('CD')->search( { - 'artist.name' => 'Bob Marley' + 'artists.name' => 'Bob Marley' }, { - join => 'artist', - order_by => [qw/ artist.name /] + join => 'artists', + order_by => [qw/ artists.name /] } ); @@ -431,12 +500,12 @@ This allows you to fetch results from related tables in advance: my $rs = $schema->resultset('CD')->search( { - 'artist.name' => 'Bob Marley' + 'artists.name' => 'Bob Marley' }, { - join => 'artist', - order_by => [qw/ artist.name /], - prefetch => 'artist' # return artist data too! + join => 'artists', + order_by => [qw/ artists.name /], + prefetch => 'artists' # return artist data too! } ); @@ -456,9 +525,6 @@ L has now prefetched all matching data from the C table, so no additional SQL statements are executed. You now have a much more efficient query. -Note that as of L 0.05999_01, C I be used with -C relationships. - Also note that C should only be used when you know you will definitely use data from a related table. Pre-fetching related tables when you only need columns from the main table will make performance worse! @@ -576,7 +642,7 @@ CD and Concert, and join CD to LinerNotes: =head2 Multi-step prefetch -From 0.04999_05 onwards, C can be nested more than one relationship +C can be nested more than one relationship deep using the same syntax as a multi-step join: my $rs = $schema->resultset('Tag')->search( @@ -616,8 +682,7 @@ method. AKA getting last_insert_id -If you are using PK::Auto (which is a core component as of 0.07), this is -straightforward: +Thanks to the core component PK::Auto, this is straightforward: my $foo = $rs->create(\%blah); # do more stuff @@ -632,7 +697,7 @@ Employ the standard stringification technique by using the C module. To make an object stringify itself as a single column, use something -like this (replace C with the column/method of your choice): +like this (replace C with the column/method of your choice): use overload '""' => sub { shift->name}, fallback => 1; @@ -676,6 +741,48 @@ Just use C instead, then check C: # do whatever else you wanted if it was a new row } +=head2 Static sub-classing DBIx::Class result classes + +AKA adding additional relationships/methods/etc. to a model for a +specific usage of the (shared) model. + +B + + package My::App::Schema; + + use base DBIx::Class::Schema; + + # load subclassed classes from My::App::Schema::Result/ResultSet + __PACKAGE__->load_namespaces; + + # load classes from shared model + load_classes({ + 'My::Shared::Model::Result' => [qw/ + Foo + Bar + /]}); + + 1; + +B + + package My::App::Schema::Result::Baz; + + use strict; + use warnings; + use base My::Shared::Model::Result::Baz; + + # WARNING: Make sure you call table() again in your subclass, + # otherwise DBIx::Class::ResultSourceProxy::Table will not be called + # and the class name is not correctly registered as a source + __PACKAGE__->table('baz'); + + sub additional_method { + return "I'm an additional method only needed by this app"; + } + + 1; + =head2 Dynamic Sub-classing DBIx::Class proxy classes AKA multi-class object inflation from one table @@ -699,16 +806,18 @@ below: B - package DB::Schema; + package My::Schema; use base qw/DBIx::Class::Schema/; - __PACKAGE__->load_classes(qw/User/); + __PACKAGE__->load_namespaces; + + 1; B - package DB::Schema::User; + package My::Schema::Result::User; use strict; use warnings; @@ -741,13 +850,15 @@ B print "I am a regular user.\n"; return ; } + + 1; + - - package DB::Schema::User::Admin; + package My::Schema::Result::User::Admin; use strict; use warnings; - use base qw/DB::Schema::User/; + use base qw/My::Schema::Result::User/; sub hello { @@ -759,13 +870,15 @@ B { print "I am doing admin stuff\n"; return ; - } + } + + 1; B test.pl use warnings; use strict; - use DB::Schema; + use My::Schema; my $user_data = { email => 'someguy@place.com', password => 'pass1', @@ -775,7 +888,7 @@ B test.pl password => 'pass2', admin => 1 }; - my $schema = DB::Schema->connection('dbi:Pg:dbname=test'); + my $schema = My::Schema->connection('dbi:Pg:dbname=test'); $schema->resultset('User')->create( $user_data ); $schema->resultset('User')->create( $admin_data ); @@ -813,11 +926,16 @@ To do this simply use L. Wasn't that easy? +Beware, changing the Result class using +L will replace any existing class +completely including any special components loaded using +load_components, eg L. + =head2 Get raw data for blindingly fast results If the L solution above is not fast enough for you, you can use a DBIx::Class to return values -exactly as they come out of the data base with none of the convenience methods +exactly as they come out of the database with none of the convenience methods wrapped round them. This is used like so: @@ -828,13 +946,13 @@ This is used like so: } You will need to map the array offsets to particular columns (you can -use the I