[Catalyst-commits] r12318 - in trunk/examples/CatalystAdvent/root/2009: . pen

zamolxes at dev.catalyst.perl.org zamolxes at dev.catalyst.perl.org
Sat Dec 12 00:04:36 GMT 2009


Author: zamolxes
Date: 2009-12-12 00:04:36 +0000 (Sat, 12 Dec 2009)
New Revision: 12318

Added:
   trunk/examples/CatalystAdvent/root/2009/12.pod
Removed:
   trunk/examples/CatalystAdvent/root/2009/pen/withmetadata.pod
Log:
day 12


Copied: trunk/examples/CatalystAdvent/root/2009/12.pod (from rev 12317, trunk/examples/CatalystAdvent/root/2009/pen/withmetadata.pod)
===================================================================
--- trunk/examples/CatalystAdvent/root/2009/12.pod	                        (rev 0)
+++ trunk/examples/CatalystAdvent/root/2009/12.pod	2009-12-12 00:04:36 UTC (rev 12318)
@@ -0,0 +1,285 @@
+=head1 How DBIx::Class::ResultSet::WithMetaData can help keep your controllers clean
+
+=head2 A little note on code cleanliness
+
+When you started using Catalyst with C<DBIx::Class> I'm betting that you
+generated a resultset in your controller, then passed it to your view
+(TT for example), then iterated through it. In short you ended up doing
+loads of really complicated stuff in your TT templates. As you probably
+learned, this is really bad, because you eventually end up with
+complicated and messy templates that are hard to maintain. You should be
+doing your data preparation in Perl, then passing some nicely formatted
+data structure to your view to happily render with a minimum of logic.
+
+A nice approach is to harness the resultset chaining magic that
+C<DBIx::Class> provides to build up your resultset in reusable stages,
+so first you add some data:
+
+  my $new_rs = $rs->search({}, { prefetch => [qw/artist/] });
+
+And then restrict it a bit:
+
+  my $newer_rs = $new_rs->search({ price => { '>' => 6 } });
+
+And then use C<DBIx::Class::ResultClass::HashRefInflator> to dump the
+resultset to an array of hashrefs and put that in your stash. The data
+could look something like this:
+
+  $newer_rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
+  my @tunes = $newer_rs->all;
+
+  # [{
+  #    'id' => 1,
+  #    'name' => 'Catchy tune',
+  #    'price' => '7',
+  #    'artist' => {
+  #        'name' => 'Some dude'
+  #    }
+  #  },
+  #  {
+  #    'id' => 2,
+  #    'name' => 'Not so catchy tune',
+  #    'price' => '7.5'
+  #    'artist' => {
+  #        'name' => 'Some other dude'
+  #    }
+  #  }]
+
+  $c->stash->{tunes} = \@tunes;
+
+And then your view can just iterate through the data structure without
+having to deal with row objects or pull in more data from the
+database. And that's really great. It's much better to pass an already
+formatted data structure to your view then it is to pass a resultset to
+your view, because application will be much more maintainable if you're
+doing all the data processing in Perl.
+
+This approach of building up the resultset in stages is great if you're
+just prefetching, or otherwise adding data that's in the database, but
+what if you need to add some stuff to the data structure that isn't in
+the database? Probably you'll end up doing something like this:
+
+  $rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
+  my @tunes = $rs->all;
+
+  foreach my $tune (@tunes) {
+    $tune->{score} = $score_map{ $tune->{id} };
+    # and so on, adding more stuff to the row's hashref
+  }
+
+Typically you'll do that in the controller, which is bad, because you
+should be keeping your logic in the model. When you realise this is bad
+you'll move it to the model; maybe you'll make a resultset method and
+call it from your controller like this:
+
+  my $formatted_tunes = $rs->get_formatted_arrayref;
+  $c->stash->{tunes} = $formatted_tunes;
+
+Which is sort of better, until you realise that in another action you
+need to reuse this method from somewhere else in your application, but
+this time you need more stuff, so maybe you extend your method like
+this:
+
+  my $formatted_tunes_with_extra_stuff = 
+    $rs->get_formatted_arrayref( with_extra_stuff => 1 );
+  $c->stash->{tunes} = $formatted_tunes_with_extra_stuff;
+
+And then in another action you realise you need the same thing, but with
+a different scoring mechanism:
+
+  my $formatted_tunes_with_extra_stuff_and_a different_scoring_mechanism =
+    $rs->get_formatted_arrayref( with_extra_stuff => 1, scoring => 'different' );
+
+And soon your C<get_formatted_arrayref> method is unmaintainable. I
+thought that it would be cool if I could just add this extra stuff by
+chaining resultsets together:
+
+  my $formatted_tunes = $rs->with_score->display;
+
+  my $formatted_tunes_with_extra_stuff =
+     $rs->with_score->with_extra_stuff->display;
+
+  my $formatted_tunes_with_extra_stuff_and_a different_scoring_mechanism =
+     $rs->with_score( mechanism => 'different' )->with_extra_stuff->display;
+
+And until you call display on it, it's still just a resultset, with the
+usual resultset methods:
+
+  my $formatted_tunes = $rs->with_score
+                           ->with_extra_stuff
+                            ->search({}, { prefetch => 'artist' })
+                            ->display;
+
+C<DBIx::Class::ResultSet::WithMetaData> allows you to do this - you can
+attach extra metadata to your resultset without first flattening it to
+a data structure, which will allow you to separate your formatting out to
+separate methods in a relatively clean way that promotes reuse.
+
+=head2 Whoa there, how do I add my own resultset methods?
+
+You need to use a custom resultset, which is just a subclass of the
+usual C<DBIx::Class::ResultSet>. There are two ways to add custom
+resultsets. Preferably, you'll use load_namespaces in your
+C<DBIx::Class::Schema class>, like this:
+
+  package MyApp::Schema;
+
+  ...
+
+  __PACKAGE__->load_namespaces(
+      result_namespace => 'Result',
+      resultset_namespace => 'ResultSet',
+  );
+
+In which case your custom resultsets will be automatically picked up from
+MyApp::Schema::ResultSet::*. 
+
+If you're not using load_namespaces, you can still make it work. Have a
+look at this:
+http://search.cpan.org/~frew/DBIx-Class-0.08114/lib/DBIx/Class/ResultSource.pm#resultset_class
+
+And a super simple resultset class might look like this:
+
+  package MyApp::Schema::ResultSet::Tune;
+
+  use strict;
+  use warnings;
+
+  use DBIx::Class::ResultClass::HashRefInflator;
+  use Moose;
+  extends 'DBIx::Class::ResultSet';
+
+  sub display {
+    my ($self) = @_;
+
+    $rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
+    my @return = $rs->all;
+    return \@return;
+  }
+
+  1;
+
+Which provides a display method on your Tune resultsets, like so:
+
+  my $displayed = $schema->resultset('Tune')->display;
+
+=head2 So down to business, what does
+DBIx::Class::ResultSet::WithMetaData actually provide?
+
+The key method is called C<add_row_info>, which allows you to attach the
+extra metadata to the rows. For example, in the first section we were
+adding a score to each row. You could do that like this:
+
+  package MyApp::Schema::ResultSet::Tune;
+
+  ...
+
+  extends 'DBIx::Class::ResultSet::WithMetaData';
+
+  sub with_score {
+    my ($self, %params) = @_;
+
+    foreach my $row ($self->all) {
+      my $score = $self->score_map->{ $row->id };
+      $self->add_row_info(id => $row->id, info => { score => $score });
+    }
+  }
+
+  ...
+
+The only other method you need to worry about is the display method,
+which flattens the resultset to a data structure, much like using
+C<DBIx::Class::ResultClass::HashRefInflator>. But it also merges the
+extra info attached using add_row_info. For example
+
+  my @tunes = $tune_rs->with_score->display;
+
+  # [{
+  #    'id' => 1,
+  #    'name' => 'Catchy tune',
+  #    'price' => '7',
+  #    'score' => '1.7',
+  #  },
+  #  {
+  #    'id' => 2,
+  #    'name' => 'Not so catchy tune',
+  #    'price' => '7.5'
+  #    'score' => '1.2'
+  #  }]
+
+=head2 So how does this keep my code clean again?
+
+These are simple examples. If you have a fairly complex application
+(like just about any real production application) then you might be
+building up complex chains to format your data ready for the
+view. You'll be add adding all sorts of extra stuff. Look at this
+example I've taken from one of my apps:
+
+  $c->stash->{products} = $rs->with_images
+                             ->with_primary_image
+                             ->with_seller
+                             ->with_primary_category
+                             ->with_related_products
+                             ->with_options
+                             ->with_token
+                             ->display;
+
+In another part of the application I need the products but with less
+info, but I can just easily reuse what I already have:
+
+  $c->stash->{products} = $rs->with_images
+                             ->with_primary_image
+                             ->with_seller
+                             ->with_token
+                             ->display;
+
+This might look complicated, but it's quite elegant when compared with
+writing one huge method which accepts a ton of flags determining whether
+or not to include different bits of info to the data structure, or
+writing separate methods for each use case, or doing it in the templates,
+or doing it in the controller.
+
+To summarize:
+
+=over 4
+
+=item *
+
+you're not doing your data pre-processing in the view where it doesn't
+belong
+
+=item *
+
+you're not doing it in the controller where it doesn't belong either
+
+=item *
+
+you've split up your formatting into reusable methods inside the model
+
+=item *
+
+you'll sleep well at night and your colleagues won't hate you do much
+
+=back
+
+=head2 It's not very efficient though is it?
+
+It's not really, no. You'll find that you're looping through the
+resultset repeatedly in order to format it, and if you're working with
+large resultsets, this might not be for you. But my general attitude is
+that you should make the code work in a maintainable and clean way, and
+then optimize for performance. Don't start coding yourself into a mess
+before you know the clean alternatives are slow.
+
+=head2 And it's fairly experimental.
+
+Although used in production on a couple of my applications, it's still
+newish and kind of experimental. But it's simple enough so improvements
+and optimizations shouldn't hard to make, and if you want to help
+improve things, I'm liberal with commit bits and co-maint rights.
+
+=head1 AUTHOR
+
+Luke Saunders <luke.saunders at gmail.com>
+
+=cut

