Skip site navigation (1)Skip section navigation (2)

FreeBSD Manual Pages

  
 
  

home | help
DBIx::Class::MigrationUsertContributed:PerlsD:Migration::Tutorial::Catalyst(3)

NAME
       DBIx::Class::Migration::Tutorial::Catalyst - Using a web	framework

GOAL
       By the end of this section, you will learn some strategies for using
       migrations with web development and for testing.

       This is not a Catalyst tutorial.	 You should be familar with the
       Catalyst	web development	framework, and have read the Catalyst::Manual.
       Although	we will	build a	minimal	Catalyst application we	are focused on
       database	integration as well as exploring some strategies for testing
       and would not consider this application to represent overall best
       practices.

       Reviewing the documentation for Catalyst::Test would be valuable, as
       well as Catalyst::Plugin::ConfigLoader for a refresher on the idea of
       localized and enviroment	specific configurations.

Bootstrap a basic Catalyst application
       Update you "dist.ini" file:

	   name	   = DBIx-Class-Migration
	   author  = John Napiorkowski <jjnapiork@cpan.org>
	   license = Perl_5
	   copyright_holder = John Napiorkowski
	   copyright_year   = 2012
	   abstract = Tutorial Application for DBIx-Class-Migration

	   version = 0.001

	   [@Basic]
	   [Prereqs]
	   Moose = 0
	   MooseX::MethodAttributes = 0
	   DBIx::Class = 0
	   DBIx::Class::Migration = 0
	   Catalyst = 0
	   Catalyst::Devel = 0
	   Catalyst::Plugin::ConfigLoader = 0
	   Catalyst::Model::DBIC::Schema = 0
	   Catalyst::Action::RenderView	= 0
	   Catalyst::View::TT =	0
	   Plack = 0

	   [Prereqs / TestRequires]
	   Test::DBIx::Class = 0
	   Test::Most =	0

       You should see we've added two dependencies related to Catalyst.	 Now
       install them with cpanm:

	   dzil	listdeps | cpanm

       Next, bootstrap a Catalyst application.	I know you can use the
       catalyst.pl commandline tool, but for our simple	application let's just
       create a	few files manually:

	   touch app.psgi
	   touch lib/MusicBase/Web.pm
	   mkdir lib/MusicBase/Web
	   mkdir lib/MusicBase/Web/Controller
	   mkdir lib/MusicBase/Web/Model
	   mkdir lib/MusicBase/Web/View
	   touch lib/MusicBase/Web/Controller/Root.pm
	   touch lib/MusicBase/Web/Model/Schema.pm
	   touch lib/MusicBase/Web/View/HTML.pm
	   mkdir share/etc
	   touch share/etc/musicbase_web.pl
	   mkdir share/html
	   touch share/html/index.tt

       If you prefer to	use a helper, you can also try
       <https://metacpan.org/pod/Dist::Zilla::Plugin::Catalyst::New> it
       generates part of the files catalyst.pl creates but is closer to	the
       normal Dist::Zilla stuff.

       Now open	"lib/MusicBase/Web.pm" and change it to	look like this:

	   package MusicBase::Web;

	   use Moose;
	   use Catalyst	qw/
	     ConfigLoader
	   /;

	   extends 'Catalyst';

	   our $VERSION	= '0.01';

	   __PACKAGE__->config(
	     'Plugin::ConfigLoader' => {
	       file => __PACKAGE__->path_to('share', 'etc'),
	     },
	   );

	   __PACKAGE__->setup;
	   __PACKAGE__->meta->make_immutable;

       This is a pretty	plain and straight Catalyst application	class.	The
       only thing I've done differently	from default is	I've placed our
       configuration files in "share/etc", rather than in the application root
       (the directory that contains your "dist.ini" file).  I think this is a
       bit more	forward	looking, and since we already have the "share"
       directory, why not use it?

       For your	Model, we'll use "Catalyst::Model::DBIC::Schema" to provide a
       bit of thin glue	between	you Catalyst web application and your
       MusicBase::Schema:

       "lib/MusicBase/Web/Model/Schema.pm"

	   package MusicBase::Web::Model::Schema;

	   use Moose;
	   extends 'Catalyst::Model::DBIC::Schema';

	   __PACKAGE__->meta->make_immutable;

       As is typical for Catalyst models, there's not a	lot going on here,
       just the	minimum	useful glue to make Catalyst aware of your Schema.

       Let's add the View now.	Open "lib/MusicBase/Web/View/HTML.pm" and
       enter:

	   package MusicBase::Web::View::HTML;
	   use base  'Catalyst::View::TT';

	   1;

       We'll need a bit	of configuration to finish the job of hooking the two
       together	so open	"share/etc/musicbase_web.pl" in	your text editor and
       make it look like this:

	   {
	     name => 'MusicBase::Web',
	     default_view => 'HTML',
	     disable_component_resolution_regex_fallback => 1,
	     'Controller::Root'	=> {
	       namespace => '',
	     },
	     'Model::Schema' =>	{
	       traits => ['FromMigration'],
	       schema_class => 'MusicBase::Schema',
	       install_if_needed => {
		 default_fixture_sets => ['all_tables']},
	     },
	     'View::HTML' => {
	       INCLUDE_PATH => [ '__path_to(share,html)__' ],
	       TEMPLATE_EXTENSION => '.tt',
	     },
	   };

       I know many of the "Catalyst" examples use "Config::General" or YAML
       for configuration information.  I tend to use Perl configuration	files
       because of the extra flexibility.

       I'm setting a few things	here, but for our discussion the most
       important one is	the "Model::Schema" section, where I point our model
       to the SQLite database we've been using all along.  Since we just want
       Catalyst	to use our database sandbox, the easiest way to	do this	is to
       use the "FromMigration" trait, which you	get for	free when you install
       DBIx::Class::Migration.	You can	review the documentation at
       Catalyst::TraitFor::Model::DBIC::Schema::FromMigration.

       The configuration given would use the SQLite sandbox.  What if you
       wanted to switch	to using the MySQL sandbox instead?  The
       "Model::Schema" parameters would	look like so:

	   'Model::Schema' => {
	     traits => ['FromMigration'],
	     schema_class => 'MusicBase::Schema',
	     extra_migration_args => {
	       db_sandbox_class	=> 'DBIx::Class::Migration::MySQLSandbox'},
	     install_if_needed => {
	       default_fixture_sets => ['all_tables']},
	     },

       In any case, you	hopefully noticed that we also run some	setup code to
       install the database and	populate some fixtures,	if they	are missing.

       NOTE: If	you use	the "FromMigration" trait, we will automatically start
       and stop	the database if	needed (and you	are using a database like
       MySQL or	Postgresql that	needs starting and stopping).  This startup
       and teardown can	impact the startup time	of you application.

       NOTE: If	you already had	a database setup, and are not using the
       database	sandbox	feature	(as you	won't when in a	production server, or
       if you are using	some shared hosting setups, for	example) you should
       setup your "connect_info" as you	normally would in a Catalyst
       configuration.

       Let's setup a trivial controller	that pulls a few rows out of the
       database	and just outputs this to a web page.

	   package MusicBase::Web::Controller::Root;

	   use Moose;
	   use MooseX::MethodAttributes;

	   extends 'Catalyst::Controller';

	   sub index :Path :Args(0) {
	     my	($self,	$ctx) =	@_;
	     my	@artists = $ctx->model('Schema::Artist')
	       ->search({},{ result_class =>
		 'DBIx::Class::ResultClass::HashRefInflator' })
	       ->all;

	     $ctx->stash(artists => \@artists);
	   }

	   sub end : ActionClass('RenderView') {}

	   __PACKAGE__->meta->make_immutable;

       Here we want to just get	all the	Artists	and send them to our View.
       Since in	MVC it is considered correct to	inform a View of a Model in a
       Read Only manner	(in other words, your View should not be able to
       modify the Model) I generally use the
       DBIx::Class::ResultClass::HashRefInflator result	class which will
       flatten your results to an array	of hashrefs, rather than return	a list
       of result objects.  Besides making it impossible	for your template
       authors to accidentally modify the model, you get a nice	speed bump
       since inflating an array	of hashrefs is much faster than	creating all
       those result objects.

       Usually I create	a resultset method in my base resultset	class, and
       have all	my custom resultsets inherit from that.	 Something like:

	   package MusicBase::Schema::ResultSet;
	   use base 'DBIx::Class::ResultSet;

	   sub all_as_array {
	     shift->search({},{	result_class =>
		 'DBIx::Class::ResultClass::HashRefInflator' })
	     ->all;
	   }

	   sub all_as_arrayref { [shift->all_as_array] }

       I would have "MusicBase::Schema::ResultSet::Artist" inherit from
       "MusicBase::Schema::ResultSet" instead of DBIx::Class::ResultSet	as it
       does now.  Then I could have written:

	   sub index :Path :Args(0) {
	     my	($self,	$ctx) =	@_;
	     $ctx->stash( artists =>
	       $ctx->model('Schema::Artist')->all_as_arrayref );
	   }

       This is a common	enough pattern for me that it is worth the trouble to
       create the base class.  Additionally, if	you have lots of DBIx::Class
       components to load it helps to create a central base class, since that
       speeds things up	at load	time.

       And here's the template for the webpage:	"share/html/index.tt"

	   <html>
	     <head>
	       <title>Artists</title>
	       <link rel="stylesheet"
		 href="http://twitter.github.com/bootstrap/1.4.0/bootstrap.min.css"
		 type="text/css" rel="Stylesheet">
	     </head>
	     <body class="container">
	       <h1>Artist List</h1>
	       [% FOR artist IN	artists	%]
		 <p>[% artist.name %]</p>
	       [% END %]
	     </body>
	   </html>

       We'll use Twitter's Bootstrap CSS to make things	look neat. Lastly you
       need to edit "app.psgi" as follows:

	   use MusicBase::Web;
	   MusicBase::Web->psgi_app;

       So now we can start our Catalyst	application!

	   plackup -Ilib

