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 thebeginaction 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 bodyWelcome 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 widgetHola 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:
NonEmptySimpleStrfrom Reaction::Types::Core is associated with <input … /> when used with forms.Strfrom MooseX::Types::Moose corresponds with a <textarea>…</textarea>.
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.
Showing changes from previous revision. Removed | Added
