A Simple Plack/DBI Example
In A Simple Mojolicious/DBI Example Joel Berger demonstrates how to build a very simple CRUD web app (well, a CR one anyway) using Mojolicious and DBI. I was impressed at how concise it was, and wondered how my preferred technology stack would compare.
I'm not a fan of template languages, preferring DOM manipulation. And rather than Mojo, I'm using Plack here. Anyway, this is what I came up with...
#!/usr/bin/env plackup
use v5.14;
use DBI;
use HTML::HTML5::Parser;
use HTML::HTML5::Writer;
use Plack::Request;
use Plack::Response;
# connect to database
my $dbh = 'DBI'->connect("dbi:SQLite:database.db","","") or die "Could not connect";
my ($insert, $select);
while (1) {
# create insert and select statements
$insert = eval { $dbh->prepare('INSERT INTO people VALUES (?,?)') };
$select = eval { $dbh->prepare('SELECT * FROM people') };
# break out of loop if statements prepared
last if $insert && $select;
# if statements didn't prepare, assume its because the table doesn't exist
warn "Creating table 'people'\n";
$dbh->do('CREATE TABLE people (name varchar(255), age int);');
}
my $template = 'HTML::HTML5::Parser'->load_html(IO => \*DATA);
my $writer = 'HTML::HTML5::Writer'->new(markup => 'html', polyglot => 1);
# the PSGI app itself
my $app = sub {
my $req = 'Plack::Request'->new(shift);
my $res = 'Plack::Response'->new(200);
if ($req->method eq 'POST') {
$insert->execute(map $req->parameters->{$_}, qw( name age ));
$res->redirect( $req->base );
}
else {
my $page = $template->cloneNode(1);
my $table = $page->getElementsByTagName('table')->get_node(1);
$select->execute;
while (my @row = $select->fetchrow_array) {
my $tr = $table->addNewChild($table->namespaceURI, 'tr');
$tr->appendTextChild(td => $_) for @row;
}
$res->body( $writer->document($page) );
}
$res->finalize;
};
__DATA__
<!DOCTYPE html>
<title>People</title>
<form action="insert" method="post">
Name: <input type="text" name="name">
Age: <input type="text" name="age">
<input type="submit" value="Add">
</form>
<br>
Data: <br>
<table border="1">
<tr>
<th>Name</th>
<th>Age</th>
</tr>
</table>
Why are you quoting your class names? Are you afraid they might also be function names?
An excellent response indeed. Thanks I'm glad to see how Plack would handle this.
In fact, I had planned a follow-up that would show how to do this without refreshing, via websockets. However, I was left thinking about how I would append to the table. Instead I think I will mimic your example and append from the beginning.
Cheers!
Precisely. Disambiguation costs nothing.
For disambiguation I generally do:
DBI::->connect(...)
no indirect;
? (see here)That works too but has the side-effect of performing a compile-time check that the package exists. Which is sometimes desirable.
Indirect method call syntax is something different.
DateTime->new
versusnew DateTime
.I'm talking about:
Hmmmm, that's something to think about. I've never tripped over that one. I have had the indirect bite me a few times, so that is when I am careful do disambiguate. The times when I have a function which returns a valid class name usually are when I want to have some handler class, so I might have
has ua_class => 'LWP::UserAgent'
, then invokingmy $ua = $self->ua_class->new
. Yet another thing to worry about!I extended your comment into a small test. I used JSON instead of DateTime b/c it wanted a year.
which reminds me, in a recent conversation with a $cow-orker, I realized it could be interesting to have a module that allows python devs to be able to use the object construction syntax they're used to, something like Acme::Constructor::Pythonic, which would import package names as functions that simply call new(@_) on the corresponding class...
Or it would be totally idiotic :)
aliased almost allows this.
Here's quick stab at implementing Acme::Constructor::Pythonic.
"it wanted a year"... I initially read that as a comment about how long it took to install DateTime. I thought you were being a little unfair... DateTime maths is hard.
Haha! Yes, DateTime wants a year. JSON is a better example.
tobyink++! Nice implementation! I wouldn't have thought to use 'no thanks'. That's clever!
tobyink++ really nice ;)
So if table cannot be created (lack of free space) - it will loop forever?
Why eval{} if you don't use RaiseError ?
Not forever. Until somebody provides some free space. :-)