Reusing part of Grails criteria closure - grails

I have a fairly large criteria closure in my Grails application, and I would like to reuse part of it in several places in my application. Rather than duplicating the section I need to reuse, I'd like to define this as a separate closure and reference it wherever it is needed, but I am struggling a bit with the syntax.
This is a simplified / cut down version, but essentially my criteria looks something like this:
def criteriaClosure = {
and {
// filtering criteria that I'd like to reuse in lots of places
or {
names.each { name ->
sqlRestriction(getFilteringSql(name), [someId])
}
}
if (isOrganisationChild(childDefaultGrailsDomainClass)) {
sqlRestriction(getFilteringSql(domain), [someArg])
}
// filtering criteria that's specific to this particular method
sqlRestriction(getSomeOtherSql(), [someOtherArg])
}
}
def criteria = domain.createCriteria()
def paginatedList = criteria.list([offset: offset, max: max], criteriaClosure)
I've tried defining the part of the closure I want to reuse as a variable, and referencing it in my criteria closure, however the restrictions it defines don't seem to be applied.
def reusableClosure = {
and {
or {
names.each { name ->
sqlRestriction(getFilteringSql(name), [someId])
}
}
if (isOrganisationChild(childDefaultGrailsDomainClass)) {
sqlRestriction(getFilteringSql(domain), [someArg])
}
}
}
def criteriaClosure = {
and {
reusableClosure() //this doesn't seem to work
sqlRestriction(getSomeOtherSql(), [someOtherArg])
}
}
I'm sure this must be a pretty straightforward thing to do, so apologies if it's a daft question. Any ideas?

I think you have to pass the delegate down to the reusableClosure, ie:
def criteriaClosure = {
and {
reusableClosure.delegate = delegate
reusableClosure()
sqlRestriction(getSomeOtherSql(), [someOtherArg])
}
}

Related

How does grails pass arguments to controller methods?

In grails controller examples, I have seen save(Model modelInstance) and save(). I tried them both, both of them works. I imagine grails instantiates the modelInstance with the params. Is my assumption correct?
I also noticed in index(Integer max), does the param has to be named max? or any name would work as long as it is a number?
How does these passing of arguments work underneath?
If you write a controller like this...
class MyController {
def actionOne() {
// your code here
}
def actionTwo(int max) {
// your code here
}
def actionThree(SomeCommandObject co) {
// your code here
}
}
The Grails compiler will turn that in to something like this (not exactly this, but this describes effectively what is happening in a way that I think addresses your question)...
class MyController {
def actionOne() {
// Grails adds some code here to
// do some stuff that the framework needs
// your code here
}
// Grails generates this method...
def actionTwo() {
// the parameter doesn't have to be called
// "max", it could be anything.
int max = params.int('max')
actionTwo(max)
}
def actionTwo(int max) {
// Grails adds some code here to
// do some stuff that the framework needs
// your code here
}
// Grails generates this method...
def actionThree() {
def co = new SomeCommandObject()
bindData co, params
co.validate()
actionThree(co)
}
def actionThree(SomeCommandObject co) {
// Grails adds some code here to
// do some stuff that the framework needs
// your code here
}
}
There is other stuff going on to do things like impose allowedMethods checks, impose error handling, etc.
I hope that helps.

Grails filter controller in package

The project I'm working on currently uses filters for various pieces. The filters themselves work great. The problem I'm running into is that when specifying which controllers the filter should be executed on, I end up with a very large list. Functionally this works fine but it ends up being ugly and somewhat unwieldy.
def filters =
{
filterSomething(controller:'one|two|three|...|xyz', action:'*')
{
//before filter here, not important.
}
}
Is there a way to specify that the filter is only applicable for controllers in a given package or list of packages?
If there is nothing out of the box, I was thinking about tying something into the bootstrap and setting my lists that way.
You could have a filter on all controllers and then check their package and then decide what you want to do. There are better and more elegant way coding this, but just to give you an idea.
class MyFilters {
def grailsApplication
def filters = {
all(controller:'*', action:'*') {
before = {
if (checkController(['com.package.name'],controllerName)){
}
}
}
}
def searchInList(list,packageName){
for (keyword in list) {
if (packageName.contains(keyword)) return true
}
return false
}
Boolean checkController(def includePackageList,cname) {
def dlist = grailsApplication.getArtefacts("Controller")
def filteredList= dlist.findAll{ searchInList(includePackageList,it.getPackageName()) }
return filteredList.contains(cname)
}
}

How to get PagedResultList using chained named queries and adding additional criteria closure?

