Graphing Your Catalyst Application

Sometimes our Catalyst applications are so large, with such complex business rules, that we could get a little overwhelmed by their myriad components. Or maybe we concentrate so hard on one portion that we lose perspective on the big picture. Well, fear not!

The Perl world offers several visualization tools. In today's Advent Calendar, in the spirit of Dickens' Holiday masterpiece and MVC, we'll look closer into three of them, while using a standard MojoMojo installation to illustrate the results.

Ghost...erm...Graph of Model Past

The amazing SQL::Translator module can easily show you what your application's schema looks like directly from your database schema - be it MySQL, Oracle, PostreSQL, SQLite, or any of the several other database parsers available. Just specify it in the "from" field, while choosing "GraphViz" as destination.

  use SQL::Translator;

  my $translator = SQL::Translator->new(
          from => 'MySQL',
          to   => 'GraphViz',
  ) or die SQL::Translator->error;

  $translator->translate( 'MySchema.sql' );

If you are using Catalyst with the ever-popular DBIx::Class (DBIC) and would rather fetch database information from the schema modules, rejoice! Use SQL::Translator::Parser::DBIx::Class as the parser, and pass the loaded schema in the "parser_args" parameter. The code below shows this and gives the producer some customization via producer_args:

  use SQL::Translator;

  my $schema = MyApp::Schema->connect;
  my $translator = SQL::Translator->new(
      parser        => 'SQL::Translator::Parser::DBIx::Class',
      parser_args   => { package => $schema },
      producer      => 'Diagram',
      producer_args => {
          out_file       => 'schema.png',
          output_type    => 'png',
          title          => 'My Schema',
      },
  ) or die SQL::Translator->error;

  $translator->translate;

You can even go crazy and make a Catalyst action that generates the diagram of the current project's schema:

  sub schema : Local {
      my ( $self, $c ) = @_;

      my $translator = SQL::Translator->new(
          parser        => 'SQL::Translator::Parser::DBIx::Class',
          data          => $c->model('DB')->schema,
          producer      => 'Diagram',
          producer_args => {
              output_type => 'png',
              title       => 'MyApp Schema',
          },
      ) or die SQL::Translator->error;

      $c->res->content_type('image/png');
      $c->res->body( $translator->translate );

  }

The result? Well, see for yourself :)

MojoMojo's default schema

Graph of Controller Present

Maybe model data is not your (only) issue, and your application's controllers and actions grew so much they need more documentation, a developer's quick reference guide, or even some refactoring. While Catalyst's controller structure usually points a programmer toward good organization, there might be occasional bumps on the road.

Fortunately for us, Franck Cuny uploaded to CPAN a handy module called CatalystX::Dispatcher::AsGraph, which can turn your private actions into a nifty directed graph in just a few lines of code!

  use CatalystX::Dispatcher::AsGraph;

  my $graph = CatalystX::Dispatcher::AsGraph->new(
        appname => 'MyApp',
        output  => 'myactions.png',
  );
  $graph->run;

If the code above is successful, $graph now holds a Graph::Easy object storing the actions graph. We can use the dot external program to output the result as a png file:

  if ( open( my $png, '|-', 'dot -Tpng -o ' . $graph->output ) ) {
      print $png $graph->graph->as_graphviz;
      close $png;
  }

A demo program, bundled with the distribution, does exactly that but takes advantage of MooseX::GetOpt to let you specify the module's parameters as command-line options.

Note: As Khisanth pointed out, this module uses MooseX::Declare and has no "package" information, so the CPAN indexer won't index it, and the shell won't find it. Until this is fixed by the author, you'll need to install using the package's full path (e.g. install FRANCKC/CatalystX-Dispatcher-AsGraph-0.02.tar.gz), or fetch the tarball directly from the web.

MojoMojo's private actions

Graph of Template Future

Just like the schema and actions, you can view your entire template structure as a directed graph. The Template::AsGraph module can be easily invoked to generate such data from any template quite easily:

  use Template::AsGraph;

  my $graph = Template::AsGraph->graph('mytemplate.tt2');

The returned $graph is a Graph::Easy object, which you can turn into a png file just like you did with the Controller graph:

  if ( open( my $png, '|-', 'dot -Tpng -o templatechart.png ) ) {
      print $png $graph->graph->as_graphviz;
      close $png;
  }

If you need a way to understand how your templates fit together, it probably means their flow is so intricate that you dynamically load bits and pieces depending on the data passed in by the Controller. Don't worry: the graph method can also receive TT configurations as the second argument, and variables as the third:

  use Template::AsGraph;

  my %config = (
      INCLUDE_PATH => 'root/src/',
      START_TAG    => '<+',
      END_TAG      => '+>',
      PLUGIN_BASE  => 'MyApp::Template::Plugin',
      PRE_PROCESS  => 'header',
  );

  my %vars = (
       foo => 'bar',
       bar => 'baz', 
  );

Alternatively, if you have a Catalyst context object lying around, you can do just like View::TT:

  my %vars = ( %{ $c->stash() }, 
                  c    => $c, 
                  base => $c->req->base, 
                  name => $c->config->{name} 
             );

  my $graph = Template::AsGraph->graph('mytemplate.tt2', \%config, \%vars);

one of MojoMojo's templates

Conclusion

We hope you get a better understanding of your applications by generating and analyzing graphs of their schema, actions, and templates. Remember what you did right, review what could be better, and act upon it. This way, you are bound to become a better developer, which is the spirit of the season :)

Authors

Breno G. de Oliveira <garu@cpan.org>

Bogdan Lucaciu <bogdan@sinapticode.ro>