8

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.

PerC
  • 389
  • 1
  • 10
0112
  • 3,194
  • 6
  • 28
  • 53
  • 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. – Joel Berger Apr 09 '14 at 02:41

2 Answers2

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>
Joel Berger
  • 19,816
  • 5
  • 47
  • 101
  • As you can see from Joel's example there won't be any name clash between the `sub factorial` and `helper factorial`. – PerC Apr 09 '14 at 07:30
  • So does the "scope" that the subroutines are declared in end at __DATA__ ? – 0112 Apr 09 '14 at 15:01
  • 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. – PerC Apr 09 '14 at 17:56
  • Sorry, it's of course the app that holds that attribute, not the controller. – PerC Apr 09 '14 at 18:29
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 %>
PerC
  • 389
  • 1
  • 10
  • 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. – 0112 Apr 08 '14 at 21:38
  • Is this the way user defined subs were supposed to be used in Mojolicious::Lite? – 0112 Apr 08 '14 at 21:39
  • 1
    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`. – PerC Apr 08 '14 at 22:07
  • 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. – Joel Berger Apr 09 '14 at 16:12