feature doc map
Arthur Axel 'fREW' Schmidt [Sat, 10 Jul 2010 07:11:48 +0000 (02:11 -0500)]
Changes
lib/DBIx/Class/Manual/Features.pod [new file with mode: 0644]

diff --git a/Changes b/Changes
index b0e0a2a..8b9c106 100644 (file)
--- a/Changes
+++ b/Changes
@@ -20,6 +20,8 @@ Revision history for DBIx::Class
         - $rs->pager now uses a lazy count to determine the amount of
           total entries only when really needed, instead of doing it
           at instantiation time
+        - New documentation map organized by features
+          (DBIx::Class::Manual::Features)
 
     * Fixes
         - Fix memory leak during populate() on 5.8.x perls
diff --git a/lib/DBIx/Class/Manual/Features.pod b/lib/DBIx/Class/Manual/Features.pod
new file mode 100644 (file)
index 0000000..6f96d73
--- /dev/null
@@ -0,0 +1,665 @@
+=head1 NAME
+
+DBIx::Class::Manual::Features - A boatload of DBIx::Class features with links to respective documentation
+
+=head1 META
+
+=head2 Large Community
+
+Currently there are 88 people listed as contributors to DBIC.  That ranges
+from documentation help, to test help, to added features, to entire database
+support.
+
+=head2 Active Community
+
+Currently (June 9, 2010) 6 active branches (commited to
+in the last two weeks) in git.  Last release (0.08122)
+had 14 new features, and 16 bug fixes.  Of course that
+L<ebbs and flows|http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=dbsrgits/DBIx-Class.git;a=blob;f=Changes>.)
+
+=head2 Responsive Community
+
+=over 1
+
+=item I needed MSSQL order-by support; the community helped me add support
+
+=item generally very welcoming of people willing to help
+
+=back
+
+=head1 General ORM
+
+These are things that are in most other ORMs, but are still reasons to use
+DBIC over raw SQL.
+
+=head2 Cross DB
+
+The vast majority of code should run on all databases without needing tweaking
+
+=head2 Basic CRUD
+
+=over 1
+
+=item C - Create
+
+=item R - Retrieve
+
+=item U - Update
+
+=item D - Delete
+
+=back
+
+=head2 SQL: Create
+
+ my $sth = $dbh->prepare('
+    INSERT INTO books
+    (title, author_id)
+    values (?,?)
+ ');
+
+ $sth->execute( 'A book title', $author_id );
+
+=head2 DBIC: Create
+
+ my $book = $book_rs->create({
+    title     => 'A book title',
+    author_id => $author_id,
+ });
+
+See L<DBIx::Class::ResultSet/create>
+
+=over 1
+
+=item No need to pair placeholders and values
+
+=item Automatically gets autoincremented id for you
+
+=item Transparently uses INSERT ... RETURNING for databases that support it
+
+=back
+
+=head2 SQL: Read
+
+ my $sth = $dbh->prepare('
+    SELECT title,
+    authors.name as author_name
+    FROM books, authors
+    WHERE books.author = authors.id
+ ');
+
+ while ( my $book = $sth->fetchrow_hashref ) {
+   say "Author of $book->{title} is $book->{author_name}";
+ }
+
+=head2 DBIC: Read
+
+ my $book = $book_rs->find($book_id);
+
+or
+
+ my $book = $book_rs->search({ title => 'A book title' }, { rows => 1 })->next;
+
+or
+
+ my @books = $book_rs->search({ author => $author_id })->all;
+
+or
+
+ while( my $book = $books_rs->next ) {
+   printf "Author of %s is %s\n", $book->title, $book->author->name;
+ }
+
+See L<DBIx::Class::ResultSet/find>, L<DBIx::Class::ResultSet/search>, L<DBIx::Class::ResultSet/next>, and L<DBIx::Class::ResultSet/all>
+
+B<TMTOWTDI!>
+
+=head2 SQL: Update
+
+ my $update = $dbh->prepare('
+    UPDATE books
+    SET title = ?
+    WHERE id = ?
+ ');
+
+ $update->execute( 'New title', $book_id );
+
+=head2 DBIC: Update
+
+ $book->update({ title => 'New title' });
+
+See L<DBIx::Class::Row/update>
+
+Will not update unless value changes
+
+=head2 SQL: Delete
+
+ my $delete = $dbh->prepare('DELETE FROM books WHERE id = ?');
+
+ $delete->execute($book_id);
+
+=head2 DBIC: Delete
+
+ $book->delete
+
+See L<DBIx::Class::Row/delete>
+
+=head2 SQL: Search
+
+ my $sth = $dbh->prepare('
+   SELECT title,
+   authors.name as author_name
+   FROM books
+   WHERE books.name LIKE "%monte cristo%" AND
+   books.topic = "jailbreak"
+ ');
+
+=head2 DBIC: Search
+
+ my $book = $book_rs->search({
+    'me.name'  => { -like => '%monte cristo%' },
+    'me.topic' => 'jailbreak',
+ })->next;
+
+=over 1
+
+=item See L<SQL::Abstract>, L<DBIx::Class::ResultSet/next>, and L<DBIx::Class::ResultSet/search>
+
+=item (kinda) introspectible
+
+=item Prettier than SQL
+
+=back
+
+=head2 OO Overidability
+
+=over 1
+
+=item Override new if you want to do validation
+
+=item Override delete if you want to disable deletion
+
+=item and on and on
+
+=back
+
+=head2 Convenience Methods
+
+=over 1
+
+=item L<DBIx::Class::ResultSet/find_or_create>
+
+=item L<DBIx::Class::ResultSet/update_or_create>
+
+=back
+
+=head2 Non-column methods
+
+Need a method to get a user's gravatar URL?  Add a C<gravatar_url> method to the
+Result class
+
+=head2 RELATIONSHIPS
+
+=over 1
+
+=item L<DBIx::Class::Relationship/belongs_to>
+
+=item L<DBIx::Class::Relationship/has_many>
+
+=item L<DBIx::Class::Relationship/might_have>
+
+=item L<DBIx::Class::Relationship/has_one>
+
+=item L<DBIx::Class::Relationship/many_to_many>
+
+=item SET AND FORGET
+
+=back
+
+=head1 DBIx::Class Specific Features
+
+These things may be in other ORM's, but they are very specific, so doubtful
+
+=head2 ->deploy
+
+Create a database from your DBIx::Class schema.
+
+ my $schema = Frew::Schema->connect( $dsn, $user, $pass );
+
+ $schema->deploy
+
+See L<DBIx::Class::Schema/deploy>.
+
+See also: L<DBIx::Class::DeploymentHandler>
+
+=head2 Schema::Loader
+
+Create a DBIx::Class schema from your database.
+
+ package Frew::Schema;
+
+ use strict;
+ use warnings;
+
+ use base 'DBIx::Class::Schema::Loader';
+
+ __PACKAGE__->loader_options({
+    naming => 'v7',
+    debug  => $ENV{DBIC_TRACE},
+ });
+
+ 1;
+
+ # elsewhere...
+
+ my $schema = Frew::Schema->connect( $dsn, $user, $pass );
+
+See L<DBIx::Class::Schema::Loader> and L<DBIx::Class::Schema::Loader::Base/CONSTRUCTOR_OPTIONS>.
+
+=head2 Populate
+
+Made for inserting lots of rows very quicky into database
+
+ $schema->populate([ Users =>
+    [qw( username password )],
+    [qw( frew     >=4char$ )],
+    [qw(      ...          )],
+    [qw(      ...          )],
+ );
+
+See L<DBIx::Class::Schema/populate>
+
+I use populate L<here|http://blog.afoolishmanifesto.com/archives/1255> to export our whole
+(200M~) db to SQLite
+
+=head2 Multicreate
+
+Create an object and it's related objects all at once
+
+ $schema->resultset('Author')->create({
+    name => 'Stephen King',
+    books => [{ title => 'The Dark Tower' }],
+    address => {
+       street => '123 Turtle Back Lane',
+       state  => { abbreviation => 'ME' },
+       city   => { name => 'Lowell'     },
+    },
+ });
+
+See L<DBIx::Class::ResultSet/create>
+
+=over 1
+
+=item books is a has_many
+
+=item address is a belongs_to which in turn belongs to state and city each
+
+=item for this to work right state and city must mark abbreviation and name as unique
+
+=back
+
+=head2 Extensible
+
+DBIx::Class helped pioneer fast MI in Perl 5 with Class::C3, so it is made to
+allow extensions to nearly every part of it.
+
+=head2 Extensibility example: DBIx::Class::Helpers
+
+=over 1
+
+=item L<DBIx::Class::Helper::ResultSet::IgnoreWantarray>
+
+=item L<DBIx::Class::Helper::ResultSet::Random>
+
+=item L<DBIx::Class::Helper::ResultSet::SetOperations>
+
+=item L<DBIx::Class::Helper::Row::JoinTable>
+
+=item L<DBIx::Class::Helper::Row::NumifyGet>
+
+=item L<DBIx::Class::Helper::Row::SubClass>
+
+=item L<DBIx::Class::Helper::Row::ToJSON>
+
+=item L<DBIx::Class::Helper::Row::StorageValues>
+
+=item L<DBIx::Class::Helper::Row::OnColumnChange>
+
+=back
+
+=head2 Extensibility example: DBIx::Class::TimeStamp
+
+=over 1
+
+=item See L<DBIx::Class::TimeStamp>
+
+=item Cross DB
+
+=item set_on_create
+
+=item set_on_update
+
+=back
+
+=head2 Extensibility example: Kioku
+
+=over 1
+
+=item See L<DBIx::Class::Schema::KiokuDB>
+
+=item Kioku is the new hotness
+
+=item Mix RDBMS with Object DB
+
+=back
+
+=head2 Result vs ResultSet
+
+=over 1
+
+=item Result == Row
+
+=item ResultSet == Query Plan
+
+=over 1
+
+=item Internal Join Optimizer for all DB's (!!!)
+
+=back
+
+=item (less important but...)
+
+=item ResultSource == Queryable collection of rows (Table, View, etc)
+
+=item Storage == Database
+
+=item Schema == associates a set of ResultSources with a Storage
+
+=back
+
+=head2 ResultSet methods
+
+ package MyApp::Schema::ResultSet::Book;
+
+ use strict;
+ use warnings;
+
+ use base 'DBIx::Class::ResultSet';
+
+ sub good {
+    my $self = shift;
+    $self->search({
+       $self->current_source_alias . '.rating' => { '>=' => 4 }
+    })
+ };
+
+ sub cheap {
+    my $self = shift;
+    $self->search({
+       $self->current_source_alias . '.price' => { '<=' => 5}
+    })
+ };
+
+ # ...
+
+ 1;
+
+See L<DBIx::Class::Manual::Cookbook/Predefined searches>
+
+=over 1
+
+=item All searches should be ResultSet methods
+
+=item Name has obvious meaning
+
+=item L<DBIx::Class::ResultSet/current_source_alias> helps things to work no matter what
+
+=back
+
+=head2 ResultSet method in Action
+
+ $schema->resultset('Book')->good
+
+=head2 ResultSet Chaining
+
+ $schema->resultset('Book')
+    ->good
+    ->cheap
+    ->recent
+
+=head2 search_related
+
+ my $score = $schema->resultset('User')
+    ->search({'me.userid' => 'frew'})
+    ->related_resultset('access')
+    ->related_resultset('mgmt')
+    ->related_resultset('orders')
+    ->telephone
+    ->search_related( shops => {
+       'shops.datecompleted' => {
+          -between => ['2009-10-01','2009-10-08']
+       }
+    })->completed
+    ->related_resultset('rpt_score')
+    ->search(undef, { rows => 1})
+    ->get_column('raw_scores')
+    ->next;
+
+The SQL that this produces (with placeholders filled in for clarity's sake)
+on our system (Microsoft SQL) is:
+
+ SELECT raw_scores
+   FROM (
+     SELECT raw_scores, ROW_NUMBER() OVER (
+         ORDER BY (
+             SELECT (1)
+           )
+       ) AS rno__row__index
+       FROM (
+         SELECT rpt_score.raw_scores
+           FROM users me
+           JOIN access access
+             ON access.userid = me.userid
+           JOIN mgmt mgmt
+             ON mgmt.mgmtid = access.mgmtid
+           JOIN [order] orders
+             ON orders.mgmtid = mgmt.mgmtid
+           JOIN shop shops
+             ON shops.orderno = orders.orderno
+           JOIN rpt_scores rpt_score
+             ON rpt_score.shopno = shops.shopno
+         WHERE (
+           datecompleted IS NOT NULL AND
+           (
+             (shops.datecompleted BETWEEN '2009-10-01' AND '2009-10-08')  AND
+             (type = '1' AND me.userid = 'frew')
+           )
+         )
+       ) rpt_score
+   ) rpt_score
+ WHERE rno__row__index BETWEEN 1 AND 1
+
+See: L<DBIx::Class::ResultSet/related_resultset>, L<DBIx::ClassResultSet/search_related>, and L<DBIx::Class::ResultSet/get_column>.
+
+=head2 bonus rel methods
+
+ my $book = $author->create_related(
+    books => {
+       title => 'Another Discworld book',
+    }
+ );
+
+ my $book2 = $pratchett->add_to_books({
+    title => 'MOAR Discworld book',
+ });
+
+See L<DBIx::Class::Relationship::Base/create_related> and L<DBIx::Class::Relationship::Base/add_to_$rel>
+
+Note that it automaticaly fills in foreign key for you
+
+=head2 Excellent Transaction Support
+
+ $schema->txn_do(sub {
+    ...
+ });
+
+ $schema->txn_begin; # <-- low level
+ # ...
+ $schema->txn_commit;
+
+See L<DBIx::Class::Schema/txn_do>, L<DBIx::Class::Schema/txn_begin>,
+and L<DBIx::Class::Schema/txn_commit>.
+
+=head2 InflateColumn
+
+ package Frew::Schema::Result::Book;
+
+ use strict;
+ use warnings;
+
+ use base 'DBIx::Class::Core';
+
+ use DateTime::Format::MySQL;
+
+ # Result code here
+
+ __PACKAGE__->load_components('InflateColumn');
+
+ __PACKAGE__->inflate_column(
+    date_published => {
+       inflate => sub { DateTime::Format::MySQL->parse_date( shift ) },
+       deflate => sub { shift->ymd },
+    },
+ );
+
+See L<DBIx::Class::InflateColumn>, L<DBIx::Class::InflateColumn/inflate_column>, and
+L<DBIx::Class::InflateColumn::DateTime>.
+
+=head2 InflateColumn: deflation
+
+ $book->date_published(DateTime->now);
+ $book->update;
+
+=head2 InflateColumn: inflation
+
+ say $book->date_published->month_abbr; # Nov
+
+=head2 FilterColumn
+
+ package Frew::Schema::Result::Book;
+
+ use strict;
+ use warnings;
+
+ use base 'DBIx::Class::Core';
+
+ # Result code here
+
+ __PACKAGE__->load_components('FilterColumn');
+
+ __PACKAGE__->filter_column(
+    length => {
+       to_storage   => 'to_metric',
+       from_storage => 'to_imperial',
+    },
+ );
+
+ sub to_metric   { $_[1] * .305 }
+ sub to_imperial { $_[1] * 3.28 }
+
+See L<DBIx::Class::FilterColumn> and L<DBIx::Class::FilterColumn/filter_column>
+
+=head2 ResultSetColumn
+
+ my $rsc = $schema->resultset('Book')->get_column('price');
+ $rsc->first;
+ $rsc->all;
+ $rsc->min;
+ $rsc->max;
+ $rsc->sum;
+
+See L<DBIx::Class::ResultSetColumn>
+
+=head2 Aggregates
+
+ my @res = $rs->search(undef, {
+    select   => [
+       'price',
+       'genre',
+       { max => price },
+       { avg => price },
+    ],
+    as       => [
+       qw(price genre max_price avg_price)
+    ],
+    group_by => [qw(price genre)],
+ });
+ for (@res) {
+    say $_->price . ' ' . $_->genre;
+    say $_->get_column('max_price');
+    say $_->get_column('min_price');
+ }
+
+See L<DBIx::Class::ResultSet/select>, L<DBIx::Class::ResultSet/as>, and
+L<DBIx::Class::ResultSet/group_by>
+
+=over 1
+
+=item Careful, get_column can basicaly mean B<three> things
+
+=item private in which case you should use an accessor
+
+=item public for what there is no accessor for
+
+=item public for get resultset column (prev example)
+
+=back
+
+=head2 HRI
+
+ $rs->search(undef, {
+   result_class => 'DBIx::Class::ResultClass::HashRefInflator',
+ });
+
+See L<DBIx::Class::ResultSet/result_class> and L<DBIx::Class::ResultClass::HashRefInflator>.
+
+=over 1
+
+=item Easy on memory
+
+=item Mega fast
+
+=item Great for quick debugging
+
+=item Great for performance tuning (we went from 2m to < 3s)
+
+=back
+
+=head2 Subquery Support
+
+ my $inner_query = $schema->resultset('Artist')
+    ->search({
+     name => [ 'Billy Joel', 'Brittany Spears' ],
+ })->get_column('id')->as_query;
+
+ my $rs = $schema->resultset('CD')->search({
+     artist_id => { -in => $inner_query },
+ });
+
+See L<DBIx::Class::Manual::Cookbook#Subqueries>
+
+=head2 Bare SQL w/ Placeholders
+
+ $rs->update({
+    # !!! SQL INJECTION VECTOR
+    price => \"price + $inc", # DON'T DO THIS
+ });
+
+Better:
+
+ $rs->update({
+    price => \['price + ?', [inc => $inc]],
+ });
+
+See L<SQL::Abstract#Literal_SQL_with_placeholders_and_bind_values_(subqueries)>
+