Skip to content

Commit 747197e

Browse files
committed
Merge branch '3.1.x' of github.com:grails/grails-core into 3.1.x
2 parents 49a74aa + 84f7f0b commit 747197e

File tree

4 files changed

+122
-21
lines changed

4 files changed

+122
-21
lines changed

grails-plugin-gsp/src/main/groovy/org/grails/plugins/web/taglib/FormTagLib.groovy

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,12 @@ class FormTagLib implements ApplicationContextAware, InitializingBean, TagLibrar
404404
}
405405

406406
// default to post
407-
def method = linkAttrs[LinkGenerator.ATTRIBUTE_METHOD]?.toUpperCase() ?: 'POST'
407+
def method
408+
if (linkAttrs[LinkGenerator.ATTRIBUTE_RESOURCE] && linkAttrs[LinkGenerator.ATTRIBUTE_ACTION]) {
409+
method = LinkGenerator.REST_RESOURCE_ACTION_TO_HTTP_METHOD_MAP.get(linkAttrs[LinkGenerator.ATTRIBUTE_ACTION].toString())
410+
} else {
411+
method = linkAttrs[LinkGenerator.ATTRIBUTE_METHOD]?.toUpperCase() ?: 'POST'
412+
}
408413
def httpMethod = HttpMethod.valueOf(method)
409414
boolean notGet = httpMethod != HttpMethod.GET
410415

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package org.grails.web.taglib
2+
3+
import org.grails.core.artefact.UrlMappingsArtefactHandler
4+
5+
class FormTagLibResourceTests extends AbstractGrailsTagTests {
6+
7+
protected void onInit() {
8+
def mappingClass = gcl.parseClass('''
9+
class TestUrlMappings {
10+
static mappings = {
11+
"/books"(resources:"book") {
12+
"/authors"(resources:"author")
13+
}
14+
}
15+
}
16+
''')
17+
18+
grailsApplication.addArtefact(UrlMappingsArtefactHandler.TYPE, mappingClass)
19+
}
20+
21+
void testResourceSave() {
22+
def template = '<g:form resource="book" action="save"/>'
23+
assertOutputEquals('<form action="/books" method="post" ></form>', template)
24+
}
25+
26+
void testResourceUpdate() {
27+
def template = '<g:form resource="book" action="update" id="1"/>'
28+
assertOutputEquals('<form action="/books/1" method="post" ><input type="hidden" name="_method" value="PUT" id="_method" /></form>', template)
29+
}
30+
31+
void testResourceUpdateIdInParams() {
32+
def template = '<g:form resource="book" action="update" params="[id:1]"/>'
33+
assertOutputEquals('<form action="/books/1" method="post" ><input type="hidden" name="_method" value="PUT" id="_method" /></form>', template)
34+
}
35+
36+
void testResourcePatch() {
37+
def template = '<g:form resource="book" action="patch" id="1"/>'
38+
assertOutputEquals('<form action="/books/1" method="post" ><input type="hidden" name="_method" value="PATCH" id="_method" /></form>', template)
39+
}
40+
41+
void testResourcePatchIdInParams() {
42+
def template = '<g:form resource="book" action="patch" params="[id:1]"/>'
43+
assertOutputEquals('<form action="/books/1" method="post" ><input type="hidden" name="_method" value="PATCH" id="_method" /></form>', template)
44+
}
45+
46+
void testResourceNestedSave() {
47+
def template = '<g:form resource="book/author" action="save" params="[bookId:1]"/>'
48+
assertOutputEquals('<form action="/books/1/authors" method="post" ></form>', template)
49+
}
50+
51+
void testResourceNestedUpdate() {
52+
// We'd really like to suppoer this format <g:form resource="book/author" action="update" id="2" bookId="1"/>
53+
// but the form tag limits the set of attributes it hands to the linkGenerator and the dynamic parameters like 'bookId' get filtered out
54+
// instead we make do with putting bookId in the params attribute
55+
def template = '<g:form resource="book/author" action="update" id="2" params="[bookId:1]"/>'
56+
assertOutputEquals('<form action="/books/1/authors/2" method="post" ><input type="hidden" name="_method" value="PUT" id="_method" /></form>', template)
57+
}
58+
59+
void testResourceNestedUpdateIdInParams() {
60+
def template = '<g:form resource="book/author" action="update" params="[bookId:1, id:2]"/>'
61+
assertOutputEquals('<form action="/books/1/authors/2" method="post" ><input type="hidden" name="_method" value="PUT" id="_method" /></form>', template)
62+
}
63+
64+
void testResourceNestedPatch() {
65+
def template = '<g:form resource="book/author" action="patch" id="2" params="[bookId:1]"/>'
66+
assertOutputEquals('<form action="/books/1/authors/2" method="post" ><input type="hidden" name="_method" value="PATCH" id="_method" /></form>', template)
67+
}
68+
69+
void testResourceNestedPatchIdInParams() {
70+
def template = '<g:form resource="book/author" action="patch" params="[bookId:1, id:2]"/>'
71+
assertOutputEquals('<form action="/books/1/authors/2" method="post" ><input type="hidden" name="_method" value="PATCH" id="_method" /></form>', template)
72+
}
73+
74+
void assertOutputEquals(expected, template, params = [:]) {
75+
def engine = appCtx.groovyPagesTemplateEngine
76+
77+
assert engine
78+
def t = engine.createTemplate(template, "test_"+ System.currentTimeMillis())
79+
80+
def w = t.make(params)
81+
82+
def sw = new StringWriter()
83+
def out = new PrintWriter(sw)
84+
webRequest.out = out
85+
w.writeTo(out)
86+
87+
assertEquals expected, sw.toString()
88+
}
89+
}

