Nested resource in RestfulController problem

368 views
Skip to first unread message

Michael Engelhart

unread,
Feb 19, 2015, 7:07:05 PM2/19/15
to grails-de...@googlegroups.com
Hi all -

I commented on a JIRA yesterday related to what I'm perceiving as a bug in the way RestfulController handles creating resources that have nested hasMany relationships on them.

The JIRA was opened by another person but his issue was similar enough to what I was running into that I piggybacked on it.   


Per discussion in JIRA and to clarify the problem I'm running into I created a very simple Grails app with a very simple AngularJS front end.  The code is here and the README should provide details on how to trigger the problem.


I'd appreciate if someone could confirm for me whether or not this is in fact a bug or if this was an intentional implementation decision.    The thing that is bothersome to me is that with one minor change to the way the models are constructed I can make it work.  The problem is making that change (removing a belongsTo) creates a join table that I don't want to have in my application as well as not allowing a back reference.

Thanks for any information  and let me know if anyone has any questions or needs further clarification (or if for some reason the application doesn't work for any of you). 


Mike

Jeff Scott Brown

unread,
Feb 19, 2015, 7:20:31 PM2/19/15
to grails-de...@googlegroups.com

> On Feb 19, 2015, at 6:07 PM, Michael Engelhart <mike.en...@gmail.com> wrote:
>
>
>
> I'd appreciate if someone could confirm for me whether or not this is in fact a bug or if this was an intentional implementation decision. The thing that is bothersome to me is that with one minor change to the way the models are constructed I can make it work.

I don’t see anything in the behavior described in the JIRA that I would call a bug. I think it is all behaving as designed.



JSB
--
Jeff Scott Brown
jbr...@pivotal.io

Autism Strikes 1 in 166
Find The Cause ~ Find The Cure
http://www.autismspeaks.org/



Michael Engelhart

unread,
Feb 19, 2015, 7:29:22 PM2/19/15
to grails-de...@googlegroups.com
Can you enlighten me as to why removing the belongsTo on the model allows the cascade to work?   That's the part I don't understand.   

Jeff Scott Brown

unread,
Feb 19, 2015, 7:31:15 PM2/19/15
to grails-de...@googlegroups.com

> On Feb 19, 2015, at 6:29 PM, Michael Engelhart <mike.en...@gmail.com> wrote:
>
> Can you enlighten me as to why removing the belongsTo on the model allows the cascade to work? That's the part I don't understand.


When you add the belongs to the way you are that adds a new property to the child which points back to the parent and that property is non-nullable. Because that property isn’t initialized, it being null causes the containing object to fail validation, which it should.

Does that make sense?

Jeff Scott Brown

unread,
Feb 19, 2015, 7:35:29 PM2/19/15
to grails-de...@googlegroups.com

> On Feb 19, 2015, at 6:31 PM, Jeff Scott Brown <jbr...@pivotal.io> wrote:
>
>
>> On Feb 19, 2015, at 6:29 PM, Michael Engelhart <mike.en...@gmail.com> wrote:
>>
>> Can you enlighten me as to why removing the belongsTo on the model allows the cascade to work? That's the part I don't understand.
>
>
> When you add the belongs to the way you are that adds a new property to the child which points back to the parent and that property is non-nullable. Because that property isn’t initialized, it being null causes the containing object to fail validation, which it should.
>
> Does that make sense?
>
>


Another point is that if you remove the belongsTo you may or may not be getting the behavior that you really want. If your intent is to have the child associated with the parent then the way the controller is currently written you should note that relationship is not being established. I think the app would just be persisting a BlogPost and not associating it with a Blog. There isn’t any magic that is calling addToBlogPosts(…) to add the child to the parent.

Michael Engelhart

unread,
Feb 19, 2015, 7:58:09 PM2/19/15
to grails-de...@googlegroups.com
Yes I understand that and looking through the logs it's clear the null is really a validation error.   I just assumed (wrongly) that because the data binding added the BlogPost objects to the Blog object that it would behave as if addToBlogPosts() was called on the object.  Looks like overriding createResource and looping through the collection of BlogPosts and calling addToBlogPosts() on the Blog might work.  

Thanks for your help

Jeff Scott Brown

unread,
Feb 19, 2015, 8:12:46 PM2/19/15
to grails-de...@googlegroups.com

> On Feb 19, 2015, at 6:58 PM, Michael Engelhart <mike.en...@gmail.com> wrote:
>
> I just assumed (wrongly) that because the data binding added the BlogPost objects to the Blog object that it would behave as if addToBlogPosts() was called on the object.

It may be that I am misunderstanding what you mean by that but I don’t think that the data binding is adding the BlogPosts to a Blog. To which scenario are you referring in which that is happening?

Michael Engelhart

unread,
Feb 20, 2015, 7:16:12 AM2/20/15
to grails-de...@googlegroups.com
Well if I override save() in my controller like this:

class BlogController extends RestfulController<Blog> {
static responseFormats = ['json','xml']
BlogController() {
super(Blog)
}
def save(Blog blog) {
println blog.dump()
blog.save()
}

}

The println dumps out a Blog that as an array of posts which includes the data from POST'd from the AngularJS front end in the sample code I submitted.   This is why this feels like a bug to me because Grails already has the data structure setup correctly it's just not cascading the save correctly.

The workaround that does work but feels like boilerplate code the framework should handle is below.   As you can see I'm looping through an array on the hasMany [posts:BlogPost] that's already there and then having to re-add them to the parent object.  This just feels wrong conceptually to me.   If I end up going this route I'm going to have to write comments in every controller explaining what is going on as I think a new developer working on the codebase would be baffled by this.

  @Override
  protected Blog createResource() {
    Blog blog = super.createResource()
    if (blog.posts) {
      blog.posts.each {post ->
        blog.addToPosts(post)
      }
    }
    blog
  }

Michael Engelhart

unread,
Feb 20, 2015, 7:21:45 AM2/20/15
to grails-de...@googlegroups.com
Sorry here's the output from the console for the save() example below

<com.example.Blog@39465f68 blogName=Grails Nested Resource Issue Blog errors=grails.validation.ValidationErrors: 0 errors $changedProperties=null id=null version=null posts=[com.example.BlogPost : (unsaved)]>

Jeff Scott Brown

unread,
Feb 20, 2015, 8:17:52 AM2/20/15
to grails-de...@googlegroups.com

> On Feb 20, 2015, at 6:16 AM, Michael Engelhart <mike.en...@gmail.com> wrote:
>
> Well if I override save() in my controller like this:
>
> class BlogController extends RestfulController<Blog> {
> static responseFormats = ['json','xml']
>
> BlogController() {
> super(Blog)
> }
>
> def save(Blog blog) {
> println blog.dump()
> blog.save()
> }
>
>
> }
>
> The println dumps out a Blog that as an array of posts which includes the data from POST'd from the AngularJS front end in the sample code I submitted. This is why this feels like a bug to me because Grails already has the data structure setup correctly it's just not cascading the save correctly.

This is a fundamentally different scenario than the one described in the JIRA. The scenario described in the JIRA is one where a POST is being made to create instances of the child class (Pet in the JIRA example, BlogPost in your example). In your example above you are posting to the parent (Owner in the JIRA example, Blog in your example). A problem with the scenario described in the JIRA is that the children are being created based on data in the body of the request, which does not contain any information about what parent they belong to. It can be made to work as described by retrieving the parent based on a request parameter but that is all happening after the data binding is done.

At least some of this will change when https://jira.grails.org/browse/GRAILS-11263 is addressed (which may happen in 3.1). You could also get the behavior that I think you are wanting by including the id of the parent in the body of the request so the built in data binding mechanism can hook the parents up to the children automatically when creating the children.

Does that make sense?

Adam Augusta

unread,
Feb 20, 2015, 9:24:09 AM2/20/15
to grails-de...@googlegroups.com


On Thursday, February 19, 2015 at 7:20:31 PM UTC-5, jbrown wrote:

I don’t see anything in the behavior described in the JIRA that I would call a bug.  I think it is all behaving as designed.

The docs give a different impression.

Docs:
-- One line of code for enabling resources, set of resulting URL mappings
Result: All mappings work great out of the box.
-- Additional line of code for enabling nested resources, set of resulting URL mappings
Result: Almost all mappings fail counterintuitively out of the box.

GET "/owners/2/pets" returns *all* pets, not just owner[id=2]'s pets
PUT "/owners/2/pets" doesn't work unless you repeat yourself
PUT "/owners/2/pets" {owner: {id=3}, name="max"} "works"
GET "/owners/7/pets/3" works even if owner 7 doesn't exist.

You're right, though. Even in the Graeme's video, he demonstrates the custom code needed for child reads, although his example doesn't handle updates and deletes.

So yup, not a bug. As much as I'd love nested resources to work as cleanly as the docs indicate, the remedy here is to make it clear that nested resources requires careful extension of RestfulController.

You'll find documenting the appropriate extensions to be a challenge, though. I had to override queryForResource, listAllResources, createResource, and delete.

-Adam

Jeff Scott Brown

unread,
Feb 20, 2015, 10:02:15 AM2/20/15
to grails-de...@googlegroups.com

> On Feb 20, 2015, at 8:24 AM, Adam Augusta <rox...@gmail.com> wrote:
>
> You'll find documenting the appropriate extensions to be a challenge, though. I had to override queryForResource, listAllResources, createResource, and delete.


I am curious about overriding delete. Why did you have to do that?

Michael Engelhart

unread,
Feb 20, 2015, 1:59:11 PM2/20/15
to grails-de...@googlegroups.com
Well right and I shouldn't have jumped onto Adams post because his issue was different - I thought originally it was the same problem and it's not.

My situation which if you look at the sample app I put in Github is that I don't have the parent id.  I'm trying to create a set of resources which is made up of a parent object (unsaved) and the children (unsaved).   This type of thing works as I expect in RoR for example.  And the odd thing IMO here with Grails is that it works just fine if you code something like this on the server side and use addToXXX() methods to add the children to the collection.   When using addToXXX() to a parent that is unsaved and the children aren't saved, when I save the parent the parent's newly generated id is set on the children before they're saved and doesn't cause validation errors in the child (which is the root of the problem here). So it seems some magic must be going on in the addToXXX() dynamic methods that I don't understand. 

BTW GRAILS-11263 isn't addressing the same problem - it's addressing adding params *and* a body request.  In my scenario you have a single body request object made up of JSON.

Anyway - I'm fine that it doesn't work that way, I just feel like the implementation is missing a very common use case where you want to save a parent and it's children being sent as JSON into a RestfulController all in one transaction instead of having to manipulate parent/child id's by hand.

Thanks
Mike

Adam Augusta

unread,
Feb 20, 2015, 4:21:14 PM2/20/15
to grails-de...@googlegroups.com
On Friday, February 20, 2015 at 10:02:15 AM UTC-5, jbrown wrote:

> On Feb 20, 2015, at 8:24 AM, Adam Augusta <rox...@gmail.com> wrote:
>
> You'll find documenting the appropriate extensions to be a challenge, though. I had to override queryForResource, listAllResources, createResource, and delete.

I am curious about overriding delete.  Why did you have to do that?  

I got an error to the effect that the deleted object would be repersisted on cascade.  I had to remove it from the parent collection and save the parent.

Jeff Scott Brown

unread,
Feb 23, 2015, 7:53:31 AM2/23/15
to grails-de...@googlegroups.com

> On Feb 20, 2015, at 12:59 PM, Michael Engelhart <mike.en...@gmail.com> wrote:
>
>
> BTW GRAILS-11263 isn't addressing the same problem - it's addressing adding params *and* a body request. In my scenario you have a single body request object made up of JSON.

It is correct that GRAILS—11263 isn’t addressing the same problem, but they are related. It does relate to your scenario because in your scenario the id of the parent is not present in the JSON body of the request. The child data is all represented in the body but the id of the parent is present only in the request parameters.

There is room for some of this to be improved and I think we probably will for 3.1. In particular I think GRAILS—11263 will simplify a number of different kinds of use cases.

Thanks again for the feedback.
Reply all
Reply to author
Forward
0 new messages