I have a model, Post, that is abstract. I have 3 child classes, call them XPost, YPost, and ZPost.
Post looks like
abstract class Post {
final String userUid;
final String documentId;
final String type;
}
and let's say XPost has an additional variable called "data". My question is, how can I access the data variable by creating a Post object.
Post post;
post = await _database.getPost(postID);
print(post.data); //it says data isn't defined for post class
I want to keep the post as the abstract type so I can decide at runtime which type of post (X, Y, Z) it is.
Method getPost returns Post so without casting, you're not going to be able to access properties on inherited classes.
If you expect different types of post to be returned by getPost you could do something like this
switch (post.runtimeType) {
case XPost:
print((post as XPost).data);
case YPost:
print((post as YPost).someotherfield);
}
I am using Grails 3.3.9 and json views 1.2.10.
In another test I have a url mapping like this
"/api/test"(resources:'test')
And I have an empty views/test folder, and I don't have a Test domain object.
But I do have a Device domain entity, and have a views/device/_device.gson template in that directory to help render a device object.
The TestController's show action looks like this
def show (Device device) {
if(device == null) {
render status:404
} else {
respond device}
}
This looks for a Device in domain model and populates into Shows parameter list, and I respond with the device.
There is however no show.gson in the views/test directory (it's empty).
However when you hit the url in the browser "http://localhost:8080/api/test/1", grails renders a response using the "views/device/_device.gson" template.
This is very confusing as it's very hard to figure exactly view view respond is actually using (unless you force it using a view:'xxx' map variable.
How/why is the TestController/show action response return, picking up "views/device/_device.gson" template ?
(Posted answer on behalf of the question author).
Step 1) if you create a new controller (I used grails create-controller <controller>, and then edit the generated controller to extend from RestfulController<domain type> (but don't override any methods). Then Setup the URL mapping for your controller, in my example this was
"/api/org"(resources:'OrgRoleInstance')
for which I have the corresponding domain class 'OrgRoleInstance' in my model.
The create-controller action also generates an empty view package 'views/orgRoleInstance' (no gson files are generated)
a) if you do the run app now and open the url
http://localhost:8080/api/org
then your still get a rendered response (like this) !
[{"id":1,"sites":[],"domains":[],"role":{"enumType":"com.softwood.domain.OrgRoleInstance$OrgRoleType","name":"Service_Provider"},"name":"Vodafone","mags":[{"id":2}]},{"id":2,"sites":[],"domains":[],"role":{"enumType":"com.softwood.domain.OrgRoleInstance$OrgRoleType","name":"Maintainer"},"name":"Cisco","mags":[{"id":1}]},{"id":3,"sites":[],"domains":[],"role":{"enumType":"com.softwood.domain.OrgRoleInstance$OrgRoleType","name":"Supplier"},"name":"Cisco","mags":[]},{"id":4,"sites":[{"id":1},{"id":2}],"domains":[{"id":1}],"role":{"enumType":"com.softwood.domain.OrgRoleInstance$OrgRoleType","name":"Customer"},"name":"Acme","mags":[]}]
Ipso facto there is some 'default framework logic' that tries to render the default implementation of the 'index' (and 'show' etc) action inherited from RestfulController'. This is done even with with no pre-existing gson - no warnings are provided.
Step 2: next stop the app (it's still caching the previous 'default' behaviour).
Now go to the empty view directory and create an 'index.gson' like this with no model
json ([1,2,3])
Then restart the app in your IDE. Now when it starts, it detects that an index.gson exists, and when you call the url 'http://localhost:8080/api/org' what you get is the rendered json for the static list [1,2,3]
step 3: gson views are using static compilation under the covers and very fiddly to get exactly right. However in the case of default RestfulController index action , a list of OrgRoleInstances is selected from the domain model and passed as a lit to the view. if you want the databinding to the view to work, then if your domain object is type T, the you get a List<T> returned.
The internal default in the code base, is if you get List<T> returned to respond then, the data model in the view is assumed to be List<T> <T>List i.e. in my example this would be
List<OrgRoleInstance> orgRoleInstanceList in the gson view model. Now with a revised index.gson (you can edit this without stop/start of server in dev mode)
import com.softwood.domain.OrgRoleInstance
model {
List<OrgRoleInstance> orgRoleInstanceList
}
json {
recordCount orgRoleInstanceList.size()
}
Now when you Get the URL you get a json response with the size of the list:
{"recordCount":4}
Note that if you add an extra variable to your model like this:
import com.softwood.domain.OrgRoleInstance
model {
List<OrgRoleInstance> orgRoleInstanceList
Integer orgRoleInstanceCount
}
json {
recordCount orgRoleInstanceList.size()
size orgRoleInstanceCount
}
and modify the index.gson to print the new Integer variable - then it is not data bound from the default respond action.
Browser response looks like this:
{"recordCount":4,"size":null}
Option 4: - look at invoking a 'template' class. Having set up a model that's expecting a List<T>, the runTime type built via data binding is an instance of "grails.orm.PagedResultList". This is an Iterable type.
This is very confusing to read in the documentation - it's really not clear however if you create a file called _<file>.gson. Then this is treated is a template gson file.
This can have its own model/json, but because all this is statically compiled you have to get the types exactly matching and where necessary declare the 'name' of the model variable when calling the template (either via g.render or implicit tmpl.<file>.
When you call the template from the parent view you can pass either an the List<T> iterable model type or iterate in the parent view and pass each <T> to the template. You have to ensure that the model type you pass to the tmpl is declared as the same type in the tmpl model.
e.g. assuming a List<T> in the parent view but a <T> in the template, you need to invoke the tmpl for each item in the list. e.g.
if you have a tmpl like this "_orgRoleInstance.gson".
If you have the parent view like this (note single <T> declared and model variable named 'org'
import com.softwood.domain.OrgRoleInstance
model {
OrgRoleInstance org
}
json {
id org.id
name org.name
}
Then the parent view "index.gson" you need something like this that invokes the tmpl as many times as you have entries in the list, but I've had to tell the framework that the tmpl model variable name is 'org' by passing a map. This will render as you expect.
import com.softwood.domain.OrgRoleInstance
model {
List<OrgRoleInstance> orgRoleInstanceList
Integer orgRoleInstanceCount
}
orgRoleInstanceList.each { OrgRoleInstance org ->
json tmpl.orgRoleInstance(org:org)
}
If you declare the tmpl variable to be "def org", it still works but as this is statically typed, the variable is passed as static instance of Object (run time type is correct, but you cant just access the properties as the static type is Object), and it gets hard sorting out the casts required to access the properties.
If you want the parent view to pass the model List<T> variable to the tmpl, you can = but you have to ensure that the model variable in the tmpl is List<T> else the data binding doesn't work.
Now in the template you can invoke the json and iterate over the List
e.g. with a modified tmpl like this
import com.softwood.domain.OrgRoleInstance
model {
//OrgRoleInstance org
List<OrgRoleInstance> orgs
}
json {
id orgs.id
name orgs.name
}
and a revised parent view like this to invoke the tmpl:
import com.softwood.domain.OrgRoleInstance
model {
List<OrgRoleInstance> orgRoleInstanceList
Integer orgRoleInstanceCount
}
/*
orgRoleInstanceList.each { OrgRoleInstance org ->
json tmpl.orgRoleInstance(orgs:org)
}*/
//alternate approach
json tmpl.orgRoleInstance (orgs:orgRoleInstanceList)
What get rendered is this:
{"id":[1,2,3,4],"name":["Vodafone","Cisco","Cisco","Acme"]}
You will note that there is a single json clause so it iterates all the ids first and the all the names second.
If this is not what you want you have to iterate over the list to do each in turn
i.e. a modified tmpl like this will iterate over each entry in turn
import com.softwood.domain.OrgRoleInstance
model {
//OrgRoleInstance org
List<OrgRoleInstance> orgs
}
json (orgs) {OrgRoleInstance org ->
id org.id
name org.name
}
and produce this in the browser
[{"id":1,"name":"Vodafone"},{"id":2,"name":"Cisco"},{"id":3,"name":"Cisco"},{"id":4,"name":"Acme"}]
For anyone else who is confused by Grails Views, I hope this shows how this works between url mappings (you can use the Gradle "urlMappingsReport" to see these) and shows what urls are mapped to what actions. You then need gson views created with the same name as these actions, and configure your gson views and any tmpl you create to be aware of the implicit behaviour that the JsonViews applies when processing your code.
Given the following classes:
Simplified example:
class Query {
Institution institution
}
class Institution {
String name
}
With the following parameters being submitted: query.institution.id=20 and query.institution.name=Example
I would like to include only the institution id and ignore the name from being bound to the query's institution instance.
Is it possible with bindData to explicitly include associated instance's that are nested multiple levels like this?
I haven't seen any examples of this, aside from using the prefix for a single level of nesting, and the following does not seem to work:
Simplified example:
bindData(queryInstance, params, [include: [
'institution.id',
]], 'query')
The best practice is to filter your request parameters through a command object, which can then be used to generate any kind of query.
Command cmd = Command.getInstance()
bindData(cmd, request.params)
if (cmd.validate()) Query query = cmd.generateQuery()
This way you get the benefit of binding only to fields you expose on the command object, while validating and transforming incoming data without involving your domain.
Using ODataController In one to many to many or in many-to-many relationship, how can I GET child of child entities.
For example in the OData 4 Sample Service here:
https://github.com/OData/ODataSamples/blob/master/Scenarios/TripPin/src/webapi/ODataSamples.WebApiService/Controllers/PeopleController.cs
I need to know how to implement something like:
public class PeopleController : ODataController
{
...
[ODataRoute("People({key})/Trips/PlanItems)]
public IQuerable<PlanItems> GetPlanItems([FromODataUri] string key])
}
That means I want to retrieve all PlanItems for a Person.
Unfortunately, all methods implement [ODataRoute("People({key})/Trips({tripId})/PlanItems)] only
The URL convention .../People(key)/Trips/PlanItems is not valid as you can see from the error message if you try "The request URI is not valid. Since the segment 'Trips' refers to a collection, this must be the last segment in the request URI or it must be followed by an function or action that can be bound to it otherwise all intermediate segments must refer to a single resource." So the alternative is using URL .../People(key)/Trips?$expand=PlanItems
I have object has XML as a string. For example I have a domain which has
class person{
String personId
String personName
String personType
String personDescription
String personDetailsXML
}
I am getting the details and binding to person object. I have to pass this object to another controller which displays the info about each person when he clicks on the profile name. How do i parse the XML string.
I have another domain say eachPerson domian which has
class eachPerson{
String personName
String personDescription
Object personDetails
I want to match the person name and person description and persondetailsXml.How do I do that and how can I parse personDetailsXML to personDetails object. Please suggest. How i can pass the personInstance as object to action show() in eachPerson controller??
I'd check this out for starters. Should be pretty straightforward to work from this example.
http://groovy.codehaus.org/Reading+XML+using+Groovy%27s+XmlParser.
(Upon re-reading, this doesn't entirely answer the question...)
You need to parse the xml (using introduced in the link in the comment above - or with XmlSlurper). This is pretty straightforward and easy to understand.
If you have all the data you can use render(action:'show', controller:'eachPerson', model:[persons:personsData]). See the grails doc for further details how to use render.
This will call the action with the given data. In the show action you can access it with params.persons.