Deleted: trunk/examples/CatalystAdvent/root/2009/pen/withmetadata.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2009/pen/withmetadata.pod	2009-12-11 23:28:09 UTC (rev 12317)
+++ trunk/examples/CatalystAdvent/root/2009/pen/withmetadata.pod	2009-12-12 00:04:36 UTC (rev 12318)
@@ -1,285 +0,0 @@
-=head1 How DBIx::Class::ResultSet::WithMetaData can help keep your controllers clean
-
-=head2 A little note on code cleanliness
-
-When you started using Catalyst with C<DBIx::Class> I'm betting that you
-generated a resultset in your controller, then passed it to your view
-(TT for example), then iterated through it. In short you ended up doing
-loads of really complicated stuff in your TT templates. As you probably
-learned, this is really bad, because you eventually end up with
-complicated and messy templates that are hard to maintain. You should be
-doing your data preparation in Perl, then passing some nicely formatted
-data structure to your view to happily render with a minimum of logic.
-
-A nice approach is to harness the resultset chaining magic that
-C<DBIx::Class> provides to build up your resultset in reusable stages,
-so first you add some data:
-
-  my $new_rs = $rs->search({}, { prefetch => [qw/artist/] });
-
-And then restrict it a bit:
-
-  my $newer_rs = $new_rs->search({ price => { '>' => 6 } });
-
-And then use C<DBIx::Class::ResultClass::HashRefInflator> to dump the
-resultset to an array of hashrefs and put that in your stash. The data
-could look something like this:
-
-  $newer_rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
-  my @tunes = $newer_rs->all;
-
-  # [{
-  #    'id' => 1,
-  #    'name' => 'Catchy tune',
-  #    'price' => '7',
-  #    'artist' => {
-  #        'name' => 'Some dude'
-  #    }
-  #  },
-  #  {
-  #    'id' => 2,
-  #    'name' => 'Not so catchy tune',
-  #    'price' => '7.5'
-  #    'artist' => {
-  #        'name' => 'Some other dude'
-  #    }
-  #  }]
-
-  $c->stash->{tunes} = \@tunes;
-
-And then your view can just iterate through the data structure without
-having to deal with row objects or pull in more data from the
-database. And that's really great. It's much better to pass an already
-formatted data structure to your view then it is to pass a resultset to
-your view, because application will be much more maintainable if you're
-doing all the data processing in Perl.
-
-This approach of building up the resultset in stages is great if you're
-just prefetching, or otherwise adding data that's in the database, but
-what if you need to add some stuff to the data structure that isn't in
-the database? Probably you'll end up doing something like this:
-
-  $rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
-  my @tunes = $rs->all;
-
-  foreach my $tune (@tunes) {
-    $tune->{score} = $score_map{ $tune->{id} };
-    # and so on, adding more stuff to the row's hashref
-  }
-
-Typically you'll do that in the controller, which is bad, because you
-should be keeping your logic in the model. When you realise this is bad
-you'll move it to the model; maybe you'll make a resultset method and
-call it from your controller like this:
-
-  my $formatted_tunes = $rs->get_formatted_arrayref;
-  $c->stash->{tunes} = $formatted_tunes;
-
-Which is sort of better, until you realise that in another action you
-need to reuse this method from somewhere else in your application, but
-this time you need more stuff, so maybe you extend your method like
-this:
-
-  my $formatted_tunes_with_extra_stuff = 
-    $rs->get_formatted_arrayref( with_extra_stuff => 1 );
-  $c->stash->{tunes} = $formatted_tunes_with_extra_stuff;
-
-And then in another action you realise you need the same thing, but with
-a different scoring mechanism:
-
-  my $formatted_tunes_with_extra_stuff_and_a different_scoring_mechanism =
-    $rs->get_formatted_arrayref( with_extra_stuff => 1, scoring => 'different' );
-
-And soon your C<get_formatted_arrayref> method is unmaintainable. I
-thought that it would be cool if I could just add this extra stuff by
-chaining resultsets together:
-
-  my $formatted_tunes = $rs->with_score->display;
-
-  my $formatted_tunes_with_extra_stuff =
-     $rs->with_score->with_extra_stuff->display;
-
-  my $formatted_tunes_with_extra_stuff_and_a different_scoring_mechanism =
-     $rs->with_score( mechanism => 'different' )->with_extra_stuff->display;
-
-And until you call display on it, it's still just a resultset, with the
-usual resultset methods:
-
-  my $formatted_tunes = $rs->with_score
-                           ->with_extra_stuff
-                            ->search({}, { prefetch => 'artist' })
-                            ->display;
-
-C<DBIx::Class::ResultSet::WithMetaData> allows you to do this - you can
-attach extra metadata to your resultset without first flattening it to
-a data structure, which will allow you to separate your formatting out to
-separate methods in a relatively clean way that promotes reuse.
-
-=head2 Whoa there, how do I add my own resultset methods?
-
-You need to use a custom resultset, which is just a subclass of the
-usual C<DBIx::Class::ResultSet>. There are two ways to add custom
-resultsets. Preferably, you'll use load_namespaces in your
-C<DBIx::Class::Schema class>, like this:
-
-  package MyApp::Schema;
-
-  ...
-
-  __PACKAGE__->load_namespaces(
-      result_namespace => 'Result',
-      resultset_namespace => 'ResultSet',
-  );
-
-In which case your custom resultsets will be automatically picked up from
-MyApp::Schema::ResultSet::*. 
-
-If you're not using load_namespaces, you can still make it work. Have a
-look at this:
-http://search.cpan.org/~frew/DBIx-Class-0.08114/lib/DBIx/Class/ResultSource.pm#resultset_class
-
-And a super simple resultset class might look like this:
-
-  package MyApp::Schema::ResultSet::Tune;
-
-  use strict;
-  use warnings;
-
-  use DBIx::Class::ResultClass::HashRefInflator;
-  use Moose;
-  extends 'DBIx::Class::ResultSet';
-
-  sub display {
-    my ($self) = @_;
-
-    $rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
-    my @return = $rs->all;
-    return \@return;
-  }
-
-  1;
-
-Which provides a display method on your Tune resultsets, like so:
-
-  my $displayed = $schema->resultset('Tune')->display;
-
-=head2 So down to business, what does
-DBIx::Class::ResultSet::WithMetaData actually provide?
-
-The key method is called C<add_row_info>, which allows you to attach the
-extra metadata to the rows. For example, in the first section we were
-adding a score to each row. You could do that like this:
-
-  package MyApp::Schema::ResultSet::Tune;
-
-  ...
-
-  extends 'DBIx::Class::ResultSet::WithMetaData';
-
-  sub with_score {
-    my ($self, %params) = @_;
-
-    foreach my $row ($self->all) {
-      my $score = $self->score_map->{ $row->id };
-      $self->add_row_info(id => $row->id, info => { score => $score });
-    }
-  }
-
-  ...
-
-The only other method you need to worry about is the display method,
-which flattens the resultset to a data structure, much like using
-C<DBIx::Class::ResultClass::HashRefInflator>. But it also merges the
-extra info attached using add_row_info. For example
-
-  my @tunes = $tune_rs->with_score->display;
-
-  # [{
-  #    'id' => 1,
-  #    'name' => 'Catchy tune',
-  #    'price' => '7',
-  #    'score' => '1.7',
-  #  },
-  #  {
-  #    'id' => 2,
-  #    'name' => 'Not so catchy tune',
-  #    'price' => '7.5'
-  #    'score' => '1.2'
-  #  }]
-
-=head2 So how does this keep my code clean again?
-
-These are simple examples. If you have a fairly complex application
-(like just about any real production application) then you might be
-building up complex chains to format your data ready for the
-view. You'll be add adding all sorts of extra stuff. Look at this
-example I've taken from one of my apps:
-
-  $c->stash->{products} = $rs->with_images
-                             ->with_primary_image
-                             ->with_seller
-                             ->with_primary_category
-                             ->with_related_products
-                             ->with_options
-                             ->with_token
-                             ->display;
-
-In another part of the application I need the products but with less
-info, but I can just easily reuse what I already have:
-
-  $c->stash->{products} = $rs->with_images
-                             ->with_primary_image
-                             ->with_seller
-                             ->with_token
-                             ->display;
-
-This might look complicated, but it's quite elegant when compared with
-writing one huge method which accepts a ton of flags determining whether
-or not to include different bits of info to the data structure, or
-writing separate methods for each use case, or doing it in the templates,
-or doing it in the controller.
-
-To summarize:
-
-=over 4
-
-=item *
-
-you're not doing your data pre-processing in the view where it doesn't
-belong
-
-=item *
-
-you're not doing it in the controller where it doesn't belong either
-
-=item *
-
-you've split up your formatting into reusable methods inside the model
-
-=item *
-
-you'll sleep well at night and your colleagues won't hate you do much
-
-=back
-
-=head2 It's not very efficient though is it?
-
-It's not really, no. You'll find that you're looping through the
-resultset repeatedly in order to format it, and if you're working with
-large resultsets, this might not be for you. But my general attitude is
-that you should make the code work in a maintainable and clean way, and
-then optimize for performance. Don't start coding yourself into a mess
-before you know the clean alternatives are slow.
-
-=head2 And it's fairly experimental.
-
-Although used in production on a couple of my applications, it's still
-newish and kind of experimental. But it's simple enough so improvements
-and optimizations shouldn't hard to make, and if you want to help
-improve things, I'm liberal with commit bits and co-maint rights.
-
-=head1 AUTHOR
-
-Luke Saunders <luke.saunders at gmail.com>
-
-=cut




More information about the Catalyst-commits mailing list