grails-web-url-mappings/src/main/groovy/grails/web/mapping/LinkGenerator.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,27 @@ public interface LinkGenerator {
7070
ATTRIBUTE_NAMESPACE
7171
);
7272

73+
Map<String, String> REST_RESOURCE_ACTION_TO_HTTP_METHOD_MAP = CollectionUtils.<String, String>newMap(
74+
"create", "GET",
75+
"save", "POST",
76+
"show", "GET",
77+
"index", "GET",
78+
"edit", "GET",
79+
"update", "PUT",
80+
"patch", "PATCH",
81+
"delete", "DELETE"
82+
);
83+
84+
Map<String, String> REST_RESOURCE_HTTP_METHOD_TO_ACTION_MAP = CollectionUtils.<String, String>newMap(
85+
"GET_ID", "show",
86+
"GET", "index",
87+
"POST", "save",
88+
"DELETE", "delete",
89+
"PUT", "update",
90+
"PATCH", "patch"
91+
);
92+
93+
7394
/**
7495
* Generates a link to a static resource for the given named parameters.
7596
*

grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/DefaultLinkGenerator.groovy

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -58,25 +58,6 @@ class DefaultLinkGenerator implements LinkGenerator, org.codehaus.groovy.grails.
5858

5959
private static final Pattern absoluteUrlPattern = Pattern.compile('^[A-Za-z][A-Za-z0-9+\\-.]*:.*$')
6060

61-
private static final Map<String, String> REST_RESOURCE_ACTION_TO_HTTP_METHOD_MAP = [
62-
create:"GET",
63-
save:"POST",
64-
show:"GET",
65-
index:"GET",
66-
edit:"GET",
67-
update:"PUT",
68-
patch:"PATCH",
69-
delete:"DELETE"
70-
]
71-
72-
private static final Map<String, String> REST_RESOURCE_HTTP_METHOD_TO_ACTION_MAP = [
73-
GET_ID:"show",
74-
GET:"index",
75-
POST:"save",
76-
DELETE:"delete",
77-
PUT:"update",
78-
PATCH:"patch"
79-
]
8061
String configuredServerBaseURL
8162
String contextPath
8263
String resourcePath
@@ -207,7 +188,12 @@ class DefaultLinkGenerator implements LinkGenerator, org.codehaus.groovy.grails.
207188
if (tokens.size()>1) {
208189
for(t in tokens[0..-2]) {
209190
final key = "${t}Id".toString()
210-
params[key] = urlAttrs.remove(key)
191+
final attr = urlAttrs.remove(key)
192+
// the params value might not be null
193+
// only overwrite if urlAttrs actually had the key
194+
if (attr) {
195+
params[key] = attr
196+
}
211197
}
212198
}
213199
if (!methodAttribute && action) {

0 commit comments

Comments
 (0)