Experience of Stepping Through Reaction 0.2 Tutorial

Overview

Reaction developers have recently beefed up their documentation including a tutorial. This papers focuses on the process and experience of going through the Reaction tutorial. It attempts to duplicate the tutorial with personal anecdotes.

Build Some Skeletal Structure

Catalyst App

Since Catalyst is underneath Reaction we build the Catalyst skeleton as usual1.

catalyst.pl MyAct

Reaction Supplemental Directories

Reaction requires additional directories and we can stub those out now:

Model

Schema - Domain Model
mkdir lib/MyAct/Schema
Interface Model
mkdir lib/MyAct/InterfaceModel

View

Layout
cd MyAct/
mkdir -p share/skin/myact/layout 
Widget
mkdir -p lib/MyAct/View/Site/Widget

View

Reaction decomposes views into more parts that traditional Catalyst applications:

  • templates
  • layouts
  • widgets

?? explain more.

Site (default view)

In order to do this, we need a custom site view, lib/MyAct/View/Site.pm:

package MyAct::View::Site;
use Reaction::Class;

use namespace::clean -except => 'meta';

extends 'Reaction::UI::View::TT';

__PACKAGE__->meta->make_immutable;

1;

The use Reaction::Class statement performs some Reaction specific setup (what exactly??). It also imports Moose, strict and warnings. Functions imported above the namespace::clean line are kept from being methods with clean::namespace, but remain available as functions. The meta method exported by Moose is the only exception (why specificially??). We keep it in the namespace. finally make_immutable is called on the meta class instance to inline methods for increasing performance of execution.

Root Controller

Just like a regular Catalyst application, we have a root controller to represent the root namespace for the application. The main parts of the Root controller are:

config

This is where we set

  • the view name for controller which is ‘Site’ corresponding to the view we just created.
  • title of the Reaction::UI::Window instance that is stored in $ctx->stash-{window} by the begin action provided by the Root controller
  • an empty namespace anchors the controller to /

sub base

This actions a a point from which all other actions can chain off of. It pushes the Reaction::UI::ViewPort::SiteLayout viewport onto the focus stack. Two other arguments are:

  • title - page title
  • and the static base uri (for css, javascript etc.)

One could also explicitly pass a layout, but in this case a default based on the SiteLayout viewport, site_layout, is implied.

sub root

The root action chains off the base and takes a layout named ‘root’.

Code

The code for the Root controller is such:

  package MyAct::Controller::Root;

  use parent 'Reaction::UI::Controller::Root';
  
  use aliased 'Reaction::UI::ViewPort';
  use aliased 'Reaction::UI::ViewPort::SiteLayout';
  
  use namespace::clean -except => 'meta';
  
  __PACKAGE__->config(
      view_name       => 'Site',
      window_title    => 'MyApp Window',
      namespace       => '',
  );
  
  sub base: Chained('/') PathPart('') CaptureArgs(0) {
      my ($self$ctx) = @_;
      $self->push_viewport(SiteLayout,
          title           => 'MyApp Test Title',
          static_base_uri => join(''$ctx->uri_for('/static')),
          meta_info       => {
              http_header   => {
                  'Content-Type' => 'text/html;charset=utf-8',
              },
          },
      );
  }
  
  sub root: Chained('base') PathPart('') Args(0) {
      my ($self$ctx) = @_;
      $self->push_viewport(ViewPort, layout => 'root');
  }
  
  1;

NOTE: As of Catalyst 5.8, strict and warnings are imported automatically since Moose is under the hood. Thus they do not need to be explicitly stated in the Controller. If you are using Catalyst before then you should add the lines to the controller.

