Grails findAll with sort, order, max and offset? - grails

I want to integrate sort, order, max and offset in a findAll query. The following works fine:
def books = Book.findAll("from Book as b where b.approved=true order by b.dateCreated desc", [max: max, offset: offset])
But what I want is:
def books = Book.findAll("from Book as b where b.approved=true", [sort: 'dateCreated', order: 'desc', max: max, offset: offset])
This does not work. How do I have to rewrite this?

Using "findAllBy" because it supports sort and order.
def results = Book.findAllByTitle("The Shining",
[max: 10, sort: "title", order: "desc", offset: 100])
Click here for details.

HQL doesn't support sort and order as parameters, so you need to include the "order by" as part of the HQL expression
def books = Book.findAll("from Book as b where b.approved=true"
+ " order by b.dateCreated desc", [max: max, offset: offset])
(or in this case just use Book.findAllByApproved(true, [...]) instead of HQL).
So if the sort and order are variables you need a trick like
def books = Book.findAll("from Book as b where b.approved=true"
+ (params.sort ? " order by b.${params.sort} ${params.order}" : ''),
[max: max, offset: offset])

Using a where query works for me:
def books = Book.where{approved == true}.list(sort: 'dateCreated', order: 'desc', max: max, offset: offset)
Or with params straight from the page:
def books = Book.where{approved == true}.list(params)

I am assuming you are calling fetching the list of books in a controller or a service class.
If you are calling this from a controller action, then a magic variable "params" is already available to you.
For example, if you request the page as follows
book/list?max=10&offset=2
then "params" will already have those values mapped automagically.
You can add more items to the params map as follows
params.sort = "dateCreated"
params.order = "desc"
Once you have build your params as desired, then you can use Grails dynamic query as follows
def books = Book.findAllByApproved(true, params)
// use "books" variable as you wish

Related

How to efficiently retrieve groups of objects from activerecord with a single SQL query?

