Groovy/Grails - How to examine relations in Domain class? - grails

I'm writing a tag that essentially needs to dump an arbitrary domain class for the fields requested via parameters to the tag. This works fine if the field is a normal attribute. But, if it's a "hasMany" relationship, what are my options?
In other words, how do I check if a string passed as a parameter to the tag corresponds to a "hasMany" relationship, and get the corresponding domain name?
Note that I can instantiate the domain class and do a getClass on it - maybe it's in the properties? I'll check, but any input is much appreciated.
To be more specific, in the following code, I need to check in if any of the names is a "hasMany" relationship as opposed to an attribute, access that domain, and print all the instances of it.
Here's the code as it exists nos:
/*
* Tag to ouput domain level information
*
*/
def get_domain_info = { attrs, body ->
// get the domain name for lookup on the Misc Fields XML table
def id = attrs['id']
def domainName = attrs['domain_name']
// get the domainInstance
def domainInstance = grailsApplication.getArtefact("Domain",domainName)?.
getClazz()?.get(id)
def dataNames = attrs['data_names']
def dataNameArray = dataNames.split(",")
out << "<div class=\"dialog\">"
for(dataName in dataNameArray) {
out << "<tr class=\"prop\">"
out << "<td valign=\"top\" class=\"name\">" + dataName + "</td>"
def dataValue = domainInstance[dataName.trim()]
if (dataValue == null){
dataValue = ""
}
def valueLine
if ( dataValue.class == java.sql.Timestamp){
valueLine = "<td valign=\"top\" class=\"value\">" +
dataValue.format("d MMM yyyy") + "</td>"
}
else {
valueLine = "<td valign=\"top\" class=\"value\">" + dataValue + "</td>"
}
out << valueLine
out << "</tr>"
}

The domain class should always have a Set or List property defined for the hasMany, e.g.,:
class Author {
static hasMany = [books:Book]
List books
}
So in that case, domainInstance[dataName] will return a List of Books. All you really need to do is see if the property is a Collection:
if(dataValue instanceof Collection) {
// handle hasMany
}
Is some weird cases, a hasMany can also be a Map. See http://www.grails.org/GORM+-+Collection+Types

Related

How can I put an if statement inside of a createCriteria in grails?

I'm new in grails and I have a problem. I have a method that receive some data and match the data with a createCriteria and return the data. It's working fine, but that I want to do now if match with the five params that I have in the method and if match return the data, if not match with the five params, try if match with four params and return the data, if not match with the four params, try if match with three and return the data....
But not really sure how can I put all this in a if statement and return my result.dataPerson or maybe I have to find another way to do it.
My method:
def getPersonData(String name, String surname, String address, String phone){
def searchdataPerson = ClientConfig.createCriteria()
def result = searchdataPerson.get{
and{
or{
eq('surname', surname)
isNull('surname')
}
or{
eq('address', address)
isNull('address')
}
or{
eq('phone', phone)
isNull('phone')
}
or{
eq('name', name)
isNull('name')
}
}
maxResults(1)
}
return result.dataPerson
}
I'm trying to do something like this but it doesn't work
def searchdataPerson = ClientConfig.createCriteria()
def result = searchdataPerson .get{
if(eq('name', name) && eq('surname', surname) && eq('address', address) && eq('phone', phone)){
}else if(eq('name', name) && eq('surname', surname) && eq('address', address)){
}
maxResults(1)
}
return result.dataPerson
I get this error:
java.lang.NullPointerException: Cannot get property 'dataPerson' on null object
I can't tell from your example what you are really trying to do but you can put if statements in side the closure anywhere normal Groovy language rules would allow:
def getPersonData(String name, String surname, String address, String phone){
def searchdataPerson = ClientConfig.createCriteria()
def result = searchdataPerson.get{
and{
if(surname != 'GooglyMoogly') {
or{
eq('surname', surname)
isNull('surname')
}
}
if(address != 'Caddyshack') {
or{
eq('address', address)
isNull('address')
}
}
// ...
}
maxResults(1)
}
return result.dataPerson
}
As Jeff has pointed out above you should really refer to result and you are getting the error because you are getting a result but then trying to return an entity or maybe event object hanging off the result.
What is dataPerson ? is it a relationship hanging off of ClientConfig i.e belongsTo or hasMany declaration in ClientConfig ?
From all of above it isn't exactly clear but to try and explain it another way, you could use HQL:
String query="""
select new map(c.name as name,c.surname as surname, d as dataPerson)
from ClientConfig c
left join c.dataPerson d where
(c.surname != 'GooglyMoogly' or c.surame is null or c.surname=:surname) and
(c.address != 'Caddyshack' or c.address is null or c.address=:address) and
(c.phone is null or c.phone=:phone) and
(c.name is null or c.name=:name) and
"""
def inputParams=[surname:surname,address:address,phone:phone,name:name]
def result = ClientConfig.executeQuery(query,inputParams,[readOnly:true,timeout:15,max:1])
result?.each { output ->
println "--- ${output?.name} ???? ${output.surname} ???? "
output?.dataPerson.each { dp ->
println "--- ${dp.name} ???? "
}
}
Some of above may not be correct but may help you decipher what it is you are trying to achieve.
In the above HQL statement I have done a left join between clientConfig and dataPerson. This is assuming
class ClientConfig {
static hasMany= [dataPerson:DataPerson]
}
The left join will mean even when dataPerson does not exist it will attempt to join the (null object)
When iterating if all I wanted was the aspects hanging off of dataPerson then
result?.each { output ->
// if hasMany
println "${output.dataPerson[0].name}"
// if a belongsTo or a direct relation:
println "${output.dataPerson.name}"
}
And to save on doing any of this if you define the actual elements you wish to collect in your HQL then your final iteration will only consist of what it is you asked for exactly and then there is no need to forward walk through the class.
String query="""
select new map(d.name as name,d.surname as surname, d.address as address)
from ClientConfig c
...
You now have objects here no need to expand from result to result.dataSet ....:
result?.each { output ->
println " $output.name $output.surname $output.address "
}

Invalid column name error

I am trying to sort by transient property but fails with SQL Error: Invalid column name error .
Pls find below my code :
In domain class declared :
static transients = ['sortCandidateLastName']
Query which I am trying to execute:
When I am trying to run the below query in Oracle :It runs fine
( select * from ( select row_.* ,rownum rownum_ from ( select * from booking b where b.marked_deleted='N' order by (select c.cand_id from candidate c where b.cand_id = c.cand_id) asc ) row_ where rownum <= 15 ) where rownum > 0)
GSP code:
<g:sortableColumn property="sortCandidateLastName" title="Sort By Candidate Last Name" />
But when Hibernate is trying to read it ,it throws Invalid column name : ResultSet.getInt(clazz_)
Transient properties are not persisted so it's impossible to write a query which sorts by a transient property. If you retrieve a list of objects from a query and want to sort them by a transient property, you'll have to do it in Groovy code, e.g.
// an example domain class with a transient property
class Book {
private static Long SEQUENCE_GENERATOR = 0
String isbn
String title
Long sequence = ++SEQUENCE_GENERATOR
static transients = ['sequence']
}
// get a list of books from the DB and sort by the transient property
def books = Book.list()
books.sort { it.sequence }
You cant sort on transient field. There is no actual database column for a transient field! So your sql would always throw an error!
I tried using jdbcTemplate and gives me the required functionality.
Code snippet:
GSP Layer:
<g:sortable property="sortCandidateLastName">
<g:message code="booking.alphabetical.label" default="Alphabetical(A-Z)" />
</g:sortable>
Domain layer:
just defined a transient property :
static transients = ['sortCandidateLastName']
Reason for adding the transient field : SO that it doesn't throw me any exception like missing property.
Controller layer :
if(params.sort == "sortCandidateLastName" )
{
bookingCandList= bookingService.orderByCandidateLastName(params.max, params.order,params.offset.toInteger())//Booking.getSortCandidateLastName(params.max, params.order,params.offset.toInteger()) //
}
Service layer :
def jdbcTemplate
public List orderByCandidateLastName(Integer max, String sortOrder,Integer offset) {
println "Inside the getcandidateLastName ${max} :: offset ${offset}"
def sortedList
int minRow = offset
int maxRow = offset+max
String queryStr = " select * from " +
" ( "+
" select row_.* " +
" ,rownum rownum_ " +
" from " +
" ( " +
" select * from booking b where b.item_id= 426 and b.marked_deleted='N' order by " +
" (select c.cand_id from candidate c where b.cand_id = c.cand_id) ${sortOrder} "+
" ) row_ "+
"where rownum <= ${maxRow} " +
") " +
"where rownum > ${minRow}"
return jdbcTemplate.queryForList(queryStr)
}
Configuration of JDBC template :
// Place your Spring DSL code here
import org.springframework.jdbc.core.JdbcTemplate
beans = {
..........
jdbcTemplate(JdbcTemplate) {
dataSource = ref('dataSource')
}
}
Hope it helps to ..
Cheers

How to use "Grouped BY" with Grails criteria among three tables

If I have three domain classes like this:
class Candidate {
static belongsto=[application:Application]
}
class Vote {
String name;
static belongsto=[application:Application]
}
class Application {
Date datedemand;
Candidate candidates;
Vote votes
static hasMany = [candidates:Candidate,votes:Vote]
}
I want to retrieve all candidates grouped by Vote's Name .
I started ​​the following attempt, and I remain blocked :
def criteria = Application.createCriteria()
def results = criteria.list {
votes {
}
candidates{
}
}
In decent grails (2.x+) you could try something similar:
Application.withCriteria {
createAlias("votes", votes)
projections {
groupProperty("votes.name")
}
}
In this scenario I would go for a simple vanilla HQL instead of Criteria, something like this or more (use join the way you need):
String hql = "Select V.name, C.name " +
"from Candidate C, Vote V " +
"where C.application = V.application " +
"group by V.name, C.name"
Query query = session.createQuery(hql)
List results = query.list()

Find all instances of a type associated with another type

I have some classes that look like this:
class User {
boolean enabled
String username
}
class ExampleClass {
User firstUser
User secondUser
}
My end goal is to find all instances of User where enabled == true OR the instance of User is associated with ExampleClass.
Where this code is running I don't have access to the variable names firstUser or secondUser.
With that said, I need to be able to find all instance of User associated with ExampleClass, disregarding which variable (firstUser or secondUser) the association was made through. How do I do this?
UPDATE:
The best I can come up with is this method in my User domain class. I the example I gave above I have an ExampleClass which has multiple fields of the User type. In fact I have multiple classes with multiple fields of the User type. This is why I get the domain class from the object being passed in instead of just typing ExampleClass.
static List findAllEnabledOrAssociatedWith( Object obj = null ) {
if( obj?.id ) { // Make sure the object in question has been saved to database.
List list = []
obj.domainClass.getPersistentProperties().each {
if( it.getReferencedPropertyType() == User ) {
def propertyName = it.getName()
list += User.executeQuery( "SELECT DISTINCT ${propertyName} FROM ${obj.class.getSimpleName()} obj INNER JOIN obj.${propertyName} ${propertyName} WHERE obj =:obj", [ obj: obj ] )
}
}
list.unique()
return User.executeQuery( "SELECT DISTINCT users FROM User users WHERE users.enabled=true OR users IN(:list)", [ list: list ] )
} else {
return User.executeQuery( "SELECT DISTINCT users FROM User users WHERE users.enabled=true" )
}
}
def sql = '''
from
User u,
ExampleClass ex
where
u.enabled = 1 or (u = ex.firstUser or u = ex.secondUser)'''
def users = User.findAll(sql)

Querying values from a Map Property in a Grails Domain Class

I found the solution Burt Beckwith offered in this question: add user define properties to a domain class and think this could be a viable option for us in some situations. In testing this, I have a domain with a Map property as described in the referenced question. Here is a simplified version (I have more non-map properties, but they are not relevant to this question):
class Space {
String spaceDescription
String spaceType
Map dynForm
String toString() {
return (!spaceType)?id:spaceType + " (" + spaceDescription + ")"
}
}
I have some instances of space saved with some arbitrary data in the dynForm Map like 'Test1':'abc' and 'Test2':'xyz'.
I am trying to query this data and have succesfully used HQL to filter doing the following:
String className = "Space"
Class clazz = grailsApplication.domainClasses.find { it.clazz.simpleName == className }.clazz
def res = clazz.executeQuery("select distinct space.id, space.spaceDescription from Space as space where space.dynForm['Test1'] = 'abc' " )
log.debug "" + res
I want to know if there is a way to select an individual item from the Map dynForm in a select statement. Somehting like this:
def res = clazz.executeQuery("select distinct space.id, elements(space.dynForm['Test1']) from Space as space where space.dynForm['Test1'] = 'abc' " )
I can select the entire map like this:
def res = clazz.executeQuery("select distinct elements(space.dynForm) from Space as space where space.dynForm['Test1'] = 'abc' " )
But I just want to get a specific instance based on the string idx.
how about to use Criteria, but i don't have any sql server so i haven't tested yet.
def results = Space.createCriteria().list {
dynForm{
like('Test1', 'abc')
}
}

Resources