Reputation: 508
I'm building an REST API in Grails 2.4.4 and relying on RestfulController to handle basic CRUD type functionality. The front end is being built in AngularJS 1.3 and for the most part Angular's $resource and Grails RestfulController work perfectly and don't require that I write a lot of boilerplate code. I have one major problem though which is that a domain object with a hasMany relationship doesn't bind as expected when I POST the JSON from Angular to Grails.
For example take the following domain objects:
class Task {
String name
//auto timestamps
Date dateCreated
Date lastUpdated
static hasMany = [filters:TaskFilter]
static constraints = {
}
}
class TaskFilter {
String filterMetaData
static belongsTo = [task:Task]
static constraints = {
task column: 'task_id'
}
}
When I POST an object like this for example:
{name: "Task 1", filters: [{filterMetaData:'some-meta-data'}]}
I get the following error in Grails:
| Error 2015-02-10 17:41:34,619 [http-bio-8080-exec-5] ERROR errors.GrailsExceptionResolver - NullPointerException occurred when processing request: [POST] /api/studies/1/tasks
Stacktrace follows:
Message: null
Line | Method
->> 99 | $tt__save in grails.rest.RestfulController
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 198 | doFilter in grails.plugin.cache.web.filter.PageFragmentCachingFilter
| 63 | doFilter . . . . . in grails.plugin.cache.web.filter.AbstractFilter
| 104 | processFilterChain in com.odobo.grails.plugin.springsecurity.rest.RestTokenValidationFilter
| 71 | doFilter . . . . . in ''
| 53 | doFilter in grails.plugin.springsecurity.web.filter.GrailsAnonymousAuthenticationFilter
| 122 | doFilter . . . . . in com.odobo.grails.plugin.springsecurity.rest.RestAuthenticationFilter
| 82 | doFilter in grails.plugin.springsecurity.web.authentication.logout.MutableLogoutFilter
| 63 | doFilter . . . . . in com.odobo.grails.plugin.springsecurity.rest.RestLogoutFilter
| 82 | doFilter in com.brandseye.cors.CorsFilter
| 1145 | runWorker . . . . in java.util.concurrent.ThreadPoolExecutor
| 615 | run in java.util.concurrent.ThreadPoolExecutor$Worker
^ 745 | run . . . . . . . in java.lang.Thread
I'm not exactly sure what is going on with this error as it's pretty vague but the Task and the TaskFilter are rolled back and nothing is saved.
The weird thing is that if I get rid of this line of code from TaskFilter:
static belongsTo = [task:Task]
Everything works fine EXCEPT that this then forces me to have a task_task_filter join table instead of the much easier to grok task_id column in task_filter table.
I've been reading posts here and on various forums, blogs and mailing lists for the last couple of days and have not found a solution. Most of the posts are older so I can't tell if they apply but several simply say "You can't bind a JSONArray using Grails" which seems bizarre as it's such a common requirement for any moderately complex data model.
Anyway - if anyone can point me to something concrete and current that either says "you can't do it" or how to actually handle this situation I'd greatly appreciate it. If possible I'd rather avoid having to write custom parsing code to manage this as the rest of RestfulController works like a charm. Also if there is any explanation as why this works when I removed the belongsTo side of the relationship maybe that will help me understand what is going on here.
Just in case anyone is wondering my RestfulController subclass looks like this:
class TaskController extends RestfulController<Task> {
static responseFormats = ['json', 'xml']
TaskController() {
super(Task)
}
}
Upvotes: 0
Views: 1017
Reputation: 508
After much discussion with the grails dev team I've been informed that my understanding of how this should work is not the way Grails works. I assumed (incorrectly) that because the data binding added the TaskFilter to the Task that it would behave as if addToFilters() was called on the Task but for whatever that's not how the collection is created. I don't know what the collection actually is but it definitely does not save them..
The JIRA has been Closed as "behaves as expected".
My workaround after this is the following in my TaskController
@Override
protected Task createResource() {
Task task = super.createResource()
if (task.filters) {
task.filters.each {filter ->
task.addToFilters(filter)
}
}
task
}
Unfortunately any associations in a RestfulController based application will need this same type of code for handling nested resources. I still think Grails should handle this in the framework and I may try and add the feature to the code if I can find time to work on it.
Upvotes: 0