I wanted to implement a default sort order in my domain class and immediately found it didn't work with the getAll method. No biggie, I just used list instead. The thing is that the default sort order in a domain class does not allow you specify multiple sort fields (as seen here).
My goal is to sort all Foo objects first by the name of their Bar object, then by their own name.
class Foo {
String name
String Bar
}
class Bar {
String name
}
How can I implement this in the domain class so I don't have to specify a long/nasty comparator every time I call .list()?
One of my attempts:
static Comparator getComparator() {
def c = { a, b ->
def result = a.bar.name.compareTo( b.bar.name );
if ( result == 0 ) {
result = a.name.compareTo( b.name );
}
}
return c as Comparator
}
Then I could just call Foo.list(Foo.getComparator())... if I could get it to work.
Update:
I think I am really close here, just having trouble with implementing two comparisons in the same sort closure.
Foo.list().sort{ a, b ->
def result = a.bar.name <=> b.bar.name;
// Things mess up when I put this if statement in.
if( result == 0 ) {
a.name <=> b.name
}
}
Disco!
class Foo { // My domain class
// ...
static Comparator getComparator() {
def c =[
compare: { a, b ->
def result = a.bar.name <=> b.bar.name;
if( result == 0 ) {
result = a.name <=> b.name
}
return result
}
] as Comparator
}
// ...
}
And implemented like this in my controller:
Foo.list().sort( Foo.getComparator() )
PS:
The above works, but Jeff Storey posted some code in his answer after I discoed, and his code works and is much nicer than mine so use it :)
In your case, would it make sense to have Foo implement Comparable and the implementation could do the comparison as you described? Then when you sort the objects in a list, because they are Comparable, they will sort properly.
If it does not make sense for you to implement Comparable though, you will need to specify a comparator to sort by.
Here's some sample code based on your comments:
edit:
class Person implements Comparable<Person> {
String firstName
String lastName
int compareTo(Person other) {
int lastNameCompare = lastName <=> other.lastName
return lastNameCompare != 0 ? lastNameCompare : firstName <=> other.firstName
}
String toString() {
"${lastName},${firstName}"
}
}
def people = [new Person(firstName:"John",lastName:"Smith"), new Person(firstName:"Bill",lastName:"Jones"), new Person(firstName:"Adam",lastName:"Smith")]
println "unsorted = ${people}"
println "sorted = ${people.sort()}"
This prints:
unsorted = [Smith,John, Jones,Bill, Smith,Adam]
sorted = [Jones,Bill, Smith,Adam, Smith,John]
To further simplify the above post (I would have commented on it but I don't have the rep yet), you can chain the groovy compare operators using the elvis operator:
class Person implements Comparable<Person> {
String firstName
String lastName
int compareTo(Person other) {
return lastName <=> other.lastName ?: firstName <=> other.firstName
}
String toString() {
"${lastName},${firstName}"
}
}
def people = [new Person(firstName:"John",lastName:"Smith"), new Person(firstName:"Bill",lastName:"Jones"), new Person(firstName:"Adam",lastName:"Smith")]
println "unsorted = ${people}"
println "sorted = ${people.sort()}"
This will give you the same result because 0 is considered false in groovy's eyes, which will make it look at the next conditional in the chain.
Related
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 "
}
orderselector:
Func<Model.Candidate, object> orderselector = p => p.UpdateDate;
SelectListbyDesc method call:
helper.Result= Model.GeneralDataAccessor.SelectListbyDesc<Model.Candidate,
OrgCandidateSearch.Result, object>(predicate, selector, orderselector,
(model.PageNo - 1) * model.Count, model.Count);
I would like the helper.Result to have the result in descending order based on UpdateDate.
But the result is in ascending order. SelectListbyDesc works fine for anything else.
Can I write sth like (p=>p.UpdateDate).OrderByDescending() in the first place?
SelectListbyDesc:
public static List<T2> SelectListbyDesc<T1, T2, T3>(Expression<Func<T1, bool>> predicate, Func<T1, T2> selector, Func<T1, T3> keySelector,
int skip = 0, int take = 0, bool addRelationalProperties = true)
where T1 : class
{
string typeName = GetTypeName<T1>();
using (ObjectContext context = GetContext())
{
ObjectSet<T1> objectSet = context.CreateObjectSet<T1>();
if (addRelationalProperties)
{
if (take == 0)
return Include(objectSet, GetIncludeProperties<T1>()).Where<T1>(predicate).OrderBy<T1, T3>(keySelector).Select<T1, T2>(selector).ToList<T2>();
else
return Include(objectSet, GetIncludeProperties<T1>()).Where<T1>(predicate).OrderBy<T1, T3>(keySelector).Skip(skip).Take(take)
.Select<T1, T2>(selector).ToList<T2>();
}
else
{
if (take == 0)
return objectSet.Where<T1>(predicate).OrderByDescending<T1, T3>(keySelector).Select<T1, T2>(selector).ToList<T2>();
else
return objectSet.Where<T1>(predicate).OrderByDescending<T1, T3>(keySelector).Take(take).Select<T1, T2>(selector).ToList<T2>();
}
}
}
Well, if there is no way to change the way items are ordered, there is a way to change to sorting value to something ordering.
Func<Model.Candidate, object> orderselector = p => DateTime.MaxValue - (p.UpdateDate - DateTime.MinValue);
Logic is like this: p.UpdateDate - DateTime.MinValue gives a time span for p.UpdateDate, and subtracting it from DateTime.MaxValue gives us the DateTime instance opposite to what we had initially.
However note that this looks more like a hack than a real solution. Real solution is of course to implement proper conditional ordering in SelectListbyDesc`.
I have domain classes A and B as follows:
class A {
String prop1
String prop2
B prop3
static embedded = ['prop3']
}
class B {
String prop4
String prop5
}
When I want to query like this:
def q = A.where { prop3.prop4 == 'bla' }
def list = q.list()
I get the following exception:
Cannot get property 'javaClass' on null object. Stacktrace follows:
on the "def q = A.where ..." line.
Any clue what's the problem? I've checked this:
http://grails.1312388.n4.nabble.com/GORM-embedded-object-issue-td1379137.html
but how to "just call them directly" is not quite clear to me. Any other way of querying the embedded objects in GORM?
I finally gave up on the where query and went with the DetachedCriteria approach. Gives me the same flexibility as the where queries, but works with embedded domain objects:
def criteria = new DetachedCriteria(A).build {
eq 'prop1', 'bla2'
}
criteria = criteria.build {
eq 'prop3.prop4', 'bla'
}
def list = criteria.list()
What do you get if you do (assuming B is in src/groovy)
def q = A.where { prop3 == new B(prop4: 'bla') }
def list = q.list()
Embedded components are persisted inside the main domain class (owner) itself. It can be accessed directly using any dynamic finder as you do directly on a domain object.
The above can also be represented in dynamic finders as:
A.findAllByProp3(new B(prop4: 'bla'))
I'm trying for several days to receive a list from my Data. The Domain looks like this:
class Alpha {
String a
String b
etc.
static hasMany = [beta:Beta]
}
class Beta {
String a
Integer foo
String status
static belongsTo = [alpha:Alpha]
static constraints = {
status(nullable:false, inList:["val1","val2","val3", "val4"])
}
}
I'd like to have in Alpha the sum of all Beta.foo and of all Beta.foo in a certain status. Best would be something like an additional row ( Integer sumVal1 ... ).
I tried named queries:
static namedQueries = {
erledigterProbeAufwend {
createAlias ('Beta', 'b')
eq ('b.status', 'val1')
projections {
groupProperty('b.alpha')
sum('b.foo', 'sumFooVal1')
}
}
}
But this just give me one sum at a time.
I'm looking forward to get some help on that.
Greetings
Bas
This could be calculated formula field, but with a subquery trick:
static mapping = {
betaCount formula: "(SELECT count(*) FROM Beta b WHERE b.alpha_id = id and b.status in('a', 'b'))"
}
Create transient variables in your Alpha class and populate them in an onLoad event.
class Alpha {
String a
String b
etc.
static transients = ["sumVal1",...]
static hasMany = [beta:Beta]
def onLoad = {
sumVal1 = ....
}
}
Is this possible to convert in createCriteria()?
SELECT * FROM node WHERE (node.type = 'act' AND nid NOT IN (SELECT nid FROM snbr_act_community)) LIMIT 10
I know there's a 'in' operator and here's what I have so far:
def c = VolunteerOpportunity.createCriteria()
def matchingActs = c.list {
node {
eq('type', 'act')
}
maxResults(10)
}
Just want to see if this is possible. Otherwise, I guess this is possible in HQL right?
thanks Sammyrulez for the code. got an idea from that. tested it but it didn't work. i fixed it and here's the final working code:
def ids = [14400 as long, 14401 as long]
def c = VolunteerOpportunity.createCriteria()
def matchingActs = c.list {
node {
eq('type', 'act')
not { 'in'(ids) }
}
maxResults(10)
}
now i know how to use 'not' operator. thanks a lot!
not tried it myself but looking at the Grails doc and hibernate api you create nodes on this builder map with the static methods found in the Restrictions class of the Hibernate Criteria API 1. So something like
def c = VolunteerOpportunity.createCriteria()
def matchingActs = c.list {
node {
not(in('propertyName', ['val1','val2']))
}
maxResults(10)
}
Since you chain the in method (that returns a Criterion) with the not method (that takes a Criterion as argument and returns a negated version)
this is the solution :
def resultat=EnteteImputationBudgetaire.createCriteria().get{
between("dateDebutPeriode", dateDebut, dateFin)
and{ eq 'natureImputationBudgetaire','FONCTIONNEMENT' }
maxResults(1)
}
def resultat2=ParametragePlanBudgetaire.createCriteria().list() {
like('composantBudgetaire','6%')
if(resultat?.details) {
not {
'in'('composantBudgetaire',resultat?.details?.imputationBudgetaire)
}
}
}
According to Grails documentation about creating criteria here, you can use something like this:
not {'in'("age",[18..65])}
In this example, you have a property named "age" and you want to get rows that are NOT between 18 and 65. Of course, the [18..65] part can be substituted with any list of values or range you need.
Just remembering: in this case you don't have to use parenthesis and you can use inList, for example:
not { inList 'age',[18..65] }