Catalyst Advent Calendar 2014http://www.catalystframework.org/calendar/feed/20142014-12-20T00:59:01ZXML::Atom::SimpleFeedThe Future of Catalyst, or the Fifty Year Framework
<div class="pod">
<h1 id="The_Future_of_Catalyst_or_the_Fifty_">The Future of Catalyst, or the Fifty Year Framework</h1>
<div id="The_Future_of_Catalyst_or_the_Fifty_-2">
</div>
<h1 id="Overview">Overview</h1>
<div id="Overview_CONTENT">
<p>Wrap up of Advent 2014, musings on the future.</p>
</div>
<h1 id="Ten_Years_of_Catalyst">Ten Years of Catalyst</h1>
<div id="Ten_Years_of_Catalyst_CONTENT">
<p>From the Changes file:</p>
<pre> 2.99_12 2005-01-28 22:00:00 2005
- first development release
</pre>
<p>Well, its been a fast ten years hasn't it? I've been lucky enough to have now
several jobs where <a href="http://metacpan.org/module/Catalyst">Catalyst</a> played an important part of the technology stack
and I've been grateful for the career opportunities and work life stability that
has given me. As I am sure many of you also feel. And I am pleased that I've been
able to spend so much time with this framework, which I consider a seminal Modern
Perl project. From my outlook nearly everything is better now than ten years ago.</p>
<p>But I think this is not a great time to think about all the past things. For sure
<a href="http://metacpan.org/module/Catalyst">Catalyst</a> still evolves and has managed to change itself over time to meet new
developer needs. That is a task that is ongoing and probably is always behind
schedule. And although we did a lot I can't help but feel slightly sad that we did
not manage to achieve more notoriety beyond our circle, as compared to similar
frameworks in other languages that date from around the same time. However the
question I often find myself thinking lately is what can I do to make sure the next
ten years of Catalyst are as at least successful as the first ten, if not more
so. Which leads me to think about what in Catalyst is changing, and how we make
decisions about change.</p>
</div>
<h1 id="Catalyst_change_management_process">Catalyst change management process</h1>
<div id="Catalyst_change_management_process_C">
<p>In general there are two rules I use when thinking about changing Catalyst. The
first is technical merit of the idea. If there is a bug, then its obvious it needs
to be fixed. Less obvious is the types of refactoring that went into giving Catalyst
modern features like websocket support, interoperability with event loops and to expose
more and more of Catalyst's <a href="http://metacpan.org/module/PSGI">PSGI</a> underpinnings.</p>
<p>When an idea has strong technical merit, it recommends itself. The only thing to
consider is the needs of backward compatibility, and to offer people upgrading at
least some sort of path forward when features change (such as to have plugins or
configuration options to replace or replicate something that is no longer available).</p>
<p>Then there is a second and more difficult type of change consideration, which is
the general will of the community. Like technical merit, this needs to balance against
our commitment to not leave existing users high and dry with changes that break code
and offer no path forward that does not involve significant code rewrites. Unlike
technical merit, the will of the community can be hard to figure. In general we don't
get a lot of bug reports or conversation around Catalyst future evolution. I wish
I could find a way to get more involvement, but I also understand this is not very
unusual issue for open source projects. I personally don't believe that &quot;silence is
consent&quot; either. I think choices need to have broad acceptability or the choosers
lose respect and authority. Typical that results in people just drifting away.</p>
<p>Without direct involvement the only other way to measure the will of the community
is to look at what other choices people are making and what other projects have
received the acceptance of a broad number of people. Since <a href="http://metacpan.org/module/Plack">Plack</a> is clearly
accepted and important it leads me to feel the choice to make Catalyst expose
more of its Plack nature and to better play with the larger Plack ecosystem are
correct ones. One can also pay attention to the kinds of problems that get reported
on IRC, at conferences and the problems that I see having looked at how Catalyst has
been used in the wild. For example its clear that Chaining actions could use a tweak
in some way since it seems to trip up people a lot. The same goes with $c-&gt;forward
and $c-&gt;go, which tend to lead to confusing code (and combined with the stash is a
particularly toxic brew).</p>
<p>Going further, if we allow ourselves to look hard at projects
outside of Perl we can get lots of great ideas about what has worked for other
projects in other languages. When we see certain features and approaches have
excited programmers using frameworks like Ruby on Rails, Django, Scala Play, etc.
then it should provide us with with help in thinking about how those features
might influence the evolution of Catalyst as well.</p>
</div>
<h1 id="2015">2015?</h1>
<div id="2015_CONTENT">
<p>Given all that, what could 2015 look like? I fully expect to see us stabilize all
the great new UTF8 and encoding fixes and to start building some community practices
to help people solve complex encoding problems. I also expect to see us continue
to move more features into the middleware layer, such that over time most of what
we think of as the Catalyst context would be mostly an interface over the Plack $env.</p>
<p>Thinking bigger, I've been asked when if ever will we have a version of Catalyst
that does more to discard the technical debt of the past. I am very committed to
offering people a path forward, even if Catalyst Six is built on a newer, cleaner
codebase, you should be able to run your existing applications. I believe that
we can do this because now that Catalyst exposes so much of its Plack roots it
is possible to think that we could have a version of Catalyst Six which could mount
over the PSGI interface a &quot;Catalyst 5 VM&quot; so to speak. So in a similar way that
Apple migrated from OS9 to OSX, Catalyst 5 can migrate to a Catalyst Six. It would be work
but it would be possible to think you could have Catalyst 5 and Six controllers and
models side by side, and the application would automatically do all the needed piping.
That way you could migration code as it needed and as you had time and will.</p>
<p>So that is the way we could do it, yet I think in 2015 we really need to answer the questions
&quot;What are we changing, and why, and how is it going to make it better?&quot; Otherwise
there's no point in taking the risk of alienating those who have spend many years
learning and building applications on top of the Catalyst core we all know and
use everyday.</p>
<p>Towards that end I initiated a documentation project a few months back with the intention
of summarizing core Catalyst code and concepts. The idea is we we fully understand what
we are today, we can more easily generate a diff to tomorrow. The project is currently
an outline and you can see it (and send pull requests to)</p>
<p><a href="https://github.com/perl-catalyst/Catalyst-Components-Concepts-Cases">https://github.com/perl-catalyst/Catalyst-Components-Concepts-Cases</a></p>
<p>My hope is that the community can help me at least ask the correct questions, so please
take a look and send a PR or open an issue with your thoughts. I've also considered making
a request for a grant from the Perl Foundation in order to work on these docs, and wonder
what you all think of that :)</p>
<p>Until we can correctly answer all these questions, I will continue to push to change
Catalyst in ways that technical merit themselves, and to do what I can do to make a
transition as I've outlined above more possible.</p>
</div>
<h1 id="Conclusion">Conclusion</h1>
<div id="Conclusion_CONTENT">
<p>Like all open source projects <a href="http://metacpan.org/module/Catalyst">Catalyst</a> needs community involvement
at all levels in order to continue to evolve in a direction that maintains
relevance for today and the future. As we begin the tenth year of <a href="http://metacpan.org/module/Catalyst">Catalyst</a>
it will be that involvement which determines if our usefulness will fade or
if we continue to meet our ever changing needs for a Modern Perl web framework.</p>
</div>
<h1 id="Author">Author</h1>
<div id="Author_CONTENT">
<p>John Napiorkowski</p>
</div>
</div>Catalystcatalyst@lists.scsys.co.ukhttp://www.catalystframework.org/calendar/2014/212014-12-20T00:59:01Z2014-12-20T00:59:01ZApplication-wide access to the context ($c) for the impatient
<div class="pod">
<h1 id="Application_wide_access_to_the_conte">Application-wide access to the context ($c) for the impatient</h1>
<div id="Application_wide_access_to_the_conte-2">
</div>
<h1 id="Overview">Overview</h1>
<div id="Overview_CONTENT">
<p>MVC is a proven development pattern, and its rules and best practices are certainly worthwhile.
Still, sometimes you just want to get at <code>$c</code> from the outside of controller code, and
don't want to fuss with passing it around in argument lists.</p>
<p>Isn't there an easier way to just DWIW when I'm in a hurry?</p>
</div>
<h1 id="stuff_c_in_a_localized_global">stuff $c in a localized global</h1>
<div id="stuff_c_in_a_localized_global_CONTEN">
<p>Sometimes you just need a global variable. By making use of the
<a href="http://perldoc.perl.org/functions/local.html">local</a> keyword, global
variables can be isolated to a particular scope. We're going to
use this technique to &quot;localize&quot; a reference to <code>$c</code> to be globally accessible, but only within
the scope of the request it applies to.</p>
<p>First, we need to declare a package/global variable someplace. Your main app class is a
good choice (as shown below), but it could be anywhere.</p>
<pre> package MyApp;
use Catalyst;
# ...
our $__ACTIVE_CTX = undef;
sub ctx { $__ACTIVE_CTX }
</pre>
<p>I also like to create a nice, clean accessor method (<code>-&gt;ctx</code> as shown above) to use instead of
accessing the variable directly. This will enable us to get $c from anyplace like this:</p>
<pre> my $c = MyApp-&gt;ctx;
</pre>
<p>Which is exactly equivalent to:</p>
<pre> my $c = $MyApp::__ACTIVE_CTX;
</pre>
<p>Now we just need to hook into the right place to capture <code>$c</code> and localize it into our global
variable. </p>
<p>The Catalyst <code>dispatch()</code> method provides the obvious choice. It is called immediately after $c
is constructed by <code>prepare()</code> and is the gatekeeper for each request. All the processing that
happens during a normal request cycle is ultimately contained within the dynamic scope of the
<code>dispatch()</code> method.</p>
<p>We can wrap this logic very easily with an around modifier and localize our variable:</p>
<pre> around 'dispatch' =&gt; sub {
my ($orig, $c, @args) = @_;
local $__ACTIVE_CTX = $c;
$c-&gt;$orig(@args)
};
</pre>
<p>Now, throughout your application you will be able to access the context object simply by calling
<code>MyApp-&gt;ctx</code> which will work within any code in any class so long as it is called during a
request. This includes Models, but also other general perl packages/modules that you may call out
to, such as <a href="http://metacpan.org/module/DBIx::Class">DBIC</a> result classes.</p>
</div>
<h1 id="Outside_of_a_request">Outside of a request</h1>
<div id="Outside_of_a_request_CONTENT">
<p>Since <a href="http://metacpan.org/module/Catalyst">Catalyst</a> is a web framework, most code is generally expected to be called during a request,
but not always. There are plenty of other possible scenarios, such as admin scripts, cron jobs, etc
that won't touch the dispatch machinery. For these cases, our <code>-&gt;ctx()</code> method will simply
return <code>undef</code> since that is what we set it to when we declared it. By using <code>local</code> to set the
value, it is isolated to that dynamic scope and remains undef from the perspective of everywhere
else. So, we don't have to worry about stale data, exceptions/interruptions, and so on. When the
request ends and <code>dispatch()</code> returns, the localized version of the variable goes out of scope and
ceases to exist.</p>
<p>This provides a reasonable degree of confidence that our <code>-&gt;ctx()</code> method will always return the
correct context object when a request is in progress, and false/undef when it is not.</p>
<p>In your code, you should always handle both cases with conditional logic, even if you only expect
to be called during a request. For instance:</p>
<pre>
if ( my $c = MyApp-&gt;ctx ) {
# do stuff specific to web request, apply permissions, etc
# ...
}
else {
# do stuff specific to non-requests
# Maybe die if that should not happen/be allowed
# Maybe skip permission logic, logging, etc
# ...
}
</pre>
<p>In many cases this can also be as simple as:</p>
<pre> my $c = MyApp-&gt;ctx or die &quot;Not in a request!&quot;;
</pre>
<p>This is at least better/more descriptive than the &quot;can't call method on undefined value&quot; type
exception you'll get if you simply assume you're in a request and blindly try to use $c.</p>
</div>
<h1 id="Caveats">Caveats</h1>
<div id="Caveats_CONTENT">
<p>This should be filed under the category of &quot;quick and dirty&quot; and is still not recommended to be
your first design choice. It is handy for getting stuff working quickly and in a pinch.</p>
<p>Also, note that this should <strong>not</strong> be expected to work under async scenarios -- only when you are deploying
with standard synchronous worker threads.</p>
</div>
<h1 id="Author">Author</h1>
<div id="Author_CONTENT">
<p>Henry Van Styn <a href="email:vanstyn@cpan.org">vanstyn@cpan.org</a></p>
</div>
</div>Catalystcatalyst@lists.scsys.co.ukhttp://www.catalystframework.org/calendar/2014/202014-12-20T00:59:01Z2014-12-20T00:59:01ZAnother Take on redispatching
<div class="pod">
<h1 id="Another_Take_on_redispatching">Another Take on redispatching</h1>
<div id="Another_Take_on_redispatching_CONTEN">
</div>
<h1 id="Overview">Overview</h1>
<div id="Overview_CONTENT">
<p>In a previous advent story we reviewed a recipe for adding a redispatch method
to the global <a href="http://metacpan.org/module/Catalyst">Catalyst</a> object. This allows one to replace the current
request with a new request to a different public path (URL). Presented is a
different approach to the same problem, but instead of hacking Catalyst internals
it creates a new request and dispatches over PSGI. This leads to a higher level
of isolation as well as the ability to construct a full <a href="http://metacpan.org/module/HTTP::Request">HTTP::Request</a> and
customize it as need (for example make a POST request, or sets the query parameters.</p>
</div>
<h1 id="The_redispatch_to_methods">The redispatch_to methods</h1>
<div id="The_redispatch_to_methods_CONTENT">
<p>Here's the method on your application class:</p>
<pre> use warnings;
use strict;
package MyApp;
use Catalyst;
use HTTP::Message::PSGI ();
sub redispatch_to {
my $c = shift;
my $env = HTTP::Message::PSGI::req_to_psgi(shift);
our $app ||= $c-&gt;psgi_app;
$c-&gt;res-&gt;from_psgi_response( $app-&gt;($env) );
}
MyApp-&gt;setup;
</pre>
<p>This method expects an <a href="http://metacpan.org/module/HTTP::Request">HTTP::Request</a> as its first argument. We then convert
that request to a <a href="http://metacpan.org/module/PSGI">PSGI</a> style <code>$env</code> hash and invoke it on the application
coderef. The response is sent directly to the initiating response.</p>
<p>This would work fine with streaming and delayed style response, FWIW. Here's an
example controller using it:</p>
<pre> use warnings;
use strict;
package MyApp::Controller::Example;
use base 'Catalyst::Controller';
use HTTP::Request::Common;
sub base :Path('') {
my ($self, $c) = @_;
$c-&gt;redispatch_to(GET $c-&gt;uri_for($self-&gt;action_for('target')));
}
sub target :Path('target') {
my ($self, $c) = @_;
$c-&gt;response-&gt;content_type('text/plain');
$c-&gt;response-&gt;body(&quot;This is the target action&quot;);
}
__PACKAGE__-&gt;meta-&gt;make_immutable;
</pre>
<p>And a test case that shows how it works:</p>
<pre> use Test::Most;
use Catalyst::Test 'MyApp';
my $res = request &quot;/example&quot;;
is $res-&gt;code, 200, 'OK';
is $res-&gt;content, 'This is the target action', 'correct body';
done_testing;
</pre>
<p>You might note this would allow one to redispatch to essentially ANY public URL
include ones not part of your controlled website. This may be construed as a bug
or as a feature.</p>
</div>
<h1 id="Caveats">Caveats</h1>
<div id="Caveats_CONTENT">
<p>Same as in the last advent article :)</p>
</div>
<h1 id="Author">Author</h1>
<div id="Author_CONTENT">
<p>John Napiorkowski</p>
</div>
</div>Catalystcatalyst@lists.scsys.co.ukhttp://www.catalystframework.org/calendar/2014/192014-12-18T17:39:01Z2014-12-18T17:39:01ZRedispatching to a public path
<div class="pod">
<h1 id="Redispatching_to_a_public_path">Redispatching to a public path</h1>
<div id="Redispatching_to_a_public_path_CONTE">
</div>
<h1 id="Overview">Overview</h1>
<div id="Overview_CONTENT">
<p>Catalyst provides well-known dispatch API methods such as <code>$c-&gt;forward</code>,
<code>$c-&gt;go</code> and <code>$c-&gt;visit</code> to call/jump to from one controller action to
another from within controller code. However, these methods only accept private,
internal path arguments which could be completely different from the actual, publicly
exposed URL paths. This is especially true for applications with complex dispatch
logic, such as when <code>:Chained</code> and <code>:Regex</code> dispatch types are in play.</p>
<p>If you need to be able to resolve the controller action associated with a real,
public URL, there isn't any straightforward way to do it. This actually makes sense
when you think about; dispatch rules apply to the request as a whole (not just the
URL), so the only way to observe the ultimate result of a fresh request from the
outside is to actually perform one.</p>
<p>In many cases where this would come up you can just call
<code>$c-&gt;response-&gt;redirect($public_url)</code>, which simply tells the client to make a
new request. If this isn't an option, and you really need to do it within the same
request, there are ways to &quot;fake it&quot; and get <i>reasonably</i> close to what it would be.</p>
</div>
<h1 id="A_public_redispatch_recipe">A public redispatch recipe </h1>
<div id="A_public_redispatch_recipe_CONTENT">
<p>Since dispatch logic applies to a request, you must either create a new, simulated
request object, or modify the existing request object for the new URL you want to
dispatch to. The following method does the latter, along with some extra string
normalization (copied from <a href="http://metacpan.org/module/RapidApp">RapidApp</a> code base):</p>
<pre>
sub redispatch_public_path {
my ($c, @args) = @_;
my $path = join('/',@args);
$path =~ s/^\///; #&lt;-- strip leading /
$path =~ s/\/$//; #&lt;-- strip trailing leading /
$path =~ s/\/+/\//g; #&lt;-- strip any double //
$path ||= '';
$c-&gt;log-&gt;debug(&quot;Redispatching as path: $path&quot;) if ($c-&gt;debug);
# Overwrite the 'path' in the request object:
$c-&gt;request-&gt;path($path);
# Now call prepare_action again, now with the updated path:
$c-&gt;prepare_action;
# Now forward to the new action. If there is no action,
# call $c-&gt;dispatch just for the sake of error handling
return $c-&gt;action ? $c-&gt;forward( $c-&gt;action ) : $c-&gt;dispatch;
}
</pre>
</div>
<h1 id="Caveats">Caveats</h1>
<div id="Caveats_CONTENT">
<p>Keep in mind that this is only a <i>simulated</i> public redispatch because the idea of
a <i>real</i> redispatch - without a real request - doesn't actually make sense. You can't
know the result of a request that didn't happen just like you can't know if
Schrodinger's cat is still alive without opening the box.</p>
<p>Before using this code, you should take a step back and ask yourself if you actually
really need it. There is a good chance the better solution is to rethink the aspects
of your design that led you to want to do it in the first place. There are legitimate
reasons to do this in certain situations (otherwise I wouldn't even be writing this
article), but it should never be your first choice.</p>
</div>
<h1 id="Author">Author</h1>
<div id="Author_CONTENT">
<p>Henry Van Styn <a href="email:vanstyn@cpan.org">vanstyn@cpan.org</a></p>
</div>
</div>Catalystcatalyst@lists.scsys.co.ukhttp://www.catalystframework.org/calendar/2014/182014-12-18T15:39:01Z2014-12-18T15:39:01ZThe Plack::App::RapidApp::rDbic interface to RapidApp
<div class="pod">
<h1 id="The_Plack_App_RapidApp_rDbic_interfa">The Plack::App::RapidApp::rDbic interface to RapidApp</h1>
<div id="The_Plack_App_RapidApp_rDbic_interfa-2">
</div>
<h1 id="Overview">Overview</h1>
<div id="Overview_CONTENT">
<p>In the previous article I introduced the <a href="http://metacpan.org/module/RapidApp">RapidApp</a> utility script <a href="http://metacpan.org/module/rdbic.pl">rdbic.pl</a> which
provides instant CRUD interfaces for <a href="http://metacpan.org/module/DBI">DBI</a> databases. In this article we'll look
at the internal module which <a href="http://metacpan.org/module/rdbic.pl">rdbic.pl</a> uses, <a href="http://metacpan.org/module/Plack::App::RapidApp::rDbic">Plack::App::RapidApp::rDbic</a></p>
</div>
<h1 id="Mounting_the_rDbic_Plack_app">Mounting the rDbic Plack app</h1>
<div id="Mounting_the_rDbic_Plack_app_CONTENT">
<p>Consider the following <a href="http://metacpan.org/module/rdbic.pl">rdbic.pl</a> command which starts up the CRUD app using
a built-in web server:</p>
<pre> rdbic.pl dbi:mysql:mydb,root,''
</pre>
<p>Internally, this starts a PSGI application using <a href="http://metacpan.org/module/Plack::App::RapidApp::rDbic">Plack::App::RapidApp::rDbic</a>
which does the heavy lifting. The above rdbic.pl is roughly equivalent to the following
(which could be specified in a .psgi file and started with <a href="http://metacpan.org/module/plackup">plackup</a>):</p>
<pre> use Plack::App::RapidApp::rDbic;
my $app = Plack::App::RapidApp::rDbic-&gt;new({
connect_info =&gt; {
dsn =&gt; 'dbi:mysql:mydb',
user =&gt; 'root',
password =&gt; ''
}
})-&gt;to_app;
</pre>
<p>Under the hood, this generates a fully working RapidApp/Catalyst application into a temp
directory, including generation of a <a href="http://metacpan.org/module/DBIx::Class::Schema">DBIx::Class::Schema</a>, <a href="http://metacpan.org/module/Catalyst::Model::DBIC::Schema">Model::DBIC::Schema</a> and <a href="http://metacpan.org/module/Catalyst::Plugin::RapidApp::RapidDbic">Plugin::RapidDbic</a>
configuration using the RapidApp bootstrap system.</p>
<p>Alternatively, if you already have a DBIC schema class, you can tell rDbic to use it
instead of auto-generating: </p>
<pre> my $app = Plack::App::RapidApp::rDbic-&gt;new({
schema_class =&gt; 'My::Db::Schema',
connect_info =&gt; {
dsn =&gt; 'dbi:mysql:mydb',
user =&gt; 'root',
password =&gt; ''
}
})-&gt;to_app;
</pre>
<p>You can also supply an already connected schema object:</p>
<pre> my $schema = My::Db::Schema-&gt;connect();
# ...
my $app = Plack::App::RapidApp::rDbic-&gt;new({
schema =&gt; $schema
})-&gt;to_app;
</pre>
<p>This makes it possible to do things like exposing the rDbic a CRUD interface
within the structure of an existing Catalyst or PSGI app, such as:</p>
<pre> use MyCatApp;
my $app = MyCatApp-&gt;apply_default_middlewares(MyCatApp-&gt;psgi_app);
use Plack::Builder;
use Plack::App::RapidApp::rDbic;
my $rDbic = Plack::App::RapidApp::rDbic-&gt;new({
schema =&gt; MyCatApp-&gt;model('DB')-&gt;schema
});
builder {
mount '/' =&gt; $app;
mount '/rdbic/' =&gt; $rDbic-&gt;to_app;
};
</pre>
</div>
<h1 id="Configuration">Configuration</h1>
<div id="Configuration_CONTENT">
<p><a href="http://metacpan.org/module/Plack::App::RapidApp::rDbic">rDbic</a> is just a Plack wrapper for a generated RapidApp
using <a href="http://metacpan.org/module/Catalyst::Plugin::RapidApp::RapidDbic">Plugin::RapidDbic</a> which provides
powerful configuration options.</p>
<p>The RapidDbic plugin reads its config from the <code>'RapidDbic'</code> key in DBIC::Schema model,
and this can be set via the <code>model_config</code> param to rDbic:</p>
<pre> my $app = Plack::App::RapidApp::rDbic-&gt;new({
schema =&gt; $schema,
model_config =&gt; {
RapidDbic =&gt; {
# join all columns of all first-level relationships in all grids:
grid_params =&gt; {
'*defaults' =&gt; {
include_colspec =&gt; ['*','*.*']
}
},
# Set the display_column for the source named Account to 'fname':
TableSpecs =&gt; {
Account =&gt; {
display_column =&gt; 'fname'
},
}
# ...
# ...
}
}
})-&gt;to_app;
</pre>
<p>RapidDbic allows for all kinds of scenarios and customization. For more
info, see the Chinook demo on the RapidApp website:</p>
<ul>
<li><a href="http://www.rapidapp.info/demos/chinook">http://www.rapidapp.info/demos/chinook</a></li>
</ul>
</div>
<h1 id="Summary">Summary</h1>
<div id="Summary_CONTENT">
<p><a href="http://metacpan.org/module/Plack::App::RapidApp::rDbic">Plack::App::RapidApp::rDbic</a> provides a convenient Plack-based interface to a
runtime loaded <a href="http://metacpan.org/module/RapidApp">RapidApp</a> using <a href="http://metacpan.org/module/Catalyst::Plugin::RapidApp::RapidDbic">Plugin::RapidDbic</a>.
This is a great place to get started with RapidApp, although it only scratches the surface
of what can be done.</p>
<p>To learn more or get involved, see the project website at <a href="http://www.rapidapp.info">www.rapidapp.info</a> or visit us on IRC in the <code>#rapidapp</code> channel on <code>irc.perl.org</code>. </p>
</div>
<h1 id="Author">Author</h1>
<div id="Author_CONTENT">
<p>Henry Van Styn <a href="email:vanstyn@cpan.org">vanstyn@cpan.org</a></p>
</div>
</div>Catalystcatalyst@lists.scsys.co.ukhttp://www.catalystframework.org/calendar/2014/172014-12-15T20:39:01Z2014-12-15T20:39:01Z