Reaction will look for layout files in share/skin/$app_name/layout/* where $app_name is ‘myact’ in our case. Therefore, we now to create a new skin and the layout files.

Get Some Skin

skin.conf

We need to configure a new skin. This is done in share/skin/myact/skin.conf The most basic form is:

extends /Reaction/default

This is using the Reaction bundle default template as a base. Using something like:

extends foo

would extend a skin named share/skin/foo

default.conf

Application wide settings for all skins are placed in share/skin/defaults.conf Specifically:

widget_search_path MyAct::View::Site::Widget
widget_search_path Reaction::UI::Widget

These two lines inform Reaction to look for widgets classes in our application or Reaction’s own widgets. Therefore are layout named root will check for MyAct::View::Site::Widget::Root first then look for Reaction::UI::Widget::Root if the former does not exist. (?? Is this correct ?? where it looks until it finds??)

Tell Reaction about the Skin

This is done by adding to myact.conf


    skin_name myact

The value, myact in this example, is the target directory under share/skin.

Layouts

We start with two layouts:

  • one for the site layout
  • one for the root action

share/skin/myact/layout/site_layout.tt

=extends NEXT

=for layout body

  

Welcome to My Act

[% inner %]
=cut

=extends NEXT directive


The =extends directive specifies that this layout file is an extension of another layout file (I find the NEXT thing strange). In effect we are extending the Reaction default located at share/default/layout/site_layout.tt in the Reaction distribution which looks like:

=extends NEXT

=for layout widget

[% doctype %]




  [% head %]



  [% body %]




=cut

=for layout directive

This directive allows us to set a layout fragment. Here we define a body fragement. containing the common body for all pages uising this site layout. The inner is where deeper parts of the stack are included. In our root action casection what would be the Reaction::UI::ViewPort with the root layout.

share/skin/myact/layout/root.tt

This is the layout representing the root action and contains:

=for layout widget

  

Hola Mon!

=cut

h4 =for layout widget

This directive is special in that it’s where the rendering starts. The site_layout.tt we inherited from has one of these as well.

Widgets

Widget are the last point, in the direction of the view, where we have Perl code. We start are widget exploration by constructing a minimal root widget.

lib/MyAct/View/Site/Widget/Root.pm

  package MyAct::View::Site::Widget::Root;
  use Reaction::UI::WidgetClass;
  
  use namespace::autoclean;
  
  __PACKAGE__->meta->make_immutable;
  
  1;

Schema

As with traditional Catalyst applications, it’s common to abstract the database schema with DBIx::Class::Schema into its own model.

Schema Class

We’ll start by defining a schema class at lib/MyAct/Schema.pm:

package MyAct::Schema;
use strict;
use warnings;

use parent 'DBIx::Class::Schema';

__PACKAGE__->load_classes;

1;

Example Databae

The tutorial uses a single table SQLite database for it’s example. Let’s create that now:

  $ cat > myact.sqlite.sql
  CREATE TABLE people (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    first_name  VARCHAR NOT NULL,
    last_name   VARCHAR NOT NULL
  );
  <Ctrl-D>

  $ sqlite3 myact.sqlite < myact.sqlite.sql

Result Classes

Reaction starts with the same type of result classes, but then requires one to add Moose type attributes which will be utilized for the reflection (introspection).

package MyAct::Schema::Person;
use Moose;
use MooseX::Types::Moose qw( Int );
use Reaction::Types::Core qw( NonEmptySimpleStr );

use namespace::autoclean;

extends 'DBIx::Class';

has id => 
  (is => 'ro', isa => Int, required => 1);
  
has first_name =>
  (is => 'rw', isa => NonEmptySimpleStr, required => 1);

has last_name => 
  (is => 'rw', isa => NonEmptySimpleStr, required => 1);

__PACKAGE__->load_components(qw( IntrospectableM2M Core ));
__PACKAGE__->table('foo');
  
__PACKAGE__->add_columns(
    id => { 
        data_type         => 'integer', 
        is_auto_increment => 1,
    },
    first_name => { data_type => 'varchar' },
    last_name  => { data_type => 'varchar' },
);
  
__PACKAGE__->set_primary_key('id');
  
1;

Types

The isa attribute parameter is useful for determining what type of form element an attribute corresponds to:

Interface Model

We’ve seen that Reaction decomposes the view into widgets, skins and layouts. Now we’ll look at another area where Reaction becomes more detail oriented about the model. In particular the interface between the schema and the application. In this example, we’ll create an interface model to glue CRUD from our schema to our app using the Reaction Reflector class for DBIC. We will put it at lib/MyAct/InterfaceModel/DBIC.pm:

package MyAct::InterfaceModel::DBIC;

# keep this on top
use parent 'Reaction::InterfaceModel::Object';

use Reaction::Class;
use Reaction::InterfaceModel::Reflector::DBIC;
use namespace::autoclean;

my $reflector = Reaction::InterfaceModel::Reflector::DBIC->new;

$reflector->reflect_schema(
  model_class  => __PACKAGE__,
  schema_class => 'MyAct::Schema',
);

__PACKAGE__->meta->make_immutable;
  
1;

Enlighten the Application about the Interface

Here we create a Catalyst model at lib/MyAct/Model/DBIC.pm:

package MyAct::Model::DBIC;

use Reaction::Class;
use namespace::autoclean;
extends 'Catalyst::Model::Reaction::InterfaceModel::DBIC';

__PACKAGE__->meta->make_immutable;

__PACKAGE__->config(
  im_class => 'MyAct::InterfaceModel::DBIC',
  db_dsn   => 'dbi:SQLite:myact.sqlite',
);

1;

CRUD Control

Now that we have our domain and interface models set up we can create a CRUD controller with ease:

package MyAct::Controller::Person;

use parent 'Reaction::UI::Controller::Collection::CRUD';
use Reaction::Class;

use namespace::autoclean;

__PACKAGE__->config(
  model_name => 'DBIC',
  collection_name => 'Person',
  actions => {
    base => { Chained => '/base', PathPart => 'person' },
  },
);

1;

Footnotes

1 Only difference here, for variety sake, is that I choose MyAct instead of MyApp as the project name.

My tags:
 
Popular tags:
 
Powered by MojoMojo