[Catalyst-commits] r14418 - in trunk/examples/CatalystAdvent/root: 2012 static/2012

jnapiorkowski at dev.catalyst.perl.org jnapiorkowski at dev.catalyst.perl.org
Mon Dec 10 13:44:14 GMT 2012


Author: jnapiorkowski
Date: 2012-12-10 13:44:14 +0000 (Mon, 10 Dec 2012)
New Revision: 14418

Added:
   trunk/examples/CatalystAdvent/root/2012/18.pod
   trunk/examples/CatalystAdvent/root/2012/19.pod
   trunk/examples/CatalystAdvent/root/2012/20.pod
   trunk/examples/CatalystAdvent/root/static/2012/graph_example.jpg
Log:
three more articles

Added: trunk/examples/CatalystAdvent/root/2012/18.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2012/18.pod	                        (rev 0)
+++ trunk/examples/CatalystAdvent/root/2012/18.pod	2012-12-10 13:44:14 UTC (rev 14418)
@@ -0,0 +1,168 @@
+=head1 BAKING A SIMPLE MODEL FROM AN EXISTING CLASS
+
+=head1 OVERVIEW
+
+Let's assume you already have production-tested code for implementing a given
+task. You like to get this code included into a Catalyst application. This is
+where
+L<Catalyst::Model::Adaptor|https://metacpan.org/release/Catalyst-Model-Adaptor>
+comes into play. It offers the glue needed to make such an integration easy
+and allows reuse of existing code.
+
+For demonstration purposes we are building our model based on a simple
+job queue class.
+
+=head1 Preparing our job queue
+
+First, we look into our job queue class. Admittedly, this one is very simple
+but offers the basic things a job queue should do: appending and retrieving
+jobs. For sake of simplicity we do not consider concurrency problems and
+omit error handling here.
+
+    package JobQueue;
+    use Moose;
+    use MooseX::Types::Path::Class;
+    use YAML qw(DumpFile LoadFile);
+    
+    has dir => (
+        is       => 'ro',
+        isa      => 'Path::Class::Dir',
+        required => 1,
+        coerce   => 1,
+    );
+    
+    sub append_job {
+        my ($self, $data) = @_;
+        
+        my ($fh, $filename) = $self->dir->tempfile(time . '-XXXXX');
+        DumpFile $filename, $data;
+    }
+    
+    sub next_job {
+        my $self = shift;
+        
+        my ($job_file) = $self->dir->children
+            or return;
+        
+        my $data = LoadFile($job_file);
+        $job_file->remove;
+        
+        return $data;
+    }
+    
+    1;
+
+The package above is completely independent of Catalyst and can get used
+everywhere. It acts as a placeholder for a class you might like to use.
+
+=head1 Creating a simple application
+
+    $ catalyst.pl ModelDemo
+    $ cd ModelDemo
+
+=head1 Integration
+
+Create a model class:
+
+    package ModelDemo::Model::JobQ;
+    use Moose;
+    extends 'Catalyst::Model::Adaptor';
+    
+    # nothing more needed. Everything is inside JobQueue.pm :-)
+    
+    1;
+
+and add the following things to your config section of F<ModelDemo.pm>. Please
+note that you have to specify the package name and every required attribute
+for instantiation of an object. Otherwise the instantiation will throw an
+exception.
+
+    'Model::JobQ' => {
+        class => 'JobQueue',
+        args  => {
+            dir => '/tmp',
+        },
+    },
+
+Inside your Catalyst application you can use your model just as you are used
+to handle other models. The model instantiates an object of the destination
+package and returns it. Thus, everything being possible with the
+original object is possible with your "model".
+
+    $c->model('JobQ')->append_job( { whatever => 'foo' } );
+
+There is still one thing we could improve: getting rid of the hard coded path
+to the job directory. Think about having a dev-, a staging- and a production
+website which might reside at different locations. And consider the different
+locations of the application at the various developers working with you.
+Setting a different path every time is boring and error-prone.
+
+If you decide to use the
+L<ConfigLoader|https://metacpan.org/release/Catalyst-Plugin-ConfigLoader>
+plugin, you may create a configuration file (here F<modeldemo.pl> in the root
+directory of your app). The location of the config file is customizable via
+environment variables if you like to change the location.
+
+One of the great features of ConfigLoader is that it substitutes some
+predefined patterns wrapped in double underscore characters before
+interpreting the config file's content. This allows to specify e.g. paths
+relative to the application's main directory. Please note that you will
+have to create the directories yourself.
+
+    # config file "modeldemo.pl"
+    {
+        name => 'ModelDemo',
+        
+        'Model::JobQ' => {
+            class => 'JobQueue',
+            args  => {
+                dir => '__path_to(root/jobs)__',
+            },
+        },
+    }
+
+=head1 For More Information
+
+L<Catalyst::Model::Adaptor|https://metacpan.org/release/Catalyst-Model-Adaptor>
+offers three different scopes for the objects getting instantiated.
+
+=over
+
+=item Catalyst::Model::Adaptor
+
+instantiates an object at application launch time. The object persists the
+runtime of the application.
+
+=item Catalyst::Model::Factory
+
+instantiates an object every time you request the object.
+
+=item Catalyst::Model::Factory::PerRequest
+
+instantiates an object once per request.
+
+=back
+
+Two earlier Catalyst advent calendars had similar recipes:
+
+=over
+
+=item
+
+L<2007-24|http://www.catalystframework.org/calendar/2007/24>
+
+=item 
+
+L<2009-18|http://www.catalystframework.org/calendar/2009/18>
+
+=back
+
+=head1 Summary
+
+Existing mature classes may easily integrated into an existing Catalyst
+Application with the aid of
+L<Catalyst::Model::Adaptor|https://metacpan.org/release/Catalyst-Model-Adaptor>.
+
+=head1 Author
+
+Wolfgang Kinkeldei E<lt>wolfgang [at] kinkeldei [dot] deE<gt>