I have a table products which has a product_type_code column on it. What I'd like to do is retrieve different numbers of objects based on this column (eg.: 3 products with product_type_code = 'fridge', 6 products with product_type_code = 'car', 9 products with product_type_code = 'house', etc.).
I know I can do like this:
fridges = Product.where(product_type_code: 'fridge').limit(3)
houses = Product.where(product_type_code: 'house').limit(9)
[...]
And even create a scope like this:
# app/models/product.rb
scope :by_product_type_code, -> (material) { where(product_type_code: product_type_code) }
However, this is not efficient since I go to the database 3 times, if I'm not wrong. What I'd like to do is something like:
scope :by_product_type_code, -> (hash) { some_method(hash) }
where hash is: { fridge: 3, car: 6, house: 9 }
and get an ActiveRecord_Relation containing 3 fridges, 6 cars and 9 houses.
How can I do that efficiently?
You can create a query using UNION ALL, which selects records having a specifc product_type_code and limit to use it with find_by_sql:
{ fridge: 3, car: 6, house: 9 }.map do |product_type_code, limit|
"(SELECT *
FROM products
WHERE product_type_code = '#{product_type_code}'
LIMIT #{limit})"
end.join(' UNION ALL ')
And you're gonna have a query like:
(SELECT * FROM products WHERE product_type_code = 'fridge'LIMIT 3)
UNION ALL
(SELECT * FROM products WHERE product_type_code = 'car'LIMIT 6)
UNION ALL
(SELECT * FROM products WHERE product_type_code = 'house'LIMIT 9)
#SebastianPalma's answer is the best solution; however if you were looking for a more "railsy" fashion of generating this query you can use arel as follows:
scope :by_product_type_code, ->(h) {
products_table = self.arel_table
query = h.map do |product_type,limit|
products_table.project(:id)
.where(products_table[:product_type_code].eq(product_type))
.take(limit)
end.reduce do |scope1, scope2|
Arel::Nodes::UnionAll.new(scope1,scope2)
end
self.where(id: query)
end
This will result in the sub query being part of the where clause.
Or
scope :by_product_type_code, ->(h) {
products_table = self.arel_table
query = h.map do |product_type,limit|
products_table.project(Arel.star)
.where(products_table[:product_type_code].eq(product_type))
.take(limit)
end.reduce do |scope1, scope2|
Arel::Nodes::UnionAll.new(scope1,scope2)
end
sub_query = Arel::Nodes::As.new(query,products_table)
self.from(sub_query)
end
This will result in the subquery being the source of the data.

Grails Criteria distinct doesn't work

In my app I use createCriteria for getting a list according some criteria.
roleMapping contains user.
I use the following code:
def getTeamOfCompany(def company,def offset=0){
def c = roleMapping.createCriteria()
def result = c.list{
eq('company',company)
eq('isCurrentCompany',true)
firstResult offset
maxResults 10
distinct('user')
user{
order "lastname", "asc"
}
}
return result
}
I use the distinct in order to not get the same user twice, but it didn't work.
If I put projections on the distinct I'll get a list of users instead roleMapping

How to filter Groovy/Grails closure from web app

I have a web app that makes a call to a grails app for its db calls. The db is filled with products that are returned through groovy calls. An example object that I would get from the db is as follows:
class Product{
Boolean is_blue;
Boolean is_round;
Boolean is_alive;
Boolean is_active;
String type;
String name;
}
I want to make a call to the grails app to filter on these boolean values but I am not sure how to do it via a closure, this what my closure currently looks like.
def productXML =
Product.findAll("from Product as p where p.is_active = 1 and p.type = :type
ORDER BY p.${params.sort} ${params.order}",
[type: type], [max: params.max, offset: params.offset])
What I'm most confused on is how I can pass these parameters to the closure. Any help would be greatly appreciated. Thanks.
Something like
def productXML =
Product.findAll("from Product as p where p.is_active is :active \
and p.type = :type \
ORDER BY p.${params.sort} ${params.order}",
[type: type, active: true],
[max: params.max, offset: params.offset])
OR
def productXML = Product.findAll(params){
type == type && is_active == active
}
is what you are looking for? Refer findAll for details.
Always use substitution variables
To make parameters optional, use this trick (using type as an example):
def productXML = Product.findAll("from Product as p where p.is_active is :active
and (p.type = :type or :type == null)
ORDER BY p.:sort :order",
[type: type, active: true, sort:params.sort, order:params.order], [max: params.max, offset: params.offset])
I ended up making a query builder where if in in the query string it had is_blue=1, I would add that to the query.
if(params.is_blue){
query +=" and p.is_blue = ${params.is_blue}"
}

Grails Pagination in a search query page

I am using following query to filter results in grails.
userList = SecUser.all.findAll{it.merchants.findAll {it.name.toLowerCase()=~ searchString.toLowerCase()}.size()>0}
In this code i have Users and each User have multiple merchants. I extract only that user whose merchant name matches a certain pattern.
Now i further have to filter these users on:
params.max
params.offset
So that i can perform pagination on them. Kindly please help me with this problem.
This has not been tested, but try something like this:
def query = SecUser.where {
merchants.any { merchant ->
merchant.name.equalsIgnoreCase( searchString )
}
}
def userList = query.findAll(max: params.max, offset: params.offset)

Spring Security UI Grails only search/create/edit users for the admin?

I am currently working on a solution in Grails and I have installed the following security plugins:
Spring Security Core
Spring Security UI
I will basically have a solution with the following security structure:
Super Users
Admins(For different business areas)
Users (within the different business areas)
So basically I installed the Spring Security UI in order to allow the various Business Area Admins manage their own areas, they should be able to use the UI in order to allow them to search only for users in thier own area, create users in their own area and edit users only in their own area. However the spring security UI gives people who have access blanket access do anything.
I have added an extra field to the spring security domain model which is "Area", so I was thinking when the admin is searching for users they would only see users in the same area as them, when they create a user they can only do so for their own area and they can only edit users in their own area.
Below is some code that the spring security UI uses to search for the users, can I modify this in order to only return the users that are in the same area as the admin who is currently logged in? or is there a better way?
def userSearch = {
boolean useOffset = params.containsKey('offset')
setIfMissing 'max', 10, 100
setIfMissing 'offset', 0
def hql = new StringBuilder('FROM ').append(lookupUserClassName()).append(' u WHERE 1=1 ')
def queryParams = [:]
def userLookup = SpringSecurityUtils.securityConfig.userLookup
String usernameFieldName = userLookup.usernamePropertyName
for (name in [username: usernameFieldName]) {
if (params[name.key]) {
hql.append " AND LOWER(u.${name.value}) LIKE :${name.key}"
queryParams[name.key] = params[name.key].toLowerCase() + '%'
}
}
String enabledPropertyName = userLookup.enabledPropertyName
String accountExpiredPropertyName = userLookup.accountExpiredPropertyName
String accountLockedPropertyName = userLookup.accountLockedPropertyName
String passwordExpiredPropertyName = userLookup.passwordExpiredPropertyName
for (name in [enabled: enabledPropertyName,
accountExpired: accountExpiredPropertyName,
accountLocked: accountLockedPropertyName,
passwordExpired: passwordExpiredPropertyName]) {
Integer value = params.int(name.key)
if (value) {
hql.append " AND u.${name.value}=:${name.key}"
queryParams[name.key] = value == 1
}
}
int totalCount = lookupUserClass().executeQuery("SELECT COUNT(DISTINCT u) $hql", queryParams)[0]
Integer max = params.int('max')
Integer offset = params.int('offset')
String orderBy = ''
if (params.sort) {
orderBy = " ORDER BY u.$params.sort ${params.order ?: 'ASC'}"
}
def results = lookupUserClass().executeQuery(
"SELECT DISTINCT u $hql $orderBy",
queryParams, [max: max, offset: offset])
def model = [results: results, totalCount: totalCount, searched: true]
// add query params to model for paging
for (name in ['username', 'enabled', 'accountExpired', 'accountLocked',
'passwordExpired', 'sort', 'order']) {
model[name] = params[name]
}
render view: 'search', model: model
}
EDIT....
I believe it may have something to do with the code below:
def results = lookupUserClass().executeQuery(
"SELECT DISTINCT u $hql $orderBy",
queryParams, [max: max, offset: offset])
I think I just need to alter this statement so that it looks for the list of users where the currently logged in users "Area" is equal to the same area as the users. Can anyone please help me with this??
EDIT 2.....
I have now looked into this and have been able to obtain the users Area and now alls I need to do is to modify the query to the database to look for the users that have the same Area as the admin searching. I have tried the following with no luck, can someone please help me with this as I know this must be simple just cant seem to get there :-S
def user = springSecurityService.currentUser
def userArea = user.area
def hql = new StringBuilder('FROM ').append(lookupUserClassName()).append(' u WHERE 1=1 AND u.area = userArea')
EDIT 3.......
Thanks so much half of my problem is solved lol, now just the Ajax piece:
I have tried the below code in order to modify the search for the Ajax function to only return results where the Area of the user is the same as the currently logged in user:
String username = params.term
String usernameFieldName = SpringSecurityUtils.securityConfig.userLookup.usernamePropertyName
def user = springSecurityService.currentUser
setIfMissing 'max', 10, 100
def results = lookupUserClass().executeQuery(
"SELECT DISTINCT u.$usernameFieldName " +
"FROM ${lookupUserClassName()} u " +
"WHERE LOWER(u.$usernameFieldName) LIKE :name AND LOWER(u.area) = :area " +
"ORDER BY u.$usernameFieldName",
[name: "${username.toLowerCase()}%"],
[area: "user.area"],
[max: params.max])
Also tried changing the param as below:
[area: user.area]
The controller is building an HQL query, so you can't just say "WHERE u.area = userArea", you'll need to use a named parameter and put the value in the queryParams map
def user = springSecurityService.currentUser
def hql = new StringBuilder('FROM ').append(lookupUserClassName()).append(
' u WHERE u.area = :userArea ')
def queryParams = [userArea:user.area]
For the second part of the problem (the Ajax bit), I doubt you need the LOWER conversion, and also you need to put all your query parameters into one map (the second map parameter is just for the pagination settings):
def results = lookupUserClass().executeQuery(
"SELECT DISTINCT u.$usernameFieldName " +
"FROM ${lookupUserClassName()} u " +
"WHERE LOWER(u.$usernameFieldName) LIKE :name AND u.area = :area " +
"ORDER BY u.$usernameFieldName",
[name: "${username.toLowerCase()}%", area:user.area],
[max: params.max])
If you really do want the area check to be case-insensitive then leave it as LOWER(u.area) = :area but then you also need to convert the value you are testing against to lower case:
[name: "${username.toLowerCase()}%", area:user.area.toLowerCase()],

Resources