⬆️ ⬇️

Useful and pleasant for the developer in Mojolicious

About Mojolicious on Habré already several times wrote . The framework is successfully developing and, in my opinion, it becomes more convenient for rapid development every day.



Under the cat, I collected a few tricks to work with the framework, which seriously simplify life for me and, perhaps, will be useful for someone else.



over



Among the route methods in Mojolicious is the over () method. The method allows you to impose conditions on the route so that the client can get into the controller specified in the route only by satisfying the conditions. Powerman has already written about this. You can use the method, for example, like this:



AppName.pm file

package AppName; use Modern::Perl; use Mojo::Base 'Mojolicious'; use utf8; # This method will run once at server start sub startup { my $self = shift; $self->plugin('AppName::Helpers::Core'); #          my $r = $self->routes; #   #      #     user_id      isAdmin,   -  $r->add_condition( isAdmin => sub { my ($route, $c, $captures, $pattern) = @_; return 1 if $c->isAdmin($c->session->{'user_id'}); return undef; } ); #  GET- /users     users  sd   isAdmin -  $r->get('/users')->over(isAdmin => 1)->to('sd#users'); #      -      ,      -    404 $r->any('/(*everything')->to('user#main'); } 1; 


')

The code for the helper itself looks like this (I have few users, so I store the privileged ones right in the config file):

 package AppName::Helpers::Core; use base 'Mojolicious::Plugin'; use Modern::Perl; sub register { my ($self, $app) = @_; $app->helper( isAdmin => sub { #   ,        # invocation: # $whatever->isAdmin($user_login) # outputs: 1|2, undef my ($self, $user_login) = @_; return 1 if $user_login eq $user foreach (@{$self->config->{PrivilegedUsers}->{Administrators}}); return 2 if $user_login eq $manager foreach (@{$self->config->{PrivilegedUsers}->{Managers}}); return undef; } ); } 1; 




Mojolicious :: Plugin :: Authentication



This plugin seemed to me the most convenient way to implement user authentication due to the simplicity of its use. To use the plugin, in AppName.pm it is enough to connect it as a normal perl module and add something like this code:



 $self->plugin('authentication', autoload_user => 1, #     ,    $c->current_user   load_user => sub { my $self = shift; my $uid = shift; return { 'id' => $uid, 'name' => $self->session->{'user_id'}, } if $uid; return undef; }, validate_user => sub { #    my $self = shift; my $username = shift || ''; my $password = shift || ''; my $extradata = shift || {}; my $user = $self->APIrequest(...); #        -  ,     -      if (ref($user) eq 'HASH') { $self->error("API internal error while logging in user: ".$username); return undef; } if ($user->[0] !~ m/^Error:/) { $self->session->{'user_id'} = $username; return $user->[0]; } $self->info("Login for user '$username' failed: $user->[0]"); return undef; } ); 




The plugin also exports the condition for routes and the route above can be rewritten as follows:

 $r->get('/users')->over(authenticated => 1)->over(isAdmin => 1)->to('sd#users'); 




At the same time inside the controllers do not need to think about anything: if the user does not satisfy the conditions, he simply will not fall into these controllers.



Mojolicious :: Commands



This mechanism allows the use of console commands in the application.

The main charm lies in the fact that you can add your own commands, which will be executed in the context of the application and have access to its internals ( see the documentation ). Thus, for example, by crown I synchronize the data stored locally in the application with the company's internal web services:



 $ ./AppName GetCMDB -h Usage: APPLICATION GetCMDB 




Hooks



Mojolicious provides many hooks that are triggered by certain events and through which you can influence the operation of the application.

One of them I always use is before_render . This hook is triggered before the controller transfers data to the template and allows you to solve two main tasks:



Error Diagnosis


From the hook, renderer invocation arguments are available, among which, if the controller decides to "fall" and show error 500, there will be an anonymous hash {exception} containing information about the causes of the problems. I use it to “catch” the error, report it through the API in Redmine and force the application to react - correct the problem, or still show an error message, but mine.



By the way, in the process of an error message, the application searches for a file on its disk templates / exception.production.html.ep - HTML-template error page. If there is no template, a boxed one is used, which may surprise users.



Diagnosing Frontend Problems


Since the whole content of the controller is available to the hook, you can directly collect the necessary data about the operation of the controller from it and transfer it to the client via stash (). Conveniently, if the frontend behaves differently than intended, the question arises whether the data (for example, json) that is sent to the client matches what is to be transferred.



The hook code is added to AppName.pm and may look like this:

 $self->hook(before_render => sub { my ($c, $args) = @_; if ($args->{'exception'}) { #    ,   %snapshot      my %snapshot = map {$_ => $c->stash->{$_}} grep {!/mojo.active_session|mojo.captures|mojo.routed|mojo.secrets|mojo.started|^config$|^exception$/ and defined $c->stash->{$_}} keys %{$c->stash}; #   Redmine    $c->RedmineReport(); } #       $c->stash(snapshot => { map {$_ => $c->stash->{$_}} grep {!/mojo.active_session|mojo.captures|mojo.routed|mojo.secrets|mojo.started|^config$|^exception$/ and defined $c->stash->{$_}} keys %{$c->stash} }); return; }); 




A lot of data



If an application is large, it can contain several controller and helper files (connected as plugins). By default, all Mojo files will search in AppName / lib, in order not to produce a lot of visual garbage in one directory, you can split the files into subdirectories and connect to AppName.pm, for example:



 #     Core.pm, Lib.pm, CMDB.pm   AppName/lib/Helpers $self->plugin('AppName::Helpers::Core'); $self->plugin('AppName::Helpers::Lib'); $self->plugin('AppName::Helpers::CMDB'); #       AppName/lib/Controllers $r = $r->namespaces(['AppName::Controllers']); 




Instead of conclusion



As always, in Perl and the solutions built on it, there are many ways to solve problems simply, quickly and beautifully. I do not pretend that my methods are the best, but they are at least very useful in work.

Source: https://habr.com/ru/post/244317/



All Articles