[Catalyst-commits] r7279 - trunk/examples/CatalystAdvent/root/2007/pen

peterdragon at dev.catalyst.perl.org peterdragon at dev.catalyst.perl.org
Wed Dec 12 01:57:11 GMT 2007


Author: peterdragon
Date: 2007-12-12 01:57:11 +0000 (Wed, 12 Dec 2007)
New Revision: 7279

Modified:
   trunk/examples/CatalystAdvent/root/2007/pen/14.pod
Log:
First draft of Catalyst advent calendar day 14 "Using Catalyst Models Externally and Multiple Configuration Files"



Modified: trunk/examples/CatalystAdvent/root/2007/pen/14.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/pen/14.pod	2007-12-12 01:35:57 UTC (rev 7278)
+++ trunk/examples/CatalystAdvent/root/2007/pen/14.pod	2007-12-12 01:57:11 UTC (rev 7279)
@@ -1,9 +1,329 @@
-=head1 Using Multiple Configuration Files and Accessing A Catalyst Model From An External Batch Script
+=head1 Day 14 - Using Catalyst Models Externally and Multiple Configuration Files
 
-Catalyst::Plugin::ConfigLoader::Multi
+Today we look at how to solve a common problem: how to access a Catalyst 
+model from a batch script outside Catalyst while sharing some configuration
+settings such as the database connection string.
 
-Accessing a Catalyst model from an external batch script.
 