Added: trunk/examples/CatalystAdvent/root/2012/19.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2012/19.pod	                        (rev 0)
+++ trunk/examples/CatalystAdvent/root/2012/19.pod	2012-12-10 13:44:14 UTC (rev 14418)
@@ -0,0 +1,152 @@
+=head1 DRAWING GRAPH CHARTS
+
+=head1 OVERVIEW
+
+Statistical data is best viewed using graph images. A possible way to integrate
+graphs into a Catalyst Application could result in creating a custom View
+for drawing the graphs requested. Today we will examine how such a task
+could look like.
+
+=head1 Preparations
+
+First we have to find and get warm with graph drawing libraries. There are
+plenty of them we can choose from on CPAN. My choice was
+L<Imager::Graph|https://metacpan.org/release/Imager-Graph>. This module
+offers the typical kinds of graphs (Area, Bar, Column, Horizontal, Line,
+Pie and StackedColumn) and allows customization of almost every parameter.
+
+C<Imager::Graph> depends on L<Imager|https://metacpan.org/release/Imager>, so
+the same prerequisites apply here. Please see 
+### FIXME: Link must be adjusted! ###
+L<Scaling images on demand|http://www.catalystframework.org/calendar/2012/99>
+for a list of C-libraries required for installing C<Imager::Graph>.
+
+=head1 How to talk to the view
+
+Inside any Controller we like to prepare some data which is forwarded to
+the view where the actual drawing occurs. A typical use case inside your
+Controller might look like this:
+
+    sub some_method :Local {
+        my ($self, $c) = @_;
+        
+        # ...get data somehow
+        
+        $c->stash(
+            title  => 'Title for your artwork',
+            width  => 600,
+            height => 400,
+            data   => [42, 30, 33, 32, 38, 44, 39],
+            y_max  => 50,
+            y_min  => 0,
+            labels => [qw(Mon Tue Wed Thu Fri Sat Sun)],
+        );
+        
+        $c->forward('View::Graph');
+    }
+
+=head1 Implementing the View
+
+Add a view F<View/Graph.pm> to your Catalyst Application and fill it with
+the code below. The tick arithmetic admittedly looks a bit awkward but
+it works well with negative and positive values and draws tick lines at
+multiples of a power of 10 depending on the minimum and maximum values.
+
+    package YourApp::View::Graph;
+    use Moose;
+    use Imager::Graph::Column;
+    use namespace::autoclean;
+    
+    extends 'Catalyst::View';
+    
+    sub process {
+        my ( $self, $c ) = @_;
+    
+        # Please check this path!
+        my $font = Imager::Font->new(file => '/Library/Fonts/Arial.ttf')
+            or die "Error: $!";
+    
+        my $graph = Imager::Graph::Column->new();
+    
+        my $y_max = $c->stash->{y_max} // 20;
+        my $y_min = $c->stash->{y_min} // 0;
+    
+        my $delta = 1;
+        my $ticks = 9999;
+        my $count = 0;
+        while ($ticks > 10 && $count < 100) {
+            $y_max = int(($y_max + (9 * $delta)) / (10 * $delta)) * (10 * $delta);
+            $y_max = 10 if $y_max < 10;
+    
+            $y_min = int(($y_min - (9 * $delta)) / (10 * $delta)) * (10 * $delta);
+            $y_min = 0 if $y_min > 0;
+    
+            $ticks = int(($y_max - $y_min) / (5 * $delta)) + 1;
+            if ($ticks > 10) {
+                $ticks = int(($y_max - $y_min) / (10 * $delta)) + 1
+            }
+    
+            if ($ticks > 20) {
+                $delta *= 10;
+            }
+    
+            $count++;
+        }
+    
+        $graph->add_data_series($c->stash->{data});
+    
+        $graph->set_style('fount_lin');
+        $graph->show_horizontal_gridlines();
+        $graph->use_automatic_axis();
+        $graph->set_y_max($y_max);
+        $graph->set_y_min($y_min);
+        $graph->set_y_tics($ticks);
+        $graph->set_image_width($c->stash->{width}  // 600);
+        $graph->set_image_height($c->stash->{height}  // 400);
+    
+        my $img = $graph->draw(
+            column_padding => 20,
+            labels         => $c->stash->{labels},
+            title          => $c->stash->{title} // 'Untitled',
+            font           => $font,
+            hgrid          => { style => "dashed", color => "#888" },
+            graph          => { outline => { color => "#F00", style => "dotted" }, },
+            fills          => [ qw(60ff60 a0a0ff) ],
+        ) or die $graph->error;
+    
+        my $data;
+        $img->write( data => \$data, type => 'jpeg' )
+            or die "could not write image: $!";
+    
+        $c->response->body($data);
+        $c->response->content_type('image/jpeg');
+    }
+    
+    __PACKAGE__->meta->make_immutable;
+    
+    1;
+
+=begin xhtml
+
+<img src="/calendar/static/2012/graph_example.jpg" />
+
+=end xhtml
+
+The rendering time typically is not an issue. On my machine typical graphs
+are rendered in less than 100ms.
+
+=head1 For More Information
+
+See L<Imager::Graph|https://metacpan.org/release/Imager-Graph> for the full
+details.
+
+=head1 Summary
+
+Rendering graphs inside a View is not a very complicated task and can get
+triggered with just a few values from a Controller.
+
+=head1 Author
+
+Wolfgang Kinkeldei E<lt>wolfgang [at] kinkeldei [dot] deE<gt>
+
+=cut

Added: trunk/examples/CatalystAdvent/root/2012/20.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2012/20.pod	                        (rev 0)
+++ trunk/examples/CatalystAdvent/root/2012/20.pod	2012-12-10 13:44:14 UTC (rev 14418)
@@ -0,0 +1,251 @@
+=head1 SCALING IMAGES ON DEMAND
+
+=head1 OVERVIEW
+
+If a website gets its material from various sources or must display
+variable-sized items in a uniform way you will need some kind of image scaler.
+Sending the high resolution images to the browser is not wise and consumes too
+much bandwidth. Scaling images in advance to every required size could be a
+way that works if you have enough resources for this preparation.
+
+Assuming your website does not have too much traffic, you might think about
+scaling images on-demand and keeping the scaled version for future requests.
+This makes you flexible, saves bandwidth on transmission and only consumes
+valuable CPU cycles on initial requests for a given image and size.
+
+When I first came across a request for scaling images, I could not find a
+library that suited my needs. So I decided to roll my own. I called it
+L<Catalyst::Controller::Imager|https://metacpan.org/module/Catalyst::Controller::Imager>
+as it internally uses L<Imager|https://metacpan.org/module/Imager> as its engine.
+
+Today's article will show some tricks you might do with this little module.
+
+=head1 Prepare your system
+
+Imager uses several libraries written in C for reading, manipulating and
+writing images. Depending on the formats you like to support more or less
+libraries are needed. Imager is very flexible in its configuration. Simply
+run F<Makefile.PL> by hand after downloading Imager and you will get an idea
+of what I am talking about...
+
+For a typical web scenario you will probably need to install C<libgif>,
+C<libpng>, C<libjpeg> and C<libtif>. The packages for Ubuntu Linux are named
+C<libgif4>, C<libpng12-0>, C<libjpeg8> and C<libtiff4>. For compiling Imager
+you will have to install the "-dev" versions of these libs as well.
+
+For Mac OS-X I recommend using L<MacPorts|http://www.macports.org> and
+installing the packages C<jpeg>, C<giflib at 4.2.1+no_x11>, C<tiff> and C<libpng>.
+The command you will need to make a friend is named F<port>.
+
+After that, C<Imager> and C<Catalyst::Controller::Imager> should install
+without any complains.
+
+=head1 Create a simple app
+
+    $ catalyst.pl ImageDemo
+    $ cd ImageDemo
+    $ ./script/imagedemo_create.pl controller Image Imager
+    $ mkdir root/cache
+    $ ./script/imagedemo_server.pl -d -r -f
+
+And for testing, please put some images of any size into F<root/static/images>
+
+=head1 Explore the default options
+
+Assumed you have an original image located in F<root/static/images/house.jpg>
+you can try the following URLs:
+
+=over
+
+=item the original image:
+
+http://localhost:3000/static/images/house.jpg
+
+=item a thumbnail (80x80)
+
+http://localhost:3000/image/thumbnail/house.jpg
+
+=item width 100 pixels
+
+http://localhost:3000/image/w-100/house.jpg
+
+=item height 100 pixels
+
+http://localhost:3000/image/h-100/house.jpg
+
+=item height 10000 pixels
+
+http://localhost:3000/image/h-10000/house.jpg
+
+oops. we get an error. See Controller::Image in your app to find out why
+
+=item width 200 pixels and height 100 pixels
+
+http://localhost:3000/image/w-200-h-100/house.jpg
+
+=back
+
+=head1 Extending the logic behind the scenes
+
+Until now, you did not touch the auto-generated Controller::Imager in your
+application. Every URL you called could get serviced from the logic of the
+base class.
+
+The whole logic inside the base class operates in these stages:
+
+=over
+
+=item collecting
+
+The URL is split into parts. The first part after the namespace contains a
+dash-separated list of keywords with optional parameters. Several keywords
+with their parameters may exist and are again separated by a single dash.
+
+For every keyword a method named like the keyword prefixed by C<want_> must
+exist.
+
+During URL traversal, all C<want_xxx> methods are called. Each of these
+methods may add things to stash variables, typically C<scale>.
+
+=item before_scale
+
+if the array-ref C<$c->stash->{before_scale}> contains action names, a
+forward to every of these actions is performed.
+
+=item scaling
+
+The C<scale> stash variable contains a hash-ref with options for scaling, like
+C<w>, C<h> and C<mode>. Depending on the mode, a method named C<scale_xxx> is
+called to execute the desired scaling. The default is to call C<scale_min>.
+In this case the minimum scaling factor is chosen which will ensure that the
+resulting image is not bigger than requested.
+
+=item after_scale
+
+if the array-ref C<$c->stash->{after_scale}> contains action names, a
+forward to every of these actions is performed.
+
+=item delivery
+
+the image is converted to the desired format (based on the extension of the
+URL) and delivered to the client.
+
+=back
+
+If you like to support an URI like C</image/small/house.jpg>, simply implement a
+method named C<want_small> with an ":Args(0)" attribute that puts the required
+scaling options into a stash variable.
+
+    sub want_small :Action :Args(0) {
+        my ($self, $c) = @_;
+        
+        # set the desired scaling.
+        # fill means scaling by minimum factor and filling the gaps in order
+        # to get an image of the desired size
+        $c->stash(scale => {w => 100, h => 80, mode => 'fill'});
+    }
+
+If you change the C<mode> to 'fit', 'min', 'max' and 'fill' you will quickly
+see the difference between the various modes. If you need another mode,
+you may create one.
+
+
+For getting a URI C</image/foo-42/house.jpg>, you might guess that a method
+named C<want_foo> with ":Args(1)" is required. Simple. Here we also define
+and implement a scaling method of our own which also blurs the image. Actually
+using the C<after_scale> hook would be more wise but as the example below
+should demonstrate how to create a scaler action.
+
+    sub want_foo :Action :Args(1) {
+        my ($self, $c, $mode) = @_;
+        
+        $c->stash(scale => {w => $mode * 4, h => $mode * 3, mode => 'blur'});
+    }
+
+    sub scale_blur :Action {
+        my ($self, $c) = @_;
+        
+        my $scale = $c->stash->{scale};
+        my %scale_options = (
+            ($scale->{w} ? (xpixels => $scale->{w}) : ()),
+            ($scale->{h} ? (ypixels => $scale->{h}) : ())
+        );
+        
+        my $scaled_image = keys %scale_options
+            ? $c->stash->{image}->scale(%scale_options, type => 'min')
+            : $c->stash->{image};
+        
+        my $scaled_and_blurred_image = $scaled_image->filter(
+            type   => 'gaussian',
+            stddev => 5,
+        );
+        
+        $c->stash->{image} = $scaled_and_blurred_image;
+    }
+
+If you created a F<root/cache> directory you may enable the caching option 
+in the config section. After that you will see that it then contains
+all images you requested afterwards.
+
+Simply change the cache_dir entry in the config section
+
+    __PACKAGE__->config(
+        # the directory to look for files (inside root)
+        # defaults to 'static/images'
+        #root_dir => 'static/images',
+
+        # specify a cache dir if caching is wanted
+        # defaults to no caching (more expensive)
+        cache_dir => 'root/cache',
+        
+        # ... nothing to change below
+
+As soon as you enabled the caching, you will immediately feel that reloading
+the same image becomes much faster. However, changing scaling options will
+need to clear the cache.
+
+You could raise the performance further if you add a redirect rule into your
+frontend Webserver's configuration that delivers a cached image when available
+and only falls back to your Catalyst Application if no cached image is present.
+
+=head1 See also
+
+L<Catalyst::View::GD::Thumbnail|https://metacpan.org/module/Catalyst::View::GD::Thumbnail>
+
+L<Catalyst::View::Thumbnail|https://metacpan.org/module/Catalyst::View::Thumbnail>
+
+L<Catalyst::View::Thumbnail::Simple|https://metacpan.org/module/Catalyst::View::Thumbnail::Simple>
+
+L<Catalyst::Plugin::Upload::Image::Magick::Thumbnail|https://metacpan.org/module/Catalyst::Plugin::Upload::Image::Magick::Thumbnail>
+
+the fastest scaler I have ever seen for JPEG images:
+
+L<Image::Epeg|https://metacpan.org/module/Image::Epeg>
+
+These earlier Catalyst advent calendars had similar recipes:
+
+=over
+
+=item Gearman
+
+L<2010-07|http://www.catalystframework.org/calendar/2010/7>
+
+=item Imager
+
+L<2008-05|http://www.catalystframework.org/calendar/2008/5>
+
+=back
+
+=head1 Summary
+
+With this simple module, image manipulations can get encapsulated into a
+single controller. No additional view is needed, the controller also delivers
+the scaled image data to the browser. By extending the logic that drives
+the scaling process you can get nice URLs with more or less complicated
+logic behind the scenes. Caching allows fast redelivery of already requested
+images. By leveraging the powerful operations Imager offers, you can do a lot
+of cool things.
+
+=head1 Author
+
+Wolfgang Kinkeldei E<lt>wolfgang [at] kinkeldei [dot] deE<gt>

Added: trunk/examples/CatalystAdvent/root/static/2012/graph_example.jpg
===================================================================
(Binary files differ)


Property changes on: trunk/examples/CatalystAdvent/root/static/2012/graph_example.jpg
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream




More information about the Catalyst-commits mailing list