Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
added render_steps method to Mojolicious::Controller
  • Loading branch information
kraih committed Jun 29, 2014
1 parent 153e22d commit 6ad381a
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 18 deletions.
3 changes: 2 additions & 1 deletion Changes
@@ -1,5 +1,6 @@

5.11 2014-06-29
5.11 2014-06-30
- Added render_steps method to Mojolicious::Controller.
- Improved error method in Mojolicious::Validator::Validation to return
field names when called without arguments.

Expand Down
6 changes: 3 additions & 3 deletions lib/Mojolicious.pm
Expand Up @@ -159,9 +159,9 @@ sub new {
# Hide controller attributes/methods and "handler"
$r->hide(qw(app continue cookie finish flash handler match on param));
$r->hide(qw(redirect_to render render_exception render_later render_maybe));
$r->hide(qw(render_not_found render_static render_to_string rendered req));
$r->hide(qw(res respond_to send session signed_cookie stash tx url_for));
$r->hide(qw(validation write write_chunk));
$r->hide(qw(render_not_found render_static render_steps render_to_string));
$r->hide(qw(rendered req res respond_to send session signed_cookie stash));
$r->hide(qw(tx url_for validation write write_chunk));

# Check if we have a log directory
my $mode = $self->mode;
Expand Down
53 changes: 53 additions & 0 deletions lib/Mojolicious/Controller.pm
Expand Up @@ -186,6 +186,24 @@ sub render_static {
return !$self->render_not_found;
}

sub render_steps {
my $self = shift;

$self->render_later->stash->{'mojo.steps'}++;
my $delay = Mojo::IOLoop->delay(@_);
my $tx = $self->tx;
$delay->on(
finish => sub {
--$self->stash->{'mojo.steps'}
or $self->res->code
or $self->render_maybe
or $self->render_not_found;
undef $tx;
}
);
$delay->catch(sub { $self->render_exception(pop) })->wait;
}

sub render_to_string { shift->render(@_, 'mojo.to_string' => 1) }

sub rendered {
Expand Down Expand Up @@ -732,6 +750,41 @@ Render a static file using L<Mojolicious::Static/"serve">, usually from the
C<public> directories or C<DATA> sections of your application. Note that this
method does not protect from traversing to parent directories.
=head2 render_steps
$c->render_steps(sub {...}, sub {...});
Disable automatic rendering and use L<Mojo::IOLoop/"delay"> to manage
callbacks and control the flow of events, which can help you avoid deep nested
closures that often result from continuation-passing style. Perform automatic
rendering once there are no remaining steps or call L</"render_exception"> if
an error occured in one of the steps, breaking the chain.
# Longer version
$c->render_later;
my $delay = Mojo::IOLoop->delay(sub {...}, sub {...});
$delay->on(error => sub { $c->render_exception(pop) });
$delay->on(finish => sub {
$c->res->code or $c->render_maybe or $c->render_not_found;
});
$delay->wait;
# Render response in two steps
$c->render_steps(
sub {
my $delay = shift;
$c->ua->get('mojolicio.us' => $delay->begin);
$c->ua->get('mojolicio.us/perldoc' => $delay->begin);
},
sub {
my ($delay, $root, $perldoc) = @_;
$c->render(json => {
root => $root->res->code,
perldoc => $perdoc->res->code
});
}
);
=head2 render_to_string
my $output = $c->render_to_string('foo/index', format => 'pdf');
Expand Down
6 changes: 3 additions & 3 deletions lib/Mojolicious/Guides/Cookbook.pod
Expand Up @@ -393,8 +393,8 @@ latency backend web services.
</html>

Multiple events such as concurrent requests can be easily synchronized with
L<Mojo::IOLoop/"delay">, which can help you avoid deep nested closures that
often result from continuation-passing style.
L<Mojolicious::Controller/"render_steps">, which can help you avoid deep
nested closures that often result from continuation-passing style.

use Mojolicious::Lite;
use Mojo::IOLoop;
Expand All @@ -405,7 +405,7 @@ often result from continuation-passing style.
my $c = shift;

# Prepare response in two steps
Mojo::IOLoop->delay(
$c->render_steps(

# Concurrent requests
sub {
Expand Down
25 changes: 14 additions & 11 deletions lib/Mojolicious/Lite.pm
Expand Up @@ -876,17 +876,20 @@ L<Mojo::JSON> and L<Mojo::DOM> this can be a very powerful tool.
# Concurrent non-blocking
get '/titles' => sub {
my $c = shift;
my $delay = Mojo::IOLoop->delay(sub {
my ($delay, @titles) = @_;
$c->render(json => \@titles);
});
for my $url ('http://mojolicio.us', 'https://metacpan.org') {
my $end = $delay->begin(0);
$c->ua->get($url => sub {
my ($ua, $tx) = @_;
$end->($tx->res->dom->html->head->title->text);
});
}
$c->render_steps(
sub {
my $delay = shift;
$c->ua->get('http://mojolicio.us' => $delay->begin);
$c->ua->get('https://metacpan.org' => $delay->begin);
},
sub {
my ($delay, $mojo, $cpan) = @_;
$c->render(json => {
mojo => $mojo->res->dom->html->head->title->text,
cpan => $cpan->res->dom->html->head->title->text
});
}
);
};
app->start;
Expand Down
1 change: 1 addition & 0 deletions t/mojolicious/app.t
Expand Up @@ -115,6 +115,7 @@ ok $t->app->routes->is_hidden('render_later'), 'is hidden';
ok $t->app->routes->is_hidden('render_maybe'), 'is hidden';
ok $t->app->routes->is_hidden('render_not_found'), 'is hidden';
ok $t->app->routes->is_hidden('render_static'), 'is hidden';
ok $t->app->routes->is_hidden('render_steps'), 'is hidden';
ok $t->app->routes->is_hidden('render_to_string'), 'is hidden';
ok $t->app->routes->is_hidden('rendered'), 'is hidden';
ok $t->app->routes->is_hidden('req'), 'is hidden';
Expand Down
85 changes: 85 additions & 0 deletions t/mojolicious/steps_lite_app.t
@@ -0,0 +1,85 @@
use Mojo::Base -strict;

BEGIN {
$ENV{MOJO_NO_IPV6} = 1;
$ENV{MOJO_REACTOR} = 'Mojo::Reactor::Poll';
}

use Test::More;
use Mojolicious::Lite;
use Test::Mojo;

helper steps => sub {
my ($c, $cb) = @_;
$c->render_steps(
sub { Mojo::IOLoop->next_tick(shift->begin) },
sub {
$c->stash(text => 'helper', steps => 'action');
Mojo::IOLoop->next_tick($cb);
}
);
};

get '/steps' => sub {
my $c = shift;
$c->render_steps(
sub { Mojo::IOLoop->next_tick(shift->begin) },
sub { shift->pass('three steps') },
sub { $c->render(data => pop) unless $c->param('auto') }
);
};

get '/nested' => sub {
my $c = shift;
$c->render_steps(
sub { Mojo::IOLoop->next_tick(shift->begin) },
sub { $c->steps(shift->begin) },
sub { $c->stash(text => $c->stash('steps')) }
);
};

get '/not_found' => sub {
my $c = shift;
$c->render_steps(
sub { Mojo::IOLoop->next_tick(shift->begin) },
sub { $c->stash(template => 'does_not_exist') }
);
};

get '/exception' => sub {
my $c = shift;
$c->render_steps(
sub { Mojo::IOLoop->next_tick(shift->begin) },
sub { die 'Intentional error' },
sub { $c->render(text => 'fail') }
);
};

my $t = Test::Mojo->new;

# Event loop is automatically started
my $c = app->build_controller;
$c->steps(sub { });
is $c->res->body, 'helper', 'right content';

# Three steps with manual rendering
$t->get_ok('/steps')->status_is(200)->content_is('three steps');

# Three steps with template
$t->get_ok('/steps?auto=1')->status_is(200)
->content_is("three steps (template)\n");

# Nested steps
$t->get_ok('/nested')->status_is(200)->content_is('action');

# Template not found
$t->get_ok('/not_found')->status_is(404);

# Exception in step
$t->get_ok('/exception')->status_is(500)->content_like(qr/Intentional error/);

done_testing();

__DATA__
@@ steps.html.ep
three steps (template)

0 comments on commit 6ad381a

Please sign in to comment.