+=head2 Sample Application
+
+This article uses an enhanced version of the ExtJS sample application
+written for the Catalyst Advent Calendar 2007 day 1 article
+and you will need to read the code along with this article.
+It's visible at
+L<http://dev.catalystframework.org/repos/Catalyst/trunk/examples/ExtJS>.
+
+You can check out the code under Linux with
+
+ $ svn co http://dev.catalystframework.org/repos/Catalyst/trunk/examples/ExtJS
+
+Under Windows use TortoiseSVN L<http://tortoisesvn.tigris.org/> to 
+check it out from that URL.
+
+
+=head2 The problem
+
+If you write a simple Catalyst application, you can put the database
+connection right in the model class. E.g. in the sample ExtJS application
+in lib/ExtJS/Model/ExtJSModel.pm you might have:
+
+ package ExtJS::Model::ExtJSModel;
+
+ use strict;
+ use base 'Catalyst::Model::DBIC::Schema';
+
+ __PACKAGE__->config(
+     schema_class => 'ExtJS::Schema',
+     connect_info => [
+         'dbi:SQLite:extjs.db',
+         'myusername', 'mypassword',
+         { AutoCommit => 1 },
+     ],
+ );
+
+That just works.
+
+However, say you are using live and test environments and
+they have different paths to the database, or different SQL usernames and
+passwords. Now you would rather hold the database connection information 
+in a separate configuration file that you can change depending whether 
+it's the live or test area.
+
+Also, as well as a master application 
+configuration file like C< conf/extjs.pl > you may want a local site file
+like C< conf/extjs_local.pl > that lets you override settings in your
+development area. Commonly you might want to turn on extra debug or 
+turn off flags to stop your application doing things like sending 
+emails. It's annoying debugging web user registration and receiving 100 emails a day
+from yourself.
+
+
+=head2 The Solution
+
+Change the main Catalyst configuration and the Model configuration 
+to run from several separate configuration files. Split out the
+model database connection parameters.
+
+
+=head2 Catalyst Configuration
+
+To handle multiple configuration files we need to replace ConfigLoader with ConfigLoader::Multi
+in our main Catalyst perl module and let it do the hard work for us.
+
+in C< lib/ExtJS.pm >
+
+ use Catalyst qw/ -Debug 
+   ConfigLoader::Multi
+   ...
+   /;
+
+ use Config::Any::Perl;
+
+ our $VERSION = '0.01';
+
+ __PACKAGE__->config( name => 'ExtJS' );
+ __PACKAGE__->config( file => __PACKAGE__->path_to('conf') );
+
+ __PACKAGE__->setup;
+
+This looks for files below your application root directory in the C< conf > sub-directory
+named after the lower case version of your application name, in this case 'extjs',
+with an optional '_local' after it, followed by a suffix from any of the known configuration
+file extensions / formats:
+
+=over 2
+
+=item * .yml or .yaml / YAML
+
+=item * .pl or .perl / Perl
+
+=item * .xml / XML
+
+=item * .json or .jsn / JSON
+
+=item * .ini / INI
+
+=item * .cnf or .conf / Config::General
+
+=back
+
+The equivalent regex pattern for this application is something like 
+C< conf/extjs(_local)?\.{yml,yaml,pl,perl,xml,json,jsn,ini,cnf,conf} >.
+
+In this application I've used .pl Perl configuration files. I write my code in 
+Perl and like to have my configuration in Perl too.
+It's nice to write config like:
+
+  session => {
+     expires => (60*60*24 * 1), # 1 day in seconds
+  },
+
+Yeah, baby!
+
+Note that the order of loading would be extjs.pl followed by extjs_local.pl, so the 
+local configuration file settings override the default ones in extjs.pl.
+
+You can read more details about ConfigLoader::Multi here
+L<http://search.cpan.org/author/TOMYHERO/Catalyst-Plugin-ConfigLoader-Multi-0.03/lib/Catalyst/Plugin/ConfigLoader/Multi.pm>.
+It's a nice piece of work.
+
+
+=head2 Set Up Configuration Files
+
+We've set up our Catalyst application to expect multiple configuration files, so
+we'd better create them:
+
+C< conf/extjs.pl >
+
+ # extjs.pl
+ {
+   name => 'ExtJS',
+   default_view => 'TT',
+   static => {
+     include_path => [ '__path_to(root/static)__' ],
+   },
+   overrideme => 'first value',
+ }
+
+Note that Catalyst replaces __path_to with the application root directory.
+
+
+C< conf/exjs_local.pl >
+
+ # extjs_local.pl
+ {
+   dummy => 'someval',
+   overrideme => 'second value',
+ }
+
+That 'overrideme' value should override the one in extjs.pl. We'll check later.
+
+
+C< conf/extjs_model.pl >
+
+ # extjs_model.pl
+ {
+   # model DBI connection data
+   'Model::ExtJSModel' => {
+     schema_class => 'ExtJS::Schema',
+     connect_info => [ 'dbi:SQLite:extjs.db', '', '', { AutoCommit => 1 } ],
+    #connect_info => [ 'DBI:mysql:database=extjs;host=localhost', 'username', 'password', {} ], # mysql connection string sample
+   },
+ }
+
+Here's our model connection data separated out. Note that we could have more 
+than one model in here. For example, you may have an authentication database
+holding user and session, and a separate application database holding the
+other tables.
+
+
+=head2 Add Debugging
+
+In C< lib/ExtJS.pm > add near the top
+
+ use Data::Dump qw(dump);
+
+and then add a debug line after the setup call like this:
+
+ __PACKAGE__->setup;
+ $ENV{CATALYST_CONF_DEBUG} && print STDERR 'cat config looks like: '. dump(__PACKAGE__->config) . "\n";# . dump(%INC)."\n";
+
+Now if you run your application like this (Linux)
+
+ $ CATALYST_CONF_DEBUG=1 perl script/extjs_test.pl /
+
+or (Windows)
+
+ C:\mydir> SET CATALYST_CONF_DEBUG=1
+ C:\mydir> perl script\extjs_test.pl /
+
+you will see the contents of your loaded configuration prettily dumped to the output.
+
+This makes it easy to spot configuration errors that otherwise you would not see.
+You can spend a long time re-running an application wondering why it doesn't work
+only to find out some vital configuration setting is not being loaded.
+
+At this point you may want to try running the ExtJS sample app 
+like this to verify that the configuration key 'overrideme' is indeed set
+to 'second value'.
+
+
+=head2 Model Configuration Loader
+
+The final step is to make our Model class(es) work with the separated model
+configuration file we created. The following is a rather kludgy manual solution  
+that could do with a more elegant solution tied to ConfigLoader::Multi.
+But it works with Catalyst today (2007/12/12).
+
+C< lib/ExtJS/Model/ExtJSModel.pm >
+
+ use Catalyst qw/ ConfigLoader /; # gives us __PACKAGE__->config->{'home'}
+ use Config::Any::Perl;
+ use Path::Class;
+
+ my $cfg;
+ eval { $cfg = ExtJS->config; }; # this succeeds if running inside Catalyst
+ if ($@) # otherwise if called from outside Catalyst try manual load of model configuration
+ {
+   my $cfgpath1 = Path::Class::File->new( __PACKAGE__->config->{'home'},
+     'conf', 'extjs_model_local.pl' )->stringify;
+   my $cfgpath2 = Path::Class::File->new( __PACKAGE__->config->{'home'},
+     'conf', 'extjs_model.pl' )->stringify;
+   my $cfgpath = -r $cfgpath1 ? $cfgpath1 
+               : -r $cfgpath2 ? $cfgpath2
+               : die "cannot read $cfgpath1 or $cfgpath2";
+   delete $INC{$cfgpath}; # workaround so older Config::Any::Perl will work when reloading config file
+   $cfg = Config::Any::Perl->load( $cfgpath );
+ }
+
+ # test we have got our model config in
+ defined $cfg->{'Model::ExtJSModel'} || die "Catalyst config not found";
+
+ # put model parameters into main configuration
+ __PACKAGE__->config( $cfg->{'Model::ExtJSModel'} );
+
+As the comments suggest, if you're running inside the Catalyst framework
+the configuration just works.
+However, if you're running from an external batch
+progam then you have to read the config the hard way and you don't get 
+niceties like the '__path_to(root/static)__' we mentioned before in the
+'Set Up Configuration Files' section.
+
+Here I've allowed for reading either a conf/extjs_model_local.pl or conf/extjs_model.pl
+config file to define the database connection string. Then in your development area 
+you can use an extjs_model_local.pl to point to a different database.
+
+You can change the way this code works if you prefer to merge local and master config files
+(like ConfigLoader::Multi) or if you want to handle them in a more 
+generalised way using a regex file glob().
+
+The reload workaround mentioned is not necessary with the latest version of 
+Config::Any::Perl but I mention it because it fixes an issue when using 
+multiple models in earlier versions.
+
+
+=head2 External Batch Script
+
+Finally we get to our external batch script, the reason we went through 
+all the above. In the example app look at C< t/10_schema.t > and
+C< script/dump_bookings.pl >. These demonstrate how to write an
+external test script and batch program that access a Catalyst model class.
+
+C< script/dump_bookings.pl >
+
+ #!/usr/bin/perl
+ # script/dump_bookings.pl
+ # a really simple example of accessing a Catalyst application's Model
+ # from an external script
+ # it lists all the booking records in the database
+
+ use strict;
+ use warnings;
+
+ # allow for running from root directory or from script directory
+ use lib qw / lib ../lib /;
+
+ use ExtJS::Model::ExtJSModel;
+ use ExtJS::Schema;
+
+ # demonstrate picking up database connection info
+ my $connect_info = ExtJS::Model::ExtJSModel->config->{connect_info};
+ print "connecting schema to ".$connect_info->[0]."\n";
+
+ # connect to the Catalyst schema
+ my $schema = ExtJS::Schema->connect( @$connect_info );
+
+ # show the model classes available
+ my @sources = $schema->sources();
+ print 'found schema model sources :-  ' . join(", ", at sources) . "\n";
+
+ # list all bookings
+ print "listing all bookings ordered by po_ref\n";
+ my $rs = $schema->resultset('Booking')->search({}, { order_by => 'po_ref' });
+ for my $row ( $rs->all )
+ {
+   print "\nBooking ". $row->id ." - PO Ref " . $row->po_ref . "\n";
+   for my $col ( sort $row->columns )
+   {
+     next if $col eq 'id' || $col eq 'po_ref';
+     printf "  %-20s: %-50s\n", $col, $row->get_column($col);
+   }
+ }
+
+Try running it
+
+ $ perl script/dump_bookings.pl
+
+The model connection information was automatically read by the model class 
+from conf/extjs_model.pl. All we had to do is use the Booking schema class 
+to read and dump out all the bookings.
+
+That's all for now. I hope this helps you get a clearer idea of how to play with
+Catalyst configuration files and model classes.
+
+
 =head1 AUTHOR
 
-peterdragon
+peterdragon - Peter Edwards <peter at dragonstaff.co.uk>
+
+L<http://perl.dragonstaff.co.uk/>




More information about the Catalyst-commits mailing list