I have an existing application (my website) that I'm doing some code tidying in, and the tidy up is following the same sort of idea as the Mojo::Pg example here, with separate model and controller files to keep things defined. My site accesses both Flickr and Last.fm's APIs, and I have a helper defined in Site::Helpers:
$app->helper(
get_base_rest_url => sub {
my ( $self, $config ) = #_;
sswitch ( $config ) {
case 'photos': {
my $base_url = 'https://api.flickr.com/services/rest/';
my $user_id = '7281432#N05';
my $api_key = $self->app->config->{ 'api_token' }{ 'flickr' };
my $url =
"$base_url"
. "?user_id=$user_id"
. "&api_key=$api_key"
. "&per_page=" . $self->session->{ per_page }
. '&format=json'
. '&nojsoncallback=1';
return $url;
}
case 'music': {
my $base_url = 'https://ws.audioscrobbler.com/2.0/';
my $username = 'virtualwolf';
my $api_key = $self->app->config->{ 'api_token' }{ 'last_fm' };
my $per_page = $self->session->{ 'per_page' };
my $url = "$base_url?user=$username&limit=$per_page&api_key=$api_key&format=json";
return $url;
}
}
}
);
The problem I'm running into is that I don't know how to access that helper from the Site::Model::Photos module. The error is
Can't locate object method "get_base_rest_url" via package "Site::Model::Photos"
which is fair enough, but I can't work out how to actually get at that get_base_rest_url helper (or alternatively, how to access the api_token config).
The problem is that your module have not got app attribute/method which get access to your app.
So, when you create instance of Site::Model::Photos you need to pass app to it in param and make it weaken something like that:
package Site::Model::Photos
use Scalar::Util 'weaken';
sub new {
my $class = shift;
my $app = shift;
my $hash = {app => $app, ...};
weaken $hash->{app};
return bless $hash, $class;
}
sub your_method {
my $self = shift;
$self->{app}->get_base_rest_url(...);
}
1;
Or you may to use this module https://metacpan.org/release/Mojolicious-Plugin-Model which do it for you:
package Site::Model::Photos
use Mojo::Base 'MojoX::Model';
... code of your module ...
sub your_method {
my $self = shift;
$self->app->get_base_rest_url(...);
}
1;
And in your App.pm need to add this:
$app->plugin('Model', {namespaces => ['Site::Model']});
And use it that in controller:
$c->model('photos');
$c->app->model('photos');
Related
Given a syntax like
$c->routes->get($path)->to("$controller#$sub");
I would like to know which sub $controller#$sub resolves to on dispatch. Is there a simple method to get the ref of the sub? You can hard-set ->namespaces() so I assume it's not always as simple as $controller::$sub because you could have namespace::$controller::$sub.
I could not find a way to do this using the api, but there is a private method _class() that will give the controller object that contains the sub. Here is an example:
./lib/MyApp/Controller/Foo.pm:
package MyApp::Controller::Foo;
use Mojo::Base 'Mojolicious::Controller';
sub welcome {
my $self = shift;
$self->render(text => 'Hello there.');
}
1;
./myapp.pl:
use strict;
use warnings;
use Mojolicious::Lite;
use lib './lib';
get '/' => sub {
my $c = shift;
$c->render(text => 'Hello World!');
};
my $namespaces = app->routes->namespaces;
push #$namespaces, 'MyApp::Controller';
app->routes->get('/welcome')->to('foo#welcome');
app->hook(
before_dispatch => sub {
my $c = shift;
my $field = { action => "welcome", controller => "foo" };
my $obj = $c->app->routes->_class( $c, $field );
my $method = $field->{action};
my $subref = sub { $obj->$method( #_ ) };
}
);
app->start;
I'm creating a user module to extract user information and currently I have:
sub new
{
my $class = shift;
my ( $id ) = #_;
my $self = getUserInfo ($id);
bless $self, $class;
return $self;
}
sub getUserInfo
{
...
}
However, I would like to achieve something to the effect of:
my $self = (getFirstName($id), getLastName($id), getEmpNum($id));
...
sub getFirstName{ return { firstname => $firstname }; }
sub getLastName{ return { lastname => $lastname }; }
sub getEmpNum{ return { empnum => $empnum }; }
How do I go about distributing a parameter to multiple subroutines?
I think your general code architecture has a few problems, but the snippets so far don't offer enough context to suggest an alternative solution – consider posting your complete code on Code Review for a more complete criticism.
Regarding your immediate problem: You could write a function to combine the hash references:
use Carp ();
sub combine_hashrefs {
my %combined;
for my $hashref (#_) {
if (my #conflicts = grep { exists $combined{$_} } keys %$hashref) {
Carp::confess "The keys [#conflicts] are conflicting";
}
#combined{keys %$hashref} = values %$hashref;
}
return \%combined;
}
...
my $self = combine_hashrefs($hashref_a, $hashref_b, $hashref_c, ...);
Do I understand correctly that you want to avoid the repetition of $id in the following line?
my $self = (getFirstName($id), getLastName($id), getEmpNum($id));
$self is a scalar, so you should rather use the anonymous array [...]. To specify $id only once, you can use
my $self = [ map $_->($id), \&getFirstName, \&getLastName, \&getEmpNum ];
Is there a method/feature to write auto-start subroutine/method for all available Mojolicious routes ?
Maybe an automatic helper, but I don't know how to do it yet.
I think this is useful especially to initialize database connection $self->{dbh} for nearly every available routes, ... so I can write like this:
helper DB => sub { state $dbh = Database->new };
get '/' => sub {
my $self = shift;
//$self->{dbh} // is automatically initialized & shared
};
get '/another_route' => sub {
my $self = shift;
//$self->{dbh} // also initialized & shared
};
instead of:
get '/' => sub {
my $self = shift;
$self->{dbh} = init_db();
};
get '/another_route' => sub {
my $self = shift;
$self->{dbh} = init_db();
};
P.S: I'm using Mojolicious:Lite, Perl 5.16, SQLite3
I'm not 100% sure I understand your question, helper does almost exactly what you want, but you shouldn't be using the object's hash. Here is how you would use your code:
helper db => sub { state $dbh = Database->new };
get '/' => sub {
my $self = shift;
$self->db->do_somthing();
};
get '/another_route' => sub {
my $self = shift;
my $dbh = $self->db;
...
};
helper methods are available for use by all controllers, templates and the main app.
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.
I have the following code in my class :
sub new {
my $class = shift;
my %args = #_;
my $self = {};
bless( $self, $class );
if ( exists $args{callback} ) {
$self->{callback} = $args{callback};
}
if ( exists $args{dir} ) {
$self->{dir} = $args{dir};
}
return $self;
}
sub test {
my $self = shift;
my $arg = shift;
&$self->{callback}($arg);
}
and a script containing the following code :
use strict;
use warnings;
use MyPackage;
my $callback = sub {
my $arg = shift;
print $arg;
};
my $obj = MyPackage->new(callback => $callback);
but I receive the following error:
Not a CODE reference ...
What am I missing? Printing ref($self->{callback}) shows CODE. It works if I use $self->{callback}->($arg), but I would like to use another way of invoking the code ref.
The ampersand is binding just to $self and not the whole thing. You can do curlies around the part that returns the reference:
&{$self->{callback}}($arg);
But the
$self->{callback}->($arg);
is generally considered cleaner, why don't you want to use it?