Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
711 lines (636 sloc) 21.9 KB
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Catalyst tricks</title>
<!-- metadata -->
<meta name="version" content="S5 1.1" />
<meta name="presdate" content="20071105" />
<meta name="author" content="Jozef Kutej"/>
<meta name="company" content="Soiton, a.s." />
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<!-- configuration parameters -->
<meta name="defaultView" content="slideshow" />
<!-- <meta name="defaultView" content="outline" /> -->
<meta name="controlVis" content="hidden" />
<!-- style sheet links -->
<link rel="stylesheet" href="ui/default/slides.css" type="text/css" media="projection" id="slideProj" />
<link rel="stylesheet" href="ui/default/outline.css" type="text/css" media="screen" id="outlineStyle" />
<link rel="stylesheet" href="ui/default/print.css" type="text/css" media="print" id="slidePrint" />
<link rel="stylesheet" href="ui/default/opera.css" type="text/css" media="projection" id="operaFix" />
<!-- S5 JS -->
<script src="ui/default/slides.js" type="text/javascript"></script>
<!-- syntax highlighting -->
<link rel="stylesheet" type="text/css" href="ui/sh/sh_style.css" />
<script type="text/javascript" src="ui/sh/sh_main.js"></script>
<script type="text/javascript" src="ui/sh/sh_perl.js"></script>
<script type="text/javascript" src="ui/sh/sh_sql.js"></script>
</head>
<body><!-- onload='sh_highlightDocument();' -->
<div class="layout">
<div id="controls"><!-- DO NOT EDIT --></div>
<div id="currentSlide"><!-- DO NOT EDIT --></div>
<div id="header"></div>
<div id="footer">
<!-- added by Jozef :) -->
<div id="counter" style="float: right; margin-right: 10px;">60</div>
<h1>05 Nov 2007</h1>
<h2>Catalyst tricks</h2>
</div>
</div>
<div class="presentation">
<div class="slide">
<h1>Catalyst tricks</h1>
<h3>Jozef</h3>
<p><img src='catalyst-tricks/catalyst.jpg'/></p>
</div>
<div class="slide">
<h1>Short intro to Catalyst</h1>
<p style="text-align: center; float: right;"><img src="catalyst-tricks/catalyst_130pix.gif" alt="" title="Catalyst"></p>
<ul>
<li><a href='http://www.catalystframework.org/'>Catalyst = MVC web framework</a></li>
<li>in Catalyst everything = $c</li>
<li><pre>$c->model() - use base 'Catalyst::Model';</pre></li>
<li><pre>$c->view() - use base 'Catalyst::View';</pre></li>
<li><pre>$c->controller() - use base 'Catalyst::Controller';</pre></li>
<li>elegant way to have a tool organized</li>
</ul>
</div>
<div class="slide" style="background: url('catalyst-tricks/catalyst.png') right top no-repeat;">
<h1>web request flowchart</h1>
<p style="text-align: center;"><img src="catalyst-tricks/web_request_flowchart.png" alt="" title="web flow"></p>
<!--
<ol>
<li>request comes to the $c->controller('Root')->auto()</li>
<li>according to the url request come "http://my.site/the/right" controller<br/>
$c->controller('The')->auto()
</li>
<li>and the function is called $c->controller('The')->right()<br/>
this function decides what view and template will be used</li>
<li>view is called to render the page</li>
</ol>
-->
</div>
<div class="slide">
<h1>$c methods</h1>
<h2>build-in</h2>
<ul>
<li>$c->config->{} - hash ref of configuration variables</li>
<li>$c->stash->{} - hash ref of temporary (per request) variables</li>
<li>$c->uri_for('/') - get http url for catalyst uri</li>
<li>$c->log->debug - logging function</li>
<li>...</li>
</ul>
</div>
<div class="slide">
<h1>$c methods</h1>
<h2>others come from plugins</h2>
<ul>
<li>$c->session - hash of session variables<br/>
$c->flash - hash of temporary session variables<br/>
<a href='http://search.cpan.org/perldoc?Catalyst::Plugin::Session'>Catalyst::Plugin::Session</a></li>
<li>$c->authenticate<br/>
<a href='http://search.cpan.org/perldoc?Catalyst::Plugin::Authentication'>Catalyst::Plugin::Authentication</a></li>
<li>$c->require_ssl<br/>
<a href='http://search.cpan.org/perldoc?Catalyst::Plugin::RequireSSL'>Catalyst::Plugin::RequireSSL</a></li>
<li>and much more...<br/>
see <a href='http://search.cpan.org/perldoc?Catalyst::Manual::Plugins'>Catalyst::Manual::Plugins</a></li>
</ul>
</div>
<div class="slide" style="background: url('catalyst-tricks/view.jpg') right top no-repeat;">
<h1>What is a $c->view()?</h1>
<pre class='sh_perl'>
package ViennaPM1::View::Dump;
use base 'Catalyst::View';
use File::Slurp;
sub render {
my ($self, $c, $dump_name) = @_;
$dump_name = $c->config->{'View::Dump'}->{'path'}.'/'.$dump_name;
return read_file($c->path_to($dump_name).'');
}
sub process {
my ($self, $c) = @_;
$c->response->content_type('text/plain');
$c->response->body(
$self->render($c, $c->stash->{'template'})
);
return 1;
}
1;
</pre>
</div>
<div class="slide" style="background: url('catalyst-tricks/model.jpg') right top no-repeat;">
<h1>What is a $c->model()?</h1>
<pre class='sh_perl'>
package ViennaPM1::Model::Constants;
use base 'Catalyst::Model';
my %constants = ( 'p' => 'perl', 'm' => 'mongers' );
sub named {
my ($self, $name) = @_;
die 'no such constatnt' if not exists $constants{$name};
return $constants{$name};
}
sub names {
return join ', ', keys %constants;
}
1;
</pre>
</div>
<div class="slide" style="background: url('catalyst-tricks/controller.jpg') right top no-repeat;">
<h1>What is a $c->controller()?</h1>
<pre class='sh_perl'>
package ViennaPM1::Controller::Root;
use base 'Catalyst::Controller';
__PACKAGE__->config->{'namespace'} = '';
sub index : Private {
my ( $self, $c ) = @_;
$c->stash->{'message'} =
$c->model('Constants')->names.', generated at: '.$c->time_now;
$c->stash->{'template'} = 'index.tt2';
# default view is called to handle the template
}
sub dump : Local {
my ( $self, $c, $name ) = @_;
$c->stash->{'template'} = $name;
$c->forward('View::Dump');
}
1;
</pre>
</div>
<div class="slide">
<h1>What is a plugin after all?</h1>
<ul>
<li>plugin can execute stuff at setup time</li>
<li>plugin can add methods to the $c</li>
<li>and this is why everything in Catalyst is $c</li>
<li>let's have an example</li>
</ul>
</div>
<div class="slide" style="background: url('catalyst-tricks/plugin.jpg') right top no-repeat;">
<h1>Plugin</h1>
<pre class='sh_perl'>
package Catalyst::Plugin::TimeNow;
use POSIX 'strftime';
sub setup {
my $c = shift;
$c->log->debug('Now we have: '.time_now($c));
$c->NEXT::setup( @_ );
}
sub time_now {
my $c = shift;
my $time_format = $c->config->{'TimeNow'}->{'format'} || '%a %b %e %H:%M:%S %Y';
return strftime $time_format, localtime;
}
1;
</pre>
</div>
<div class="slide" style="background: url('catalyst-tricks/glue.jpg') right top no-repeat;">
<h1>the Glue</h1>
<pre class='sh_perl'>
package ViennaPM1;
use Catalyst::Runtime '5.70';
my @plugins;
BEGIN {
push(@plugins, '-Debug') if $ENV{'IN_DEBUG_MODE'};
push(@plugins, qw(
ConfigLoader
DefaultEnd
Static::Simple
TimeNow
));
}
use Catalyst @plugins;
__PACKAGE__->setup;
1;
</pre>
</ul>
</div>
<div class="slide" style="background: url('catalyst-tricks/tree.png') right top no-repeat;">
<h1>Catalyst folder tree</h1>
<p style="text-align: center;"><img src="catalyst-tricks/catalyst-folder-tree.png" alt="" title=""></p>
</div>
<div class="slide" style="background: url('catalyst-tricks/tree2.jpg') right top no-repeat;">
<h1>Catalyst customized folder tree</h1>
<p style="text-align: center;"><img src="catalyst-tricks/catalyst-folder-tree2.png" alt="" title="Tree2"></p>
</div>
<div class="slide">
<h1>Finally</h1>
<h2>to display our pages</h2>
<ul>
<li><a href='http://localhost:3000'>dedicated http server</a></li>
<li>cgi/fast cgi</li>
<li>mod_perl</li>
</ul>
</div>
<div class="slide">
<h1>DBIx::Class</h1>
<ul>
<li>Do you use it? "it" => ORM</li>
<li>$c->model('DBIC::User')->find()<br />
$schema->resultset('user')->find()
</li>
<li><b>Metaprogramming</b> is the writing of computer programs
that write or manipulate other programs (or themselves) as their
data or that do part of the work during compile time that is otherwise
done at run time. In many cases, this allows programmers to get more
done in the same amount of time as they would take to write all the
code manually. (<a href='http://en.wikipedia.org/wiki/Meta_programming'>Wikipedia</a>)
</li>
<li>YAPC 2007 Yuval Kogman (‎nothingmuch‎) - Object Meta Programming</li>
<li>DBIx::Class, Moose, Class::Accessor, ...</li>
</ul>
</div>
<div class="slide">
<h1>DBIx::Class example</h1>
<pre class='sh_perl'>
package ViennaPM1::DBIC::Monger;
use base 'DBIx::Class';
__PACKAGE__->load_components(qw{PK::Auto ResultSetManager Core});
__PACKAGE__->table('mongers');
__PACKAGE__->add_columns(qw{
monger_id
name
cpan_id
status
life_style_id
});
__PACKAGE__->set_primary_key('monger_id');
__PACKAGE__->sequence('mongers_monger_id_seq');
__PACKAGE__->might_have( 'personal_life' => 'ViennaPM1::PersonalLife', 'monger_id');
__PACKAGE__->has_many ( 'presentations' => 'ViennaPM1::Presentation', 'monger_id');
__PACKAGE__->belongs_to( 'life_style' => 'ViennaPM1::LifeStyle', 'life_style_id');
__PACKAGE__->add_unique_constraint(
uniq_cpan_id => [ qw{cpan_id} ]
);
# AND CONTINUES ...
</pre>
</div>
<div class="slide">
<h1>DBIx::Class example</h1>
<pre class='sh_perl'>
sub with_cpan_id : ResultSet {
my ($self, $cpan_id) = @_;
return = $self->find({
cpan_id => $cpan_id
}, { key => 'uniq_cpan_id'});
}
sub promote_to_guru {
my $self = shift;
$self->status('GURU');
$self->update;
}
sub is_active {
my $self = shift;
return 1 if $self->status ne 'DISABLED';
return 0;
}
1;
</pre>
</div>
<div class="slide">
<h1>What's so great?</h1>
<h2>For each table</h2>
<ul>
<li>we have an object for every row in the table<br/>
<pre class='sh_perl'>my $monger = $c->model('DBIC::Monger')->with_cpan_id('domm');</pre>
</li>
<li>relations between tables are easily and clearly defined<br/>
<pre class='sh_perl'>print $monger->life_style->name;</pre>
</li>
<li>common select cases predefined</li>
<li>common checks/modifications predefined<br/>
<pre class='sh_perl'>$monger->promote_to_guru;</pre>
<pre class='sh_perl'>$monger->delete if not $monger->is_active;</pre>
</li>
<li>and everything can be commented in a <a href='catalyst-tricks/examples/ViennaPM1/tmp/Monger.pod.html'>pod</a></li>
</ul>
</div>
<div class="slide" style="background: url('catalyst-tricks/trigger.jpg') right top no-repeat;">
<h1>Let's go even further away from Catalyst</h1>
<h2>pull the trigger, feel the click,<br/>
no further danger</h2>
<ul>
<li>foreign references
<pre class='sh_perl'>$c->model('DBIC::Monger')->with_cpan_id('domm')->delete;</pre>
will remove also records of his life style and personal life! :)
</li>
<li>pl/SQL is not that complex</li>
<li>pl/Perl in extreme cases (thank you PostgreSQL)</li>
<li>this will keep the job of checking data integrity
inside the database,
no matter who or how will access it<br/></li>
</ul>
</div>
<div class="slide" style="background: url('catalyst-tricks/summary.png') right top no-repeat;">
<h1>Little summary</h1>
<h2>what we have:</h2>
<ul>
<li>data integrity guarded by database</li>
<li>data manipulation using well defined objects</li>
<li>url dispatch, control, data preparing by $c->controller</li>
<li>response contructed by $c->view<br/>
some templating system (Template Toolkit?)
</li>
</ul>
<h2>we have the application/tool design before we even started :)</h2>
</div>
<div class="slide">
<h1>Time for some real tricks</h1>
<p style="text-align: center;"><img src="catalyst-tricks/real_tricks.jpg" alt="" title=":)"></p>
</div>
<div class="slide" style="background: url('catalyst-tricks/closed.png') right top no-repeat;">
<h1>Replace 'please come back later'</h1>
<ul>
<li>in debug mode the error <a href='http://localhost:3000/dump/123'>"output"</a> is fine, but <a href='http://localhost:3001/dump/123'>please come back later</a> ;)</li>
<li>the trick is to have $c->finalize_error</li>
<li>sub finalize_error {} in the ViennaPM1.pm</li>
<li>plugin that inherits finalize_error function</li>
</ul>
</div>
<div class="slide" style="background: url('catalyst-tricks/error.jpg') right top no-repeat;">
<h1>two types of errors</h1>
<ul>
<li>what we want is to have our template header.tt + footer.tt + site.css look also for errors</li>
<li>with code/syntax errors we can call $c->view('TT')->render( ... </li>
<li>for unhadled actions like wrong url we can do<br/>
$c->response->redirect($c->uri_for('/'))<br/>
having flash variable set</li>
<li><a href='http://search.cpan.org/perldoc?Catalyst::Plugin::CustomErrorMessage'>Catalyst::Plugin::CustomErrorMessage</a></li>
</ul>
</div>
<div class="slide">
<h1>$c for scripts</h1>
<h2>access to $c->controller()</h2>
<ul>
<li>we want to write
script that will notify users about their account
expiration, some actions that they need to take,
or just start some daily data processing</li>
<li>we want to launch some action triggered by email, same
as from web.</li>
<li>we use $c->controller('The')->right($c, )</li>
</ul>
<h2>access to $c->model()</h2>
<ul>
<li>same db login config for webapp and scripts</li>
</ul>
</div>
<div class="slide">
<h1>$c for scripts</h1>
<h2>example</h2>
<ul>
<li>$c->controller('Email')->send($c, {}) for sending emails</li>
<li>it will use $c->view('EmailTT') to construct the html email body</li>
<li>we can use it for both web part and the script part</li>
<li>it will use the same code</li>
</ul>
<h2>how?</h2>
<ul>
<li>with a plugin :)</li>
<li><a href='http://search.cpan.org/perldoc?Catalyst::Plugin::CommandLine'>Catalyst::Plugin::CommandLine</a></li>
</ul>
</div>
<div class="slide" style="background: url('catalyst-tricks/certificate.jpg') right top no-repeat;">
<h1>authentication with Client Certificates</h1>
<h2>Why?</h2>
<ul>
<li>because i HATE passwords :)</li>
<li>no (stupid?) questions needed</li>
<li>more secure than passwords</li>
</ul>
<h2>Why not?</h2>
<ul>
<li>there is no OCSP for standard apache<br/>
(Online Certificate Status Protocol)</li>
<li>there is no OCSP in CPAN (!!!) neither</li>
</ul>
</div>
<div class="slide" style="background: url('catalyst-tricks/certificate.jpg') right top no-repeat;">
<h1>authentication with Client Certificates</h1>
<h2>httpd.conf</h2>
<pre>
SSLCertificateKeyFile (...)/ssl/Server.key
SSLCACertificateFile (...)/ssl/SomeCertificationAuthority.crt
SSLCARevocationFile (...)/ssl/LatestCRL.pem.crl
&lt;Location /login&gt;
SSLVerifyClient optional
SSLVerifyDepth 1
SSLOptions +StdEnvVars
# PerlOptions +SetupEnv
&lt;/Location&gt;</pre>
<p>with "+SetupEnv" catalyst is confused</p>
</div>
<div class="slide" style="background: url('catalyst-tricks/certificate.jpg') right top no-repeat;">
<h1>authentication with Client Certificates</h1>
<h2>$c->controller('Root')->login()</h2>
<pre class='sh_perl'>
# need this "hack" to populate env with SSL
# could be done with "PerlOptions +SetupEnv" httpd.conf option but
# that will confuse catalyst and $c->uri_for() will not work properly
$c->apache->subprocess_env if can $c, 'apache';
if ((exists $ENV{'SSL_CLIENT_VERIFY'})
and ($ENV{'SSL_CLIENT_VERIFY'} eq 'SUCCESS')) {
$c->stash->{'cert_email'} = $ENV{'SSL_CLIENT_S_DN_Email'};
$c->stash->{'cert_name'} = $ENV{'SSL_CLIENT_S_DN_CN'};
my $autologin_user =
$c->default_auth_store->get_user($c->stash->{'cert_email'});
$c->set_authenticated($autologin_user) if ($autologin_user);
}
</pre>
</div>
<div class="slide" style="background: url('catalyst-tricks/debian.png') right top no-repeat;">
<h1>Catalyst session bug in Debian Etch</h1>
<ul>
<li>if there is no data in the $c->session hash ref<br/>
the next time you load a page the session expires</li>
<li>so there is chance 1:2 that you will loose the<br/>
next session data as they are written to expired session</li>
<li>workaround is to always set something to $c->session</li>
<li>in $c->controller('root')->auto()</li>
<li>
<pre class='sh_perl'>#workaround for buggy Catalyst::Plugin::Session that expires session cookie once it has no data
$c-&gt;session-&gt;{'Catalyst::Plugin::Session'} = 'bug';</pre></li>
</ul>
</div>
<div class="slide" style="background: url('catalyst-tricks/nuke.jpg') right top no-repeat;">
<h1>Temporary down</h1>
<h2>httpd.conf</h2>
<pre>
Alias /static (...)/root/static
&lt;Location /&gt;
SetHandler modperl
PerlResponseHandler ViennaPM1
&lt;/Location&gt;
&lt;Location /static&gt;
SetHandler default-handler
&lt;/Location&gt;
# RedirectMatch "^/(?!static/)" /static/temp-down-for-maintenance.html
</pre>
</div>
<div class="slide" style="background: url('catalyst-tricks/post_box.jpg') right top no-repeat;">
<h1>$c->controller('Email')</h1>
<h2>methods</h2>
<ul>
<li>sub list : Local {<br/>
table with "Send Time, <i>Created by</i>, To, Cc, Bcc, Subject"</li>
<li>sub show : Local {<br/>
show's the exact email
</li>
<li>sub show_template : Local {<br/>
show's email template</li>
<li>sub send : Private {<br/>
private method to send email</li>
<li>sub resend : Local {<br/>
send previous email again</li>
</ul>
</div>
<div class="slide" style="background: url('catalyst-tricks/evolution1.png') right top no-repeat;">
<h1>db history</h1>
<h2>session_info table</h2>
<pre class='sh_perl'>
__PACKAGE__->config(
schema_class => 'ViennaPM1::DBIC',
connect_info => [
$config->{'db'}->{'dbi_dsn'},
$config->{'db'}->{'username'},
$config->{'db'}->{'password'},
{
AutoCommit => 1,
on_connect_do => [
'CREATE TEMPORARY TABLE session_info (modified_by text, session_id text);',
"INSERT INTO session_info (modified_by, session_id) VALUES ('".basename($0)."', '".$PID."');",
],
},
],
);</pre>
</div>
<div class="slide" style="background: url('catalyst-tricks/evolution2.png') right top no-repeat;">
<h1>session_info up-to-date</h1>
<h2>$c->controller('Root')->auto</h2>
<pre class='sh_perl'>
if ($c->user_exists) {
$c->model('DBIC::Session_Info')->modified_by($c->session->{'person'}->{'employeeNumber'});
}
else {
$c->model('DBIC::Session_Info')->modified_by(undef);
}
$c->model('DBIC::Session_Info')->session_id($c->sessionid);
</pre>
</div>
<div class="slide" style="background: url('catalyst-tricks/evolution3.png') right top no-repeat;">
<h1>history_emails_trigger()</h1>
<pre class='sh_sql'>
CREATE OR REPLACE FUNCTION history_emails_trigger() RETURNS TRIGGER AS $$
DECLARE
v_modified_by history_emails.created_by%TYPE;
BEGIN
SELECT modified_by
INTO v_modified_by
FROM session_info;
new.created_by := v_modified_by;
RETURN new;
END;
$$ LANGUAGE plpgsql;
</pre>
</div>
<div class="slide" style="background: url('catalyst-tricks/evolution4.png') right top no-repeat;">
<h1>CREATE TABLE history</h1>
<h2>column changes history</h2>
<pre class='sh_sql'>
CREATE TABLE history (
history_id SERIAL,
session_id VARCHAR NOT NULL,
modified_by VARCHAR,
table_name VARCHAR NOT NULL,
event VARCHAR NOT NULL,
event_timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
comment VARCHAR NULL,
old_values VARCHAR NULL,
new_values VARCHAR NULL,
CHECK (event in ('INSERT', 'UPDATE', 'DELETE')),
CHECK (old_values IS NOT NULL OR new_values IS NOT NULL),
PRIMARY KEY (history_id)
);
</pre>
</div>
<div class="slide" style="background: url('catalyst-tricks/evolution5.png') right top no-repeat;">
<h1>history_trigger()</h1>
<h2>AFTER INSERT OR UPDATE ON<br/>BEFORE DELETE ON</h2>
<pre class='sh_perl'>
CREATE OR REPLACE FUNCTION history_trigger() RETURNS TRIGGER AS $$
#global variables
my $table_name = $_TD->{'relname'};
my $event = $_TD->{'event'};
# elog(NOTICE, $event.' '.$table_name);
my $session_info_query = spi_query(qq{
SELECT session_id, modified_by
FROM session_info;
});
my $session_info = spi_fetchrow($session_info_query);
elog(ERROR, "history_trigger: No session info row") if not defined $session_info;
my $session_id = (defined $session_info->{'session_id'}
? "'".$session_info->{'session_id'}."'" : 'NULL');
my $modified_by = (defined $session_info->{'modified_by'}
? "'".$session_info->{'modified_by'}."'" : 'NULL');
</pre>
</div>
<div class="slide" style="background: url('catalyst-tricks/evolution6.png') right top no-repeat;">
<h1>history_trigger() purpouse</h1>
<h2>what we have</h2>
<ul>
<li>%{$_TD->{'new'}}</li>
<li>%{$_TD->{'old'}}</li>
<li>$_TD->{'event'}</li>
</ul>
<h2>we can generate</h2>
<ul>
<li>$comment</li>
<li>$old_values</li>
<li>$new_values</li>
</ul>
</div>
<div class="slide" style="background: url('catalyst-tricks/evolution7.png') right top no-repeat;">
<h1>spi_exec_query()</h1>
<pre class='sh_perl'>
my $history_insert = spi_exec_query(qq{
INSERT INTO history (
session_id,
modified_by,
table_name,
event,
comment,
old_values,
new_values
)
VALUES (
$session_id,
$modified_by,
'$table_name',
'$event',
$comment,
$old_values,
$new_values
)
});
</pre>
</div>
<div class="slide" style="background: url('catalyst-tricks/evolution.png') right top no-repeat;">
<h1>benefits from history</h1>
<ul>
<li>we can show comments to users as history changes</li>
<li>we know who/what changed what</li>
<li>for paranoiacs owner of the triggers and tables can be<br/>
different than the application user so nobody will be able<br/>
to change thinks withou a "trace"</li>
<li>in case of a disaster we can recover from changes from<br/>
single source.</li>
</ul>
</div>
<div class="slide">
<h1>And that's it...</h1>
<h2>Questions?</h2>
<p style="text-align: center;"><img src="catalyst-tricks/questions.jpg" alt="" title=""></p>
</div>
</div>
</body>
</html>