i am trying to get a pagedResultList using chained named queries, but i seem to fail.
Any hints or tips on how this can be achieved?
See modified example below from the Grails documentation that should illustrate my needs
def books = Publication.recentPublications.grailsInTitle.list(params) {
or {
like 'author', 'Tony%'
like 'author', 'Phil%'
}
}
This always returns an ArrayList..
When or remove the additional criteria and use it like below it works
def books = Publication.recentPublications.grailsInTitle.list(params)
I would like to add some criteria closures, any hints or tips on how i could achieve this?
I am facing same problems with named queries. This is my solution applied to your classes. Comment if it works for you.
class Publication {
//fields, constraints, etc.
namedQueries = {
authoredLike { String authorName ->
if (authorName) {
like 'author', authorName
}
// untested, but you get the point, please experiment
authoredLikeMany { List<String> authors ->
authors.each { String authorName -> like 'author', authorName }
}
}
}
def tonyBooks = Publication.recentPublications.grailsInTitle.authoredLike('Tony%').list(params)
def tonyAndPhilBooks = Publication.recentPublications.grailsInTitle.authoredLikeMany(['Tony%', 'Phil%']).list(params)

Grails Webflow: Access flow scope outside of action or transition states

I want to call a subflow where the controller is not known. It is passed in parameters to beginFlow and I save that in flow scope. Inside goToForm I'd like to call use the controller that is saved in flow.theController.
def beginFlow = {
enter {
action {
if (params?.redirectTo != null) {
String flow.theController = params.redirectTo
}
if ( flow.theController() ) {
success()
}
}
on("success").to("beginPage")
}
beginPage {
on('next').to('goToForm')
}
goToForm {
// I'd like this:
// subflow(controller: flow.theController, action:'start'
// this subflow works, but won't work for all cases
subflow(controller: 'AZ_A4', action:'start')
on('done').to('showResults')
on('notDone').to('beginPage')
}
showResults {
redirect(action: 'results')
}
}
As discussed on the user list, it appears that this isn't possible directly, as the subflow name has to be known at the time when the flow structure is being built (at application startup). But since the flow definition DSL is Groovy code you can do something like this:
beginPage {
on('next').to('selectSubflow')
}
selectSubflow {
action {
return "subflow_${flow.theController}"()
}
for(subController in listOfControllers) {
on("subflow_${subController}").to("subflow_${subController}")
}
}
for(subController in listOfControllers) {
"subflow_${subController}" {
subflow(controller:subController, action:'start')
on('done').to('showResults')
on('notDone').to('beginPage')
}
}
The listOfControllers could be a static somewhere, or you could possibly do something like this at the top of the flow definition
def beginFlow = {
def listOfControllers = grailsApplication.controllerClasses.findAll {
it.flows.containsKey('start')
}.collect { it.logicalPropertyName }
enter {
// ...
to enumerate all the controllers in the application that define a startFlow. You might need a def grailsApplication in your class, I always forget which places in Grails have it available by default and which don't...

How to make a query with usage of like operator over a string collection in GORM

Assume a domain class called User. User class looks like this:
class User {
List<String> values
}
The collection values contains strings like "http://example.com/x", "http://google.com/y", "http://google.com/z" and so on...
Let's say we want to build a query which gets all the users that have specific string in the collection values (e.g. "google.com"). Something like this:
def criteria = User.createCriteria()
def results = criteria.listDistinct () {
and {
user.values.each { like('someField', '%${it}%') }
}
}
Any ideas?
I have found the answer by experimentation. The solution is:
def criteria = User.createCriteria()
def results = criteria.listDistinct () {
and {
user.values.each { like('someField', '%'+it+'%') }
}
}
I am not sure what you are doing with your suggested answer.
I have never seen that usage of each in the criteria query before.
This question has been asked many times before but never given an answer.
The problem is that you are queriyng a String association, which is not a domain class. If you would make your own String domain class for example ondrej.String { String strValue } then you would be able to do :
User.withCriteria {
values { ilike("strValue", "...") }
}
The problem is not having access to the value of the String object. The value of the String class is called value, but it is a char array, so I do not believe the following will work:
User.withCriteria {
values { ilike("value", "...") }
}
You could try using :
User.withCriteria {
values { ilike("toString", "...") }
}
or something else instead of toString ... I do not have the possibility to test this right now.
Hope that helps
After a lot of trying and researching, I found this will work with Grails 2.4.0, I don't know about older versions.
Cat.withCriteria {
createAlias('nicknames', 'n')
ilike 'n.elements', '%kitty%'
}
The trick is to use 'n.elements'

Resources