Integrating DBIx::Class::Migration and Catalyst.
       There's two main	places to where	Catalyst and DBIx::Class::Migration
       can cooperate: Running migrations and Running Tests.

   Running Migrations
       Although	you can	just use dbic-migration	directly with you Catalyst
       application, since Catalyst already does	a great	job of managing
       configuration, let's learn how to subclass
       DBIx::Class::Migration::Script and customize it for your	application.
       That way	you don't need to set "ENV" or pass option flags to the
       "dbic-migration"	commandline tool (and easily make a mistake and
       upgrade the wrong database :) ).

	   touch lib/MusicBase/Schema/MigrationScript.pm

       And then	open "lib/MusicBase/Schema/MigrationScript.pm" in your editor
       and change it to	look like this:

	   package MusicBase::Schema::MigrationScript;

	   use Moose;
	   use MusicBase::Web;

	   extends 'DBIx::Class::Migration::Script';

	   sub defaults	{
	     schema => MusicBase::Web->model('Schema')->schema,
	   }

	   __PACKAGE__->meta->make_immutable;
	   __PACKAGE__->run_if_script;

       Basically you've	made a subclass	of DBIx::Class::Migration::Script but
       you are setting the "schema" to always be whatever Catalyst thinks it
       is.  Now	you can	use Catalysts built in configuration management	to
       decide what database you	are running migrations on.  For	example	you
       can run this straight out (remember to remove the ENV var
       DBIC_MIGRATION_SCHEMA_CLASS, if you have	it set now for running the
       tutorial)

	   $ perl -Ilib	lib/MusicBase/Schema/MigrationScript.pm	status
	   Schema is 3
	   Deployed database is	3

       And you can reset the data from fixtures, dump new ones,	etc.  Plus, if
       you created an enviroment specific configuration	(such as if you	have a
       file "share/etc/musicbase_web_qa.pl" that points	to your	QA datase) you
       can leverage you	Catalyst based configuration to	make your life a bit
       easier.	For example:

	   CATALYST_CONFIG_LOCAL_SUFFIX=qa perl	-Ilib \
	     lib/MusicBase/Schema/MigrationScript.pm status

       Would grab the connected	schema for your	qa enviroment specific
       configuration and give you the status on	that (assuming you can ping it
       from your logged	in terminal).  This integration	is very	useful since
       you can use whatever your Catalyst application thinks is	the current
       database	as the target of the migration.	 You can use other bits	of
       configuration info as well, such	as a custom "target_dir" etc.

       Alternative: If subclasing DBIx::Class::Migration::Script seems like an
       overly heavy handed solution, or	running	the *.pm file like a script
       just weirds you out, you	can simply create a script like	the following,
       which would work	identically:

       For example, something in "script/my-dbic-migration"

	   #!/usr/bin/env perl

	   use MusicBase::Web;
	   use DBIx::Class::Migration::Script;

	   DBIx::Class::Migration::Script
	     ->run_with_options(
	       schema => MusicBase::Web->model('Schema')->schema);

       And then	use it like:

	   perl	-Ilib script/my-dbic-migration status

       NOTE: In	this case you need to invoke the perl interp with "-Ilib" so
       that we can find	"MusicBase::Web"

       If you have a bunch of files in "/script" or if you need	to be able to
       run your	custom migration tool after installation, this could be	a good
       option.

   Running Tests
       We've seen how using tools like Test::DBIx::Class together with
       DBIx::Class::Migration can really simplify your unit level testing
       effort.	Using them you don't need to spend a lot of time setting up
       dedicated testing databases and managing	configuration sets (that need
       to change over time).  However if you want to write tests that check
       your actual web pages (for example you want to test things like if a
       page shows the correct results and if web forms work) you need to
       manage that a bit differently.  Here's what I do:

       First, create a enviroment specific configuration for testing:

	   touch share/etc/musicbase_web_test.pl

       Then open "share/etc/musicbase_web_test.pl" and add the following:

	   {
	     'Model::Schema' =>	{
	       traits => ['FromMigration'],
	       schema_class => 'MusicBase::Schema',
	       extra_migration_args => {
		 db_sandbox_builder_class => 'DBIx::Class::Migration::TempDirSandboxBuilder',
		 db_sandbox_class => 'DBIx::Class::Migration::MySQLSandbox'},
	       install_if_needed => {
		 default_fixture_sets => ['all_tables']},
	     },
	   };

       So what is going	to happen here is if you start the application
       pointing	to this	configuration (with
       "CATALYST_CONFIG_LOCAL_SUFFIX"=test) when the application runs it will
       automatically create a clean new	database and populate it with the
       "all_tables" fixture set.  Just for fun,	we will	create a test instance
       of Mysql.  Please note this will	be a temporary sandbox,	and will be
       deleted when your Catalyst application exits.  It is not	the same as
       the MySQL sandbox we created in "share/musicbase-schema/*".

       This looks similar to the first configuration file we did, where	we use
       the "FromMigration" trait to hookup your	migration deployments.
       However,	instead	of running our tests on	the database sandbox in
       "/share"	(which you don't want to so since that's not going to be a
       reliable	and consistent database	for testing) we	use the	following bit:

	   db_sandbox_builder_class => 'DBIx::Class::Migration::TempDirSandboxBuilder',

       To instruct the sandbox builder to put the sandbox into a temporary
       directory instead of "/share".  What will happen	here is	that (similar
       to the way that we saw with Test::DBIx::Class) we build up a database
       from scratch, populate it with known fixtures, run tests, and then tear
       it down at the end.  This way you get clean and repeatable tests.  The
       downside	is that	the buildup / teardown can add time to the tests,
       athough you should be able to run your test cases in parallel (using
       "prove -j9 ...",	to run up to nine tests	at once) to offset this	issue.

       Let's write a test case:

	   touch t/web.t

       And open	"t/web.t" in your editor:

	   #!/usr/bin/env perl

	   use Test::Most;
	   use Catalyst::Test 'MusicBase::Web';

	   ok my $content  = get('/'),
	     'got some content';

	   like	$content, qr/Michael Jackson/,
	     'Found Michael Jackson';

	   done_testing;

       Finally run your	test:

	   CATALYST_CONFIG_LOCAL_SUFFIX=test prove -lvr	t/web.t

       You know	from when we did the original demo data	script that "Michael
       Jackson"	was one	of the artist, so we'd expect to find him in the
       <$content> from the Root	controller (since that's just a	list of	all
       the Artist names).  So you'd probably want a bit	more testing on	this
       page, but this should give you the idea.

       Since the above test builds and breaks down a full MySQL	sandbox, it
       might not run instantly,	just FYI.

SUMMARY
       That's it for some ideas	on using migrations with a web development
       framework like Catalyst.	 If you	are using Catalyst you can take
       advantage of its	great configuration management tools to	make it	even
       easier to manage	your migrations.  You also now have some strategies
       for making it easy to test.

NEXT STEPS
       Proceed to DBIx::Class::Migration::Tutorial::Conclusion

AUTHOR
       See DBIx::Class::Migration for author information

COPYRIGHT & LICENSE
       See DBIx::Class::Migration for copyright	and license information

perl v5.32.1			 DBIx::Class::Migration::Tutorial::Catalyst(3)

NAME | GOAL | Bootstrap a basic Catalyst application | Integrating DBIx::Class::Migration and Catalyst. | SUMMARY | NEXT STEPS | AUTHOR | COPYRIGHT & LICENSE

Want to link to this manual page? Use this URL:
<https://www.freebsd.org/cgi/man.cgi?query=DBIx::Class::Migration::Tutorial::Catalyst&sektion=3&manpath=FreeBSD+13.0-RELEASE+and+Ports>

home | help