Skip to content

Instantly share code, notes, and snippets.

/crud.diff Secret

Created March 14, 2015 04:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/656d9db70df11456aae2 to your computer and use it in GitHub Desktop.
Save anonymous/656d9db70df11456aae2 to your computer and use it in GitHub Desktop.
diff --git a/Changes b/Changes
index 7a07e31..28239c3 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,8 @@
-6.03 2015-03-13
+6.03 2015-03-14
+ - Added support for overriding the HTTP request method with the _method query
+ parameter.
+ - Added suggested_method method to Mojolicious::Routes::Route.
- Improved portability of content negotiation tests.
6.02 2015-03-09
diff --git a/lib/Mojolicious/Guides/Routing.pod b/lib/Mojolicious/Guides/Routing.pod
index f8b1653..8b24d8a 100644
--- a/lib/Mojolicious/Guides/Routing.pod
+++ b/lib/Mojolicious/Guides/Routing.pod
@@ -236,6 +236,10 @@ but content will not be sent with the response even if it is present.
# HEAD /test -> {controller => 'bar', action => 'test'}
$r->get('/test')->to(controller => 'bar', action => 'test');
+You can also use the C<_method> query parameter to override the request method,
+this can be very useful when submitting forms with browsers that only support
+C<GET> and C<POST>.
+
=head2 IRIs
IRIs are handled transparently, that means paths are guaranteed to be unescaped
@@ -706,13 +710,6 @@ Post-processing the response to add or remove headers is a very common use.
Same for pre-processing the request.
- # Allow "_method" query parameter to override request method
- $app->hook(before_dispatch => sub {
- my $c = shift;
- return unless my $method = $c->req->url->query->param('_method');
- $c->req->method($method);
- });
-
# Choose template variant based on request headers
$app->hook(before_dispatch => sub {
my $c = shift;
@@ -771,29 +768,41 @@ to make route generation more expressive.
$r->add_shortcut(resource => sub {
my ($r, $name) = @_;
- # Generate "/$name" route
+ # Prefix for resource
my $resource = $r->any("/$name")->to("$name#");
- # Handle POST requests
- $resource->post->to('#create')->name("create_$name");
+ # Render a list of resources
+ $resource->get->to('#index')->name($name);
- # Handle GET requests
- $resource->get->to('#show')->name("show_$name");
+ # Render a form to create a new resource (submitted to "store")
+ $resource->get('/create')->to('#create')->name("create_$name");
- # Handle OPTIONS requests
- $resource->options(sub {
- my $c = shift;
- $c->res->headers->allow('POST, GET, OPTIONS');
- $c->render(data => '', status => 204);
- });
+ # Store newly create resource (submitted by "create")
+ $resource->post->to('#store')->name("store_$name");
+
+ # Render a specific resource
+ $resource->get('/:id')->to('#show')->name("show_$name");
+
+ # Render a form to edit a resource (submitted to "update")
+ $resource->get('/:id/edit')->to('#edit')->name("edit_$name");
+
+ # Store updated resource (submitted by "edit")
+ $resource->put('/:id')->to('#update')->name("update_$name");
+
+ # Remove a resource
+ $resource->delete('/:id')->to('#remove')->name("remove_$name");
return $resource;
});
- # POST /user -> {controller => 'user', action => 'create'}
- # GET /user -> {controller => 'user', action => 'show'}
- # OPTIONS /user -> {cb => sub {...}}
- $r->resource('user');
+ # GET /users -> {controller => 'users', action => 'index'}
+ # GET /users/create -> {controller => 'users', action => 'create'}
+ # POST /users -> {controller => 'users', action => 'store'}
+ # GET /users/23 -> {controller => 'users', action => 'show', id => 23}
+ # GET /users/23/edit -> {controller => 'users', action => 'edit', id => 23}
+ # PUT /users/23 -> {controller => 'users', action => 'update', id => 23}
+ # DELETE /users/23 -> {controller => 'users', action => 'remove', id => 23}
+ $r->resource('users');
=head2 Rearranging routes
diff --git a/lib/Mojolicious/Plugin/TagHelpers.pm b/lib/Mojolicious/Plugin/TagHelpers.pm
index bf6ec1b..17a337f 100644
--- a/lib/Mojolicious/Plugin/TagHelpers.pm
+++ b/lib/Mojolicious/Plugin/TagHelpers.pm
@@ -53,17 +53,14 @@ sub _form_for {
push @url, shift if ref $_[0] eq 'HASH';
# POST detection
- my @post;
+ my (@post, $method);
if (my $r = $c->app->routes->lookup($url[0])) {
- my %methods = (GET => 1, POST => 1);
- do {
- my @via = @{$r->via || []};
- %methods = map { $_ => 1 } grep { $methods{$_} } @via if @via;
- } while $r = $r->parent;
- @post = (method => 'POST') if $methods{POST} && !$methods{GET};
+ @post = (method => 'POST') if ($method = $r->suggested_method) ne 'GET';
}
- return _tag('form', action => $c->url_for(@url), @post, @_);
+ my $url = $c->url_for(@url);
+ $url->query({_method => $method}) if @post && $method ne 'POST';
+ return _tag('form', action => $url, @post, @_);
}
sub _hidden_field {
@@ -366,8 +363,9 @@ Generate C<input> tag of type C<file>.
% end
Generate portable C<form> tag with L<Mojolicious::Controller/"url_for">. For
-routes that allow C<POST> but not C<GET>, a C<method> attribute will be
-automatically added.
+routes that do not allow C<GET>, a C<method> attribute will be automatically
+added. And for methods other than C<GET> and C<POST>, a C<_method> query
+parameter will be added as well.
<form action="/path/to/login">
<input name="first_name" type="text">
diff --git a/lib/Mojolicious/Routes.pm b/lib/Mojolicious/Routes.pm
index bdabbf2..d713913 100644
--- a/lib/Mojolicious/Routes.pm
+++ b/lib/Mojolicious/Routes.pm
@@ -71,7 +71,7 @@ sub match {
else { $path = $req->url->path->to_route }
# Method (HEAD will be treated as GET)
- my $method = uc $req->method;
+ my $method = uc($req->url->query->clone->param('_method') || $req->method);
$method = 'GET' if $method eq 'HEAD';
# Check cache
diff --git a/lib/Mojolicious/Routes/Route.pm b/lib/Mojolicious/Routes/Route.pm
index a98c461..0cec629 100644
--- a/lib/Mojolicious/Routes/Route.pm
+++ b/lib/Mojolicious/Routes/Route.pm
@@ -128,6 +128,19 @@ sub route {
return $route;
}
+sub suggested_method {
+ my $self = shift;
+
+ my %via;
+ for my $route (@{$self->_chain}) {
+ next unless my @via = @{$route->via || []};
+ %via = map { $_ => 1 } keys %via ? grep { $via{$_} } @via : @via;
+ }
+
+ return 'POST' if $via{POST} && !$via{GET};
+ return $via{GET} ? 'GET' : (sort keys %via)[0] || 'GET';
+}
+
sub to {
my $self = shift;
@@ -492,6 +505,12 @@ The L<Mojolicious::Routes> object this route is a descendant of.
Low-level generator for routes matching all HTTP request methods, returns a
L<Mojolicious::Routes::Route> object.
+=head2 suggested_method
+
+ my $method = $r->suggested_method;
+
+Suggested HTTP method for this route, C<GET> and C<POST> are preferred.
+
=head2 to
my $defaults = $r->to;
diff --git a/t/mojolicious/tag_helper_lite_app.t b/t/mojolicious/tag_helper_lite_app.t
index 68a6bfe..fae4ddf 100644
--- a/t/mojolicious/tag_helper_lite_app.t
+++ b/t/mojolicious/tag_helper_lite_app.t
@@ -313,7 +313,7 @@ EOF
# Empty selection
$t->put_ok('/selection')->status_is(200)
- ->content_is("<form action=\"/selection\">\n "
+ ->content_is("<form action=\"/selection?_method=PUT\" method=\"POST\">\n "
. '<select name="a">'
. '<option value="b">b</option>'
. '<optgroup label="c">'
@@ -342,7 +342,7 @@ $t->put_ok('/selection')->status_is(200)
# Selection with values
$t->put_ok('/selection?a=e&foo=bar&bar=baz&yada=b')->status_is(200)
- ->content_is("<form action=\"/selection\">\n "
+ ->content_is("<form action=\"/selection?_method=PUT\" method=\"POST\">\n "
. '<select name="a">'
. '<option value="b">b</option>'
. '<optgroup label="c">'
@@ -372,7 +372,7 @@ $t->put_ok('/selection?a=e&foo=bar&bar=baz&yada=b')->status_is(200)
# Selection with multiple values
$t->put_ok('/selection?foo=bar&a=e&foo=baz&bar=d&yada=a&yada=b')
->status_is(200)
- ->content_is("<form action=\"/selection\">\n "
+ ->content_is("<form action=\"/selection?_method=PUT\" method=\"POST\">\n "
. '<select name="a">'
. '<option value="b">b</option>'
. '<optgroup label="c">'
@@ -401,7 +401,7 @@ $t->put_ok('/selection?foo=bar&a=e&foo=baz&bar=d&yada=a&yada=b')
# Selection with multiple values preselected
$t->put_ok('/selection?preselect=1')->status_is(200)
- ->content_is("<form action=\"/selection\">\n "
+ ->content_is("<form action=\"/selection?_method=PUT\" method=\"POST\">\n "
. '<select name="a">'
. '<option selected value="b">b</option>'
. '<optgroup label="c">'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment