Does Grails supports Restful nested URLs? - grails

Does Grails supports Restful nested URLs like '/articles/1/comments/5' by default? If not is there any plugin for that?

See here. Nested RESTful URLs will be supported in Grails 2.3.

If you're bound to an older release of Grails (e.g < 2.3), and the available plugins don't work out, you can use named URL mappings to produce an effective restful mapping.
Here's an example from a project of mine - I've left out some details, but hopefully this gets you started if you decide to give this approach a try.
In your UrlMappings.groovy
/** 1. Mappings can handle multiple actions depending on HTTP
method like Rest. Names are a little clunky, like this would
be more appropriate as "resource" vs "showResource" but we didn't want
potential naming conflict in future release
2. TODO: DRY constraints - make constraints global
3. make sure controllers have proper actions defined
*/
/** RESTFUL mapping for single resource */
name listResources: "/$controller" {
action = [GET: "list", POST: "save"]
}
name createResource: "/$controller/create" {
action = [GET: "create" ]
}
name deleteResource: "/$controller/$id?/delete" {
action = [POST: "delete", DELETE: "delete"]
constraints { id(matches: /[0-9]+/) }
}
name editResource: "/$controller/$id?/edit" {
action = [GET: "edit", PUT: "update", POST: "update"]
constraints { id(matches: /[0-9]+/) }
}
name showResource: "/$controller/$id?" {
action = [GET: "show", PUT: "update", POST: "update", DELETE: "delete"]
constraints { id(matches: /[0-9]+/) }
}
/** RESTFUL mapping for CHILD with PARENT */
name listChildResources: "/$parentResource/$pid/$controller" {
action = [GET: "list", POST: "save"]
constraints { pid(matches: /[0-9]+/) }
}
name createChildResource: "/$parentResource/$pid/$controller/create" {
action = [GET: "create" ]
constraints { pid(matches: /[0-9]+/) }
}
name showChildResource: "/$parentResource/$pid/$controller/$id?" {
action = [GET: "show", PUT: "update", POST: "update", DELETE: "delete"]
constraints {
id(matches: /[0-9]+/)
pid(matches: /[0-9]+/)
}
}
name editChildResource: "/$parentResource/$pid/$controller/$id?/edit" {
action = [GET: "edit"]
constraints {
id(matches: /[0-9]+/)
pid(matches: /[0-9]+/)
}
}
Make sure you controllers have actions and supported HTTP methods define, eg
static allowedMethods = [
save: "POST",
update: ["POST", "PUT"],
delete: ["POST", "DELETE"]
]
Then use the mappings like so (for example lets say we have Gardens and Plants as resources).
//show a garden
<g:link mapping="showResource" controller="garden"
id="${gardenInstance.id}">${gardenInstance.name}</g:link>
//create a plant for garden
<g:link mapping="createChildResource" controller="plant"
params="[parentResource: 'garden', pid: gardenInstance.id]">Add Plant</g:link>
//show list of plants within a garden
<g:link mapping="listChildResources" controller="plant"
params="[parentResource: 'garden', pid: gardenInstance.id]">List plants for Garden</g:link>
Shown here it's pretty verbose, but you could put all of this into TagLib and have something like.
<g:restShow resource="garden"
id="${gardenInstance.id}">${gardenInstance.name}</g:restShow>
<g:restCreate" resource="plant"
parent="${gardenInstance}">Add Plant</g:restCreate>

Related

Grails: URL parameter in url mappings not interpreted as expected

