Delegate Rerouting to Controller - mojolicious

Mojolicious + Hypnotoad.
I want my controllers to reroute the remaining portion of the request, so that I don't have to declare all routes at the level of the main script.
So for example, '/foo/bar/baz' should route to Controller 'FOO', which will then decide routing for 'bar/baz', internal to itself.
Main script :
package MyApp;
use Mojo::Base 'Mojolicious';
use Mojolicious::Plugin::Config;
sub startup {
my $self = shift;
$self->moniker('MyApp');
$self->plugin('Config');
my $r = $self->routes;
$r->any('/foo/*remainder')->to('FOO#rerouter')->name('TEST_NAME');
}
I've tried one way, by dynamically adding routes, but running it many times shows inconsistency - probably caused by a race condition or something :
package MyApp::Controller::FOO;
use Mojo::Base 'Mojolicious::Controller';
sub rerouter {
my $self = shift;
my $r = Mojolicious::Routes->new;
$r->get('bar/:baz')->to('myInternalControllerAction');
my $current_route = $self->current_route; # 'TEST_NAME'
$self->app->routes->find($current_route)->add_child($r);
}
sub myInternalControllerAction { #stuff }
That seems consistent with this other answer that :
"The router is dynamic until the first request has been served, after
that, the router cannot change routes"
https://stackoverflow.com/a/22291864/2304437
Even if that did work though, the execution would exit the controller FOO before re-entering it on a different action via the dynamically added route(s).
The other way I can do it is just create a dispatch table in the "re-router" of the controller, since I have access to the remainder of the request anyway :
sub rerouter {
my $self = shift;
my $remainder = $self->stash->{remainder};
# ...
}
But then I lose benefits of Mojolicious' dispatcher - I would have to parse the remaining request URL / path myself. Feels dirty.
Hooks don't seem appropriate here either.
How can I elegantly let the controller route its own child portion ?

I solved this as follows :
use MyApp::Controller::FOO;
my $r = $self->routes;
my $r_root = $r->under('/' => sub { return 1; });
my $baz = $r_root->under('/baz' => sub { return 1; });
$baz->add_child($MyApp::Controller::FOO::routes);
Then in the module :
package MyApp::Controller::FOO;
use Mojo::Base 'Mojolicious::Controller';
our $routes = Mojolicious::Routes->new;
my $r = $routes->under('/')->to('FOO#bar');
sub bar { ... }
Maybe not as elegant as it could be, but it works very well for me.

Related

Mojolicious set HTTP params for controller artificially

I use Mojolicious command for testing a controller function. This function looks like this:
sub process_order {
# controller object
my $c = shift;
my $id = $c->param('id');
# process data
}
Is it possible to artificially set the params for the controller inside a command:
sub run {
my ($self, #args) = #_;
$self->app->controller_class('Base::Controller');
my $c = $self->app->build_controller;
set_http_param('id', 1); # ?
$c->process_order();
}
I am using a different database for testing and not all the tables are present there, so it would be nice if I could setup some artificial environment for the controller.

how to pass arguments from an plack app to an mojolicious app enabled in builder?

given the example plack app using lots of middleware components and an mojolicious app
enabled in builder (see below), how can i pass parameters from the app.psgi to Mojolicious
without using the ugly %ENV hack shown? of cause passing an config is just an example, this could be any scalar/object.
app.psgi
use Plack::Builder;
$ENV{CONFIG} = {...};
builder {
...
Mojolicious::Commands->start_app('MyApp');
};
MyApp.pm
package MyApp;
use Mojo::Base 'Mojolicious';
sub startup {
my $self = shift;
my $r = $self->routes;
$self->config( $ENV{CONFIG} );
$r->route('/')->to('home#');
}
This is an interesting question and is most easily tackled by looking at the source. In your example you rightly use
Mojolicious::Commands->start_app('MyApp');
Looking at the source shows that start_app is a rather simple wrapper:
sub start_app {
my $self = shift;
return Mojo::Server->new->build_app(shift)->start(#_);
}
It turns out that build_app is as well:
sub build_app {
my ($self, $app) = #_;
local $ENV{MOJO_EXE};
return $app->new unless my $e = Mojo::Loader->new->load($app);
die ref $e ? $e : qq{Couldn't find application class "$app".\n};
}
returning a new instance of your app's class. The Mojolicious class's new function is more involved, but in the end, it just calls the familiar startup method and returns the instance.
This means that you cannot easily pass arguments to the startup method from your middleware wrapper, used in the standard way. I can think of two mechanisms to accomplish what you want to do: 1) write your own build_app function to replace the server's method but which passes arguments to $app->new (which would get passed to startup in turn) or 2) write your own start_app function which could call another startup-like function.
# in MyApp.pm
sub startup {
... # as before
}
sub after_startup {
... # your new code here,
# or even most of what was in `startup` before
}
and
# app.psgi
builder {
...
my $app = Mojo::Server->new->build_app(shift);
$app->after_startup(#your_args_here);
$app->start(#_);
}

How to access object features in Perl from within the same package

I'm making a Perl module and I am still getting to grips with how Perl deals with objects.
This is the new sub that I wrote to create an object and I have no problem updating elements:
sub new {
my $class = shift;
my ($self) = {
name => undef
};
bless($self, $class);
return $self;
}
sub get_name {
my $self = shift;
$self->{name} = 'Eve';
return $self->{name};
}
I can use the object fine when I call the module and access it from another file, but I want to use the data in the object at other areas in the module code.
So I have no problem doing this:
my $new_object = new ProgramTest; # ProgramTest being the module/package
my $name = get_name();
But I want to use the $self elements in a 'module-internal' method which is never accessed by an outside script. So I want to have something like this:
sub get_variables {
return (name); # I don't know how to get the name here
# (I plan to have other variables, too)
}
I am probably missing something obvious (I'm sure I'll kick myself when I see the solution), so any help appreciated!
I want this so that the rest of the module can use the variables (without changing) as there are conditions that rely on their values.
There's no such thing as internal/private methods in perl objects. Common practise is to start any methods which should not be used publicly with an underscore, but this is not enforced in any way. Also have a look at moose - it takes a lot of the hassle out of OO perl.
With regards to your question the below shows how one module method can call another module method, with both having access to the object data. Again I woulds really recommend you use Moose!
sub publicSub{
my ( $self ) = #_;
return $self->_privateSub();
}
sub _privateSub{
my ( $self ) = #_;
return $self->{name};
}
I think you want class-variables. They are global to a class and all instances of the class (i.e. all the objects you created) can see them. Global in this case means that they are at the ouside-most lexical scope, so all subs can see them.
package ProgramTest;
my $everyone_can_see_this = 1; # lexical scope, but 'global' to the package
sub new {
my $class = shift;
my ($self) = {
name => undef
};
bless($self, $class);
return $self;
}
sub get_var {
my $self = shift;
return ++$everyone_can_see_this;
}
package Main;
my $o1 = ProgramTest->new;
my $o2 = ProgramTest->new;
say $o1->get_var;
say $o2->get_var;
say $o1->get_var;
__END__
2
3
4
But I don't see why you would want to do that. It doesn't make sense (unless you want an object-counter). Don't use it for config values, or you cannot really have objects for different purposes of the same class.
Maybe you want something else. If so, please try to rephrase your question.

Why separate routing and controller actions in mojolicious?

I am reading the section of Mojolicious::Guides::Growing where it tells you how to grow a Mojolicious::Lite into a "well organized" cpan-uploadable application. First, it tells you to split the M::L app into a launch script and an application class.
package MyApp;
use Mojo::Base 'Mojolicious';
use MyUsers;
sub startup {
my $self = shift;
# ...auth stuff omitted...
my $r = $self->routes;
$r->any('/' => sub {
my $self = shift;
my $user = $self->param('user') || '';
my $pass = $self->param('pass') || '';
return $self->render unless $self->users->check($user, $pass);
$self->session(user => $user);
$self->flash(message => 'Thanks for logging in.');
$self->redirect_to('protected');
} => 'index');
$r->get('/protected' => sub {
my $self = shift;
return $self->redirect_to('index') unless $self->session('user');
});
$r->get('/logout' => sub {
my $self = shift;
$self->session(expires => 1);
$self->redirect_to('index');
});
}
1;
This makes sense to me. But then it goes on to say that this application class can further be refactored into a controller class with the actions, and the application class itself can be reduced to the routing information:
package MyApp::Login;
use Mojo::Base 'Mojolicious::Controller';
sub index {
my $self = shift;
my $user = $self->param('user') || '';
my $pass = $self->param('pass') || '';
return $self->render unless $self->users->check($user, $pass);
$self->session(user => $user);
$self->flash(message => 'Thanks for logging in.');
$self->redirect_to('protected');
}
sub protected {
my $self = shift;
return $self->redirect_to('index') unless $self->session('user');
}
sub logout {
my $self = shift;
$self->session(expires => 1);
$self->redirect_to('index');
}
1;
package MyApp;
use Mojo::Base 'Mojolicious';
use MyUsers;
sub startup {
my $self = shift;
# ...auth stuff omitted...
my $r = $self->routes;
$r->any('/')->to('login#index')->name('index');
$r->get('/protected')->to('login#protected')->name('protected');
$r->get('/logout')->to('login#logout')->name('logout');
}
1;
I don't see why this is superior to the "hybrid" version where routes and actions are intermingled, because now in order to redirect between the actions with redirect_to() in the controller, you need to look at the routing infomration in a different file, and if you want to change a url, you have to do it in two different files instead of one. This:
$r->get('/protected' => sub {
my $self = shift;
return $self->redirect_to('index') unless $self->session('user');
});
turns into:
sub protected {
my $self = shift;
return $self->redirect_to('index') unless $self->session('user');
}
$r->get('/protected')->to('login#protected')->name('protected');
Which has the word "protected" 4 times in two different files (although I'm not sure what the name("protected") does yet).
I'm a complete novice when it comes to web development, by the way.
It's not superior; rather, it's different.
As soon as you move beyond one developer, having your app in one file is no longer a benefit; you'll end up stepping on each others toes. Even if you're the only dev, it's never easy to keep track of locations in files of 1000+ lines. In addition, being able to look at one file and determine all your routes at a glance is quite useful when you have more than just a few routes, not to mention 100+.
Also, you don't have to change a redirect url in a controller action when the route changes. Mojolicious will do the work for you if you're making use of named routes.

Feedback, question about my module and if i should change anything?

package My::Module;
# $Id$
use strict;
use Carp;
use Data::Dumper;
use DBI;
$My::Module::VERSION = '0.1';
sub new {
my ($class, %opt) = #_;
my $opt_count = keys %opt;
$class->set_error('');
#return $class->set_error("Too many arguments to initialize.") if ($opt_count > 5);
#return $class->set_error("Missing arguments to initialize.") if ($opt_count < 2);
my $self = bless {
_DRIVER_OPTIONS => $opt{'mysql'},
},$class;
if (not defined $self) {
return $class->set_error( "new() failed: " . $class->errstr );
}
if ($self->{_DRIVER_OPTIONS}->{Host} ne '') {
$self->{_DRIVER_OPTIONS}->{DataSource} = 'DBI:mysql:database=' . $self->{_DRIVER_OPTIONS}->{Database} . ';host=' . $self->{_DRIVER_OPTIONS}->{Host};
} else {
$self->{_DRIVER_OPTIONS}->{DataSource} = 'DBI:mysql:database=' . $self->{_DRIVER_OPTIONS}->{Database} . ';';
}
$self->{Handle} = DBI->connect($self->{_DRIVER_OPTIONS}->{DataSource},
$self->{_DRIVER_OPTIONS}->{Username},
$self->{_DRIVER_OPTIONS}->{Password},
{ RaiseError=>1, PrintError=>1, AutoCommit=>1 }
);
return $self->set_error("new(): couldn't connect to database: " . DBI->errstr) unless ($self->{Handle});
$self->{_disconnect} = 1;
print Dumper \$self;
return $self;
}
sub database {
my $self = shift;
if (#_) { $self->{Handle} = shift }
return $self->{Handle};
}
sub set_error {
my $class = shift;
my $message = shift;
$class = ref($class) || $class;
no strict 'refs';
${ "$class\::errstr" } = sprintf($message || "", #_);
return;
}
*error = \&errstr;
sub errstr {
my $class = shift;
$class = ref( $class ) || $class;
no strict 'refs';
return ${ "$class\::errstr" } || '';
}
sub DESTROY {
my $self = shift;
unless (defined $self->{Handle} && $self->{Handle}->ping) {
$self->set_error(__PACKAGE__ . '::DESTROY(). Database handle has gone away');
return;
}
unless ($self->{Handle}->{AutoCommit}) {
$self->{Handle}->commit;
}
if ($self->{_disconnect}) {
$self->{Handle}->disconnect;
}
}
1;
Is that the right way so i can
re-use the database on my code
instead of having to open a new
connection or that will aswell open
a new connection every time i use it
?
Should i change anything on the
module ? or anything i did wrong ?
Currently i am just learning and thinked of doing my own engine module so i began with this.
Simple test code (the bellow code is not to be reviewed just a sample on how to use the module):
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
use lib 'path to module';
use My::Module;
my $session = My::Module->new(mysql => {
Database =>'module',
Host =>'10.0.0.2',
Username =>'module',
Password =>'module'
}) or die My::Module->errstr;
my $dbh = $session->database();
my $sth = $dbh->prepare(q{
SELECT session_id
FROM sessions
});
$sth->execute() || die print($dbh->errstr);
my $ref = $sth->fetchall_arrayref({});
$sth->finish;
print Dumper \$ref;
I would suggest using an existing database interface rather than rolling your own, as there are many secret gotchas that others have spent years figuring out and solving for you. DBIx::Connector is excellent, and with its fixup mode, will let you reuse database connections, even across process forks.
Additionally, if you use Moose you will never have to write your own object constructors or object fields again. :)
DBIx::Class combined with Moose would be even better, but not necessary until you find yourself needing more ORM-ish features.
Other than using a CPAN module to accomplish this task, here are my practical recommendations:
Don't return an error value from the constructor. Instead, throw an exception.
Access the internals of your class using accessors rather than using direct hash access.
If the user of your class did not enable AutoCommit, she chose not to enable AutoCommit for a reason. Therefore don't do:
unless ($self->{Handle}->{AutoCommit}) {
$self->{Handle}->commit;
}
in DESTROY.
Note that bless is not going to fail so long as it is given a modifiable reference (compare this to, say, the behavior of open which can fail to open a file even though the argument to open is a valid filename and would indicate this situation by returning a false value). Therefore, checking the return value of bless does not serve any useful purpose. If you want to handle the possibility of bless failing, you will have to catch fatal runtime exceptions.
Your way of exposing errors is very, very oldfashioned. If something exceptional happens, why not raise a proper exception? You seem to have modelled your error handling after the DBI module. Note that DBI also has a RaiseError option. Using that is almost always more reasonable than using the oldfashioned errorstr version. Unfortunately DBI can't change it's default anymore now, but for new code I entirely don't see the reason to copy this flawed idea.
You're also constructing a DBI connection within your code, based on parameters the user provided from the outside. Do you have a good reason for doing that? Allowing the user to pass in the DBI::dh he constructed himself would be more flexible. Yes, that requires slightly more code on the outside to set up objects and wire them together, but it will also result in a cleaner design. If wiring up your objects manually bothers you too much, you might want to have a look at Bread::Board to do the wiring for you instead of compromising on the design of your module.
Also, I second Ether's suggestion of using DBIx::Connector. It really takes a lot of pain out of managing database handles, which can be very error-prone.