I'm having issues understanding how Grails Restful controllers work. I'm attempting to make a post request (see below) to a nested resource. I'm not sure I understand what I need to change to make this work, as it seems that GET requests build the Bid's association with it's parent resource Item, but when I attempt to POST I am warned that the Item cannot be blank.
Any help is appreciated!
Item.groovy
class Item {
static hasMany = [bids:Bid]
}
Bid.groovy
class Bid {
Integer ownerId
Double amount
static belongsTo = [item:Item]
static constraints = {
ownerId nullable: false
amount nullable: false
}
}
BidController.groovy
class BidController extends RestfulController<Bid> {
static responseFormats = ['json', 'xml']
BidController() {
super(Bid)
}
@Override
def getObjectToBind() {
request.parameterMap.put('itemId', params.itemId)
return request
}
}
ItemController.groovy
class ItemController extends RestfulController<Item> {
static responseFormats = ['json', 'xml']
ItemController() {
super(Item)
}
}
UrlMappings.groovy
class UrlMappings {
static mappings = {
"/items"(resources:"item") {
"/bids"(resources: "bid")
}
}
}
URL Mappings
Controller: item
| GET | /items | Action: index
| GET | /items/create | Action: create
| POST | /items | Action: save
| GET | /items/${id} | Action: show
| GET | /items/${id}/edit | Action: edit
| PUT | /items/${id} | Action: update
| PATCH | /items/${id} | Action: patch
| DELETE | /items/${id} | Action: delete
Controller: bid
| GET | /items/${itemId}/bids | Action: index
| GET | /items/${itemId}/bids/create | Action: create
| POST | /items/${itemId}/bids | Action: save
| GET | /items/${itemId}/bids/${id} | Action: show
| GET | /items/${itemId}/bids/${id}/edit | Action: edit
| PUT | /items/${itemId}/bids/${id} | Action: update
| PATCH | /items/${itemId}/bids/${id} | Action: patch
| DELETE | /items/${itemId}/bids/${id} | Action: delete
Post Request
POST /AuctionService/items/1/bids HTTP/1.1
Content-Type: application/json
Host: localhost:8080
Connection: close
Content-Length: 34
{
"ownerId": 1,
"amount": 3.00
}
Response
HTTP/1.1 422 Unprocessable Entity
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 25 Jul 2014 17:44:03 GMT
Connection: close
{"errors":[{"object":"auctionservice.Bid","field":"item","rejected-value":null,"message":"Property [item] of class [class auctionservice.Bid] cannot be null"}]}
The implementation of RestfulController has a method
getObjectToBind()
which returns the request object. I would suggest to override this method and give back a map that contains the keyitemId
as described in the comment of this method.The other alternative could be, to send the itemId in the http json body. This is a little redundant, because the information are already represented in the url. But as a workaround, this could also be a good solution.
I recommend annotating the Domain classes with @Resource even if you implement your own controller. The Grails documentation seems to indicate you do one or the other, annotate the domain or write your own controller extending the RestfulContoroller.
I found that without the domain class annotation, the request object was not bound correctly in the createResource() method. All the properties were null. As soon as I added the domain annotation back binding happened as I expected.
In your case, I expect/hope Grails will handle all the relationships without you needing to manage the item id yourself. So,
and...
Hopefully you won't need to override the getObjectToBind() any longer.
This worked for me when I was trying to post an object with a one-to-one mapping to another object.
I think you can accomplish what you want by overriding the createResource() method.
The other default controller actions will probably not work as expected when using nested URLs. You may also want to override queryForResource and index if you want to ensure that you only return Bids that belong to the item in the URL