I'm writing a REST API with grails and need to put a variable on the path of a method. The URL to call this method (which is delete) will look like:
http://localhost:8080/profiles/576/gameLibrary/591
I have the following in my URLMappings class:
"/gameLibrary/${inventoryId}/"(controller: "profile", action: "removeFromLibrary", method: "DELETE")
I tried variations for the mapping like "$inventory" without the curly brackets and leaving off the trailing slash. When I attempt to call this URL, I get a 404. I've created the URL report, and I see the following for the mapping in question:
Controller: profile
| DELETE | /profiles/${profileId}/gameLibrary/null | Action: removeFromLibrary |
So, it's like it's trying to interpolate "${inventoryId} instead of using it as a path variable.
Perhaps I'm misunderstanding how to create path variables. I thought this section of the documentation on embedded variables was describing the approach I took: http://docs.grails.org/latest/guide/theWebLayer.html#embeddedVariables.
Just in case it helps, posting my whole UrlMappings class:
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?(.$format)?"{
constraints {
// apply constraints here
}
}
"/games"(resources:"game")
"/gameGenres"(resources:"gameGenre")
"/siteUsers"(resources:"siteUser")
"/profiles"(resources:"profile") {
"/gameLibrary" (controller: "profile", action: "getGameLibrary", method: "GET")
"/gameLibrary" (controller: "profile", action: "addGameToLibrary", method: "PUT")
"/gameLibrary/${inventoryId}/"(controller: "profile", action: "removeFromLibrary", method: "DELETE")
"/wishList" (controller: "profile", action: "getWishList", method: "GET")
}
"/inventoryItems"(resources:"inventoryItem")
"/"(view:"/index")
"500"(view:'/error')
"404"(view:'/notFound')
}
}
Any help would be appreciated greatly.

Grails 2.5.0 UrlMappings with two consecutive slashes

I have inherited a Grails 2.2.4 UrlMappings (partially reproduced below) that I am trying to upgrade to 2.5.0.
Named mappings api0 & api2 work, but the two consecutive slashes in api1 (between $controller & $id) do not seem to be matched properly in 2.5.0 (though they were matched in 2.2.4). e.g.:
PUT /api/ticket//123.json
returns a 403, despite my controller allowing PUTs for its update action.
name api0: "/api/$controller/$id?(.$format)?" {
action = [GET: 'show', PUT: 'update', POST: 'save', DELETE: 'delete']
constraints {
id(matches: /\d+/)
}
}
name api1: "/api/$controller//$id?(.$format)?" {
action = [GET: 'show', PUT: 'update', POST: 'save', DELETE: 'delete']
constraints {
id(matches: /\d+/)
}
}
name api2: "/api/$controller/$action/$id?(.$format)?" {
constraints {
id(matches: /\d+/)
}
}
I can't change the incoming URLs and/or HTTP methods (and I didn't create them), so please don't tell me to change the clients to replace the double slashes in the URLs with single slashes, or that the URLs and/or HTTP methods don't follow REST paradigms.

How to exclude specific keywords from UrlMapping in Grails?

I use the following url mapping in Grails:
"/$id"{
controller = "user"
action = "show"
}
to map urls like mydomain.com/someusername
How do I attach constrains to the url mapping to exclude keywords like "login", "logout",...
I.e.,
mydomain.com/someusername should route to mydomain.com/user/show/someusername,
mydomain.com/login should not route to mydomain.com/user/show/login.
You can use contrainsts for this mapping:
"/$id"{
controller = "user"
action = "show"
constraints {
//add a validator for $id from url mapping
id(validator: {
return !(it in ['login', 'logout'])
})
}
}
Use a filter, and redirect from it.
class UrlMappingFilters {
def filters = {
filterUrlKeywords(controller: '*', action: '*') {
def keywords = ['login', 'logout']
before = {
if (params.id in keywords) {
// redirect to start, or render error...
redirect(uri: '/')
return false
}
}
}
}
}
If you want to make it specific to a controller or action, use its name instead of the '*'.
Read more:
Official docs on Grails filters
Reference
I have done this type of thing for REST based endpoints numerous times, and Grails is smart enough to figure out what you want. Note that ordering in the UrlMappings file may be important.
For example, you can define this:
class UrlMappings {
static mappings = {
"/login" { controller: 'auth', action: 'login' }
"/logout" { controller: 'auth', action: 'logout' }
"/$id" { controller: 'user', action: 'view' }
}
}
Then when you hit "/login" you will go in the auth:login method, but when you hit "/userid" you will be sent to user:view:userid as expected.

Grails UrlMapping Redirect to keep DRY

