Daniel Ruoso

Últimos Posts

Categorias

Arquivo

Nuvem de Tags

18.08.2009  Transactions and Authorization made simple

So I really like to follow DRY: Don't Repeat Yourself. In the development of Epitafio (A cemetery management system I mentioned earlier), I was workin on my model classes - note that this is not a DBIC model, but a regular model that do access a DBIC schema - and I realized that for every single method of the models I would need to do two things:

  • Enclose code in a transaction, much like:
    $schema->txn_do(sub { ... })
  • Authorize the user against a specific role:
    die 'Access denied!' unless $user->in_role('foo')

So I started wondering at #catalyst if there would be a pretty way of doing it. I was already using Catalyst::Component::InstancePerContext, but mst quickly guided me to avoid saving the context itself in the object, but rather getting the values I need from there. Since my app models will basically follow this same principle I did a model superclass with:

package Epitafio::Model;
use Moose;
with 'Catalyst::Component::InstancePerContext';
has 'user' => (is => 'rw');
has 'dbic' => (is => 'rw');

sub build_per_context_instance {
  my ($self, $c) = @_;
  $self->new(user => $c->user->obj,
             dbic => $c->model('DB')->schema->restrict_with_object($c->user->obj));
}
1;

Note that I'm still using the C::M::DBIC::Schema as usual, but I'm additionally making a local dbic schema that is restricted according with the logged user. Check DBIx::Class::Schema::RestrictWithObject for details on how that works, and mst++ for the tip.

Ok, now my model classes can know which user is logged in (in a Cat-independent way) as well as have access to the main DBIC::Schema used in the application. Now we just need to DRO - Don't Repeat Ourselves.

Following, again, mst++ tip, I decided against doing a more fancy solution and gone to a plain and simple:

txn_method 'foo' => authorize 'rolename' => sub {
   ...
}

For those who didn't get how that is parsed, this could be rewritten as:

txn_method('foo',authorize('rolename',sub { }))

This works as:

  • authorize receives a role name and a code ref and returns a code ref that does the user role checking before invoking the actual code.
  • txn_method receives the method name and a code ref and installs a new coderef that encloses the given coderef into a transcation in the package namespace as if it were a regular sub definition.

That means you can have a txn_method without authorization, but you would require

our &foo = authorize 'rolename' => sub { ... }

to get authorization without transaction. But as in my application I'll probably have both most of the time, I thought it should suffice the way it is.

But for the txn_method..authorize thing to parse, both subs need to be in the package namespace at BEGIN time, so to solve that, without having to re-type it every time, I wrote a simple Epitafio::ModelUtil module that exports this helpers.

package Epitafio::ModelUtil;
use strict;
use warnings;
use base 'Exporter';

our @EXPORT = qw(txn_method authorized);

sub txn_method {
  my ($name, $code) = @_;
  my $method_name = caller().'::'.$name;
  no strict 'refs';
  *{$method_name} = sub {
    $_[0]->dbic->txn_do($code, @_)
  };
}

sub authorized {
  my ($role, $code) = @_;
  return sub {
    if ($_[0]->user->in_role($role)) {
      $code->(@_);
    } else {
      die 'Access Denied!';
    }
  }
}

1;

And now the code of the model looks just pretty and non-repetitive ;). See the sources for the full version.

Postado por autor: ruoso em Perl.   Tags  epitafioPerlperl5catalyst.

30.07.2009  Epitáfio

The Perl monks from Brazil accepted a challenge with an important social relevance. We are working, on our spare time, in the development of a system to manage the public cemeteries. Few people know, but the public cemeteries take a fundamental role regarding the respect for human rights, as well as for public health, giving the population that doesn't have the resources to pay for a tombstone in a private cemetery a memorial service and a decent burial.

At this moment we already stablished the features for the first release, and the deadline expectation is that we have the system working in the cemetery for the "dia de Finados" (day in memory of the people who past away). Yesterday we finished the first proposal for the data model (the software will be developed in Portuguese, but I guess you can figure out the meaning of the words).

The discussions regarding the system happen in the #brasil-pm channel at irc.perl.org, we have a space in the perl.org.br wiki to document the development process and a github space to host the source code.

The system will have a Web interface and is going to use PostgreSQL, specially because of the timestamp-related features and also for possibly using PostGIS, allowing to store the spatial information about the tombstones and the cemetery map.

As the development follows, I'll post the updates here.

Postado por autor: ruoso em Perl.   Tags  epitafioPerldireitos humanosperl5.