How to use my own subroutines (globally) in a Mojolicious::Lite Application
Asked Answered
U

2

9

I need to be able to write and call my own subroutines in a Mojolicious::Lite Application. However, the intuitive way to do this doesn't seem to be working. I emailed a colleague who has more Mojolicious experience than I do with this question and he sent me the following code:

#!/usr/bin/env perl
use Mojolicious::Lite;

# Documentation browser under "/perldoc"
plugin 'PODRenderer';

get '/' => sub {
  my $self = shift;
  $self->render('index');
};

sub factorial {
    my $n = shift;
    return $n ? $n * factorial($n - 1) : 1;
}

app->start;
__DATA__

@@ index.html.ep
% layout 'default';
% title 'Welcome';
Welcome to the Mojolicious real-time web framework!

Five factorial: <%= main::factorial(5) %>

@@ layouts/default.html.ep
<!DOCTYPE html>
<html>
  <head><title><%= title %></title></head>
  <body><%= content %></body>
</html>

But when I run this it tells me that when I'm calling an undefined subroutine:

Undefined subroutine &main::factorial called at template index.html.ep from DATA section line 5, line 32.

I've spent time mucking around with this code, and trying different things that will get it to work, but so far the only thing that makes it run properly is when the subroutine is defined within the scope of the @@ xxx.html.ep's. I've googled/searched stackoverflow for "user defined subroutines in Mojolicious::Lite" and other similar queries. Nothing seems to come up. My searches of the documentation have proved fruitless as well. This seems like it should be a simple task, but I'm a bit stuck. Any help would be appreciated.

Unhallow answered 8/4, 2014 at 17:37 Comment(1)
I hate to say it, but your code runs fine for me. That said, helpers are the preferred way to do things in Mojolicious::Lite.Cullie
C
6

As PerC has already mentioned, helpers are the preferred mechanism for adding behaviors (or access to behaviors) to templates. Since he has already shown that example, I will just add that you can make a hybrid of the two by doing something like this (I'm removing the pod renderer plugin, you don't need it).

#!/usr/bin/env perl
use Mojolicious::Lite;

get '/' => sub {
  my $self = shift;
  $self->render('index');
};

sub factorial {
    my $n = shift;
    return $n ? $n * factorial($n - 1) : 1;
}

helper factorial => sub { shift; factorial(@_) };

app->start;
__DATA__

@@ index.html.ep
% layout 'default';
% title 'Welcome';
Welcome to the Mojolicious real-time web framework!

Five factorial: <%= factorial(5) %>

@@ layouts/default.html.ep
<!DOCTYPE html>
<html>
  <head><title><%= title %></title></head>
  <body><%= content %></body>
</html>
Cullie answered 9/4, 2014 at 2:53 Comment(4)
As you can see from Joel's example there won't be any name clash between the sub factorial and helper factorial.Radiotherapy
So does the "scope" that the subroutines are declared in end at DATA ?Unhallow
No, this goes quite deep. The controller has an attribute 'renderer' which stores the helper in a hash. This mean the helper name isn't in the subroutine symbol table at all.Radiotherapy
Sorry, it's of course the app that holds that attribute, not the controller.Radiotherapy
R
5

If you re-work the the sub into a helper it can be accessed from within a template (or controller).

#!/usr/bin/env perl
use Mojolicious::Lite;

helper factorial => sub {
  my ($self, $n) = @_;
  return $n ? $n * $self->factorial($n - 1) : 1;
};

get '/' => 'index';

app->start;

__DATA__

@@ index.html.ep

Five factorial: <%= factorial(5) %>

Running the app gives (minus the logging):

$ perl app.pl get /
Five factorial: 120
$

Update

This will neither answer your question, but gives you another option.

I don't know the background why you prefer to call the sub in main from the template. In my opinion the template (or view I you like) should only present data, and not try to calculate/process data, that's the job for the model (or controller). As the same (and more) data is available in the controller as in the template, why don't you call the sub in the controller and just pass the result to the template? The result can be whatever Perl data structure so you aren't limited to scalars.

Additionally when you grow the Mojolicious::Lite app into a full app, there is no longer a useful package main (it's just a small application that calls your main package MyApp). That makes the whole idea of calling generic subs from templates even less a way forward.

Here is another example of the factorial app, where the calculation is done in the controller:

#!/usr/bin/env perl
use Mojolicious::Lite;

sub factorial {
  my $n = shift;
  return $n ? $n * factorial($n - 1) : 1;
}

get '/' => sub {
  my $self = shift;
  $self->render('index', result => factorial(5));
};

app->start;

__DATA__

@@ index.html.ep

Five factorial: <%= $result %>
Radiotherapy answered 8/4, 2014 at 20:50 Comment(4)
While this does solve my problem, and it is an elegant solution, it doesn't technically answer the question. So I'm afraid I won't mark it as an answer. However very helpful. I'll definitely give it an upvote.Unhallow
Is this the way user defined subs were supposed to be used in Mojolicious::Lite?Unhallow
The templates do only have access to the helpers, stash and flash that are defined in the current controller object. AFAIK, there is no way to breakout of the sandbox and access things outside $self.Radiotherapy
I would upvote again if I could (after the update). THAT really is the proper MVC thing to do; pass data to the template rather than have it calculating things.Cullie

© 2022 - 2024 — McMap. All rights reserved.