I am working with Grails 2.1.1 and would like to add a handful of customized URLs that map to Controller Actions.
I can do that, but the original mapping still works.
For example, I created a mapping add-property-to-directory in my UrlMappings as follows:
class UrlMappings {
static mappings = {
"/add-property-to-directory"(controller: "property", action: "create")
"/$controller/$action?/$id?"{
constraints {
// apply constraints here
}
}
"/"(view:"/index")
"500"(view:'/error')
}
}
Now, I can hit /mysite/add-property-to-directory and it will execute PropertyController.create, as I would expect.
However, I can still hit /mysite/property/create, and it will execute the same PropertyController.create method.
In the spirit of DRY, I would like to do a 301 Redirect from /mysite/property/create to /mysite/add-property-to-directory.
I could not find a way to do this in UrlMappings.groovy. Does anyone know of a way I can accomplish this in Grails?
Thank you very much!
UPDATE
Here is the solution that I implemented, based on Tom's answer:
UrlMappings.groovy
class UrlMappings {
static mappings = {
"/add-property-to-directory"(controller: "property", action: "create")
"/property/create" {
controller = "redirect"
destination = "/add-property-to-directory"
}
"/$controller/$action?/$id?"{
constraints {
// apply constraints here
}
}
"/"(view:"/index")
"500"(view:'/error')
}
}
RedirectController.groovy
class RedirectController {
def index() {
redirect(url: params.destination, permanent: true)
}
}
It is possible to achieve this:
"/$controller/$action?/$id?" (
controller: 'myRedirectControlller', action: 'myRedirectAction', params:[ controller: $controller, action: $action, id: $id ]
)
"/user/list" ( controller:'user', action:'list' )
and in the action you get the values normallny in params:
log.trace 'myRedirectController.myRedirectAction: ' + params.controller + ', ' + params.action + ', ' + params.id
As of Grails 2.3, it is possible to do redirects directly in the UrlMappings, without the need for a redirect controller. So in case you ever upgrade, you can redirect in UrlMappings like so, as per the documentation:
"/property/create"(redirect: '/add-property-to-directory')
Request parameters that were part of the original request will be included in the redirect.
In recent Grails version (currently 5.x) you need to pass a Map into the redirect: property:
"/viewBooks"(redirect: [uri: '/add-property-to-directory'])
https://docs.grails.org/latest/guide/theWebLayer.html#redirectMappings

Best practices for grails index page

What is the right way to populate the model for the index page in a grails app? There is no IndexController by default, is there some other mechanism for getting lists of this and that into the model?
I won't claim that this is the right way, but it is one way to start things off. It doesn't take much to have a controller be the default. Add a mapping to UrlMappings.groovy:
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?"{
constraints {
// apply constraints here
}
}
"500"(view:'/error')
"/"
{
controller = "quote"
}
}
}
Then add an index action to the now default controller:
class QuoteController {
def index = {
...
}
}
If what you want to load is already part of another action simply redirect:
def index = {
redirect(action: random)
}
Or to really get some reuse going, put the logic in a service:
class QuoteController {
def quoteService
def index = {
redirect(action: random)
}
def random = {
def randomQuote = quoteService.getRandomQuote()
[ quote : randomQuote ]
}
}
I couldn't get Ed T's example above to work. Perhaps Grails has changed since then?
After some experimentation and some rummaging on the net, I ended up with this in UrlMappings.groovy:
"/"(controller: 'home', action: 'index')
My HomeController looks like this:
class HomeController {
def index = {
def quotes = = latest(Quote.list(), 5)
["quotes": quotes, "totalQuotes": Quote.count()]
}
}
And in views/home, I have an index.gsp file. That makes the index.gsp file in views unnecessary, so I removed it.
The good answer: If you need to populate a model for the index page, it's time to change from using a straight index.gsp to an index controller.
The evil answer: If you create a filter whose controller is '*', it'll get executed even for static pages.
In grails 1.3.6 for just adding
"/index.gsp"(uri:"/")
to UrlMappings.groovy worked fine for me. It has the same effect as adding a new controller and mappings like described before.
Below is my complete UrlMappings.groovy:
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?"{
constraints {
// apply constraints here
}
}
"/"(view:"/index")
"500"(view:'/error')
"/index.gsp"(uri:"/")
}
}

Resources