[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