How can I pass a class to Groovy's Eval binding? - grails

I'm doing some gross stuff, like using Groovy's metaClass and Eval to dynamically assign properties to an object from an XML import:
class ObjectBuilder {
def assignProp(String propName, String propValue) {
Eval.x(this, "x.metaClass.${propName} = '${propValue}'")
}
}
def h = new ObjectBuilder()
h.assignProp('name', 'Henson')
println(h.name)
What I'd like to do though, is be able instantiate another copy of the class inside itself:
Eval.x(this, "x.metaClass.${ObjName} = new ObjectBuilder()")
But I can't, because I think the class isn't passed to the binding. Is there another solution?

A couple of solutions:
Expando
You may try working with a bunch of Expandos:
h = new Expando()
h.last = new Expando()
h.last.name = 'tech'
assert h.last.name == 'tech'
Metaclass the object directly
xml = '''
<person>
<name>john</name>
<surname>doe</surname>
<age>41</age>
<location>St Louis, MO</location>
</person>
'''
class Person {
def name, surname, location, age
}
root = new XmlParser().parseText xml
person = new Person(root.children().collectEntries { [it.name(), it.text()] })
person.metaClass.getFullname = { "$delegate.name $delegate.surname" }
assert person.fullname == "john doe"
person.metaClass.likes = "chicken with lemon"
assert person.likes == "chicken with lemon"
Maps
map = [:]
map.last = [:]
map.last.name = 'Tech'
assert map.last.name == 'Tech'

Passing a newly instantiated object then assigning it with Eval seems to work:
class ObjectBuilder {
def assignProp(String propName, String propValue) {
Eval.x(this, "x.metaClass.${propName} = '${propValue}'")
}
def nestObj(String objName) {
Eval.xy(this, new ObjectBuilder(), "x.metaClass.${objName} = y")
}
}
ObjectBuilder h = new ObjectBuilder()
h.assignProp('name', 'Henson')
h.nestObj('last')
h.last.assignProp('name', 'Tech')
println h.name + ' ' + h.last.name

Related

How to find JSON value in Grails

I wanted to find if value exists in JSON.
Here is my example JSON:
data : [{id:1, name:'John'}, {id:2, name:'Harry'}]
then I will use the value from params such as
params.id // id=1
to find does it exist in data.id or not
Here you go:
def data = [{id: 1, name: 'John'}, {id:2, name: 'Harry'}]
Map value = data.find { it.id == params.id }
if (value) {
// Value exists
println("Value with id $params.id" + value)
} else {
// Not exits
}
http://docs.groovy-lang.org/latest/html/groovy-jdk/java/util/Collection.html#find(groovy.lang.Closure)
You can use Groovy Slurper to do this. Follow the blog by Mr Haki
http://mrhaki.blogspot.in/2014/08/groovy-goodness-relax-groovy-will-parse.html
import groovy.json.*
import static groovy.json.JsonParserType.LAX as RELAX
def jsonSlurper = new JsonSlurper()
def data = "[{'id':1, name:'John'}, {id:2, name:'Harry'}]"
def result = jsonSlurper.setType(RELAX).parseText(data)
https://groovyconsole.appspot.com/script/5153114243989504

Create dynamically closures in Groovy from a String object

i would like to create a query with the Criteria API in Grails (GORM).
The query will have to be something like this:
MyEntity.createCriteria().list{
assoc{
parent{
eq("code", val)
}
}
}
What i need is to build the nested closure dynamically from a String object. The String for the example above will be "assoc.parent.code" . I splitted the String by dot (by doing String.split("\\.") ) but i don't know how to construct the nested closures:
assoc{
parent{
eq("code", val)
}
}
dynamically based on the array of the splitted Strings above.
What about createAlias?. You could try something like this:
def path = "assoc.parent.code"
def split = path.split(/\./)
MyEntity.createCriteria().list {
// this will get you 'createAlias( assoc.parent, alias1 )'
createAlias split.take( split.size() - 1 ), "alias1"
// this will get you 'eq(alias1.code, userInput)'
eq "alias1.${split[-1]}", userInput
}
This snippet is not generic, but you get the idea.
Update
Not conventional, but you can build a string containing the code with the closures and evaluate it using GroovyShell:
assoc = 'assoc.parent.child.name'
split = assoc.split( /\./ )
path = split[-2..0] // will get us 'child.parent.assoc';
// we will build it from inside-out
def firstClosure = "{ eq '${split[-1]}', 'john doe' }"
def lastClosure = firstClosure
for (entity in path) {
def criteriaClosure = "{ ${entity} ${lastClosure} }"
lastClosure = criteriaClosure
}
assert lastClosure == "{ assoc { parent { child { eq 'name', 'john doe' } } } }"
def builtClosure = new GroovyShell().evaluate("return " + lastClosure)
assert builtClosure instanceof Closure
A more generic approach would be to metaClass String as below, and use it for any kind of separator
. | , - ~ and more.
String.metaClass.convertToClosureWithValue = {op, val ->
split = delegate.split(op) as List
if(split.size() == 1) {return "Cannot split string '$delegate' on '$op'"}
items = []
split.each{
if(it == split.last()){
items << "{ eq '$it', $val }"
split.indexOf(it).times{items.push("}")}
} else {
items << "{$it"
}
}
println items.join()
new GroovyShell().evaluate("return " + items.join())
}
assert "assoc.parent.child.name".convertToClosureWithValue(/\./, "John Doe") instanceof Closure
assert "assoc-parent-child-name".convertToClosureWithValue(/\-/, "Billy Bob") instanceof Closure
assert "assoc|parent|child|grandChild|name".convertToClosureWithValue(/\|/, "Max Payne") instanceof Closure
assert "assoc~parent~child~grandChild~name".convertToClosureWithValue('\\~', "Private Ryan") instanceof Closure
assert "assocparentchildname".convertToClosureWithValue(/\|/, "Captain Miller") == "Cannot split string 'assocparentchildname' on '\\|'"
//Print lines from items.join()
{assoc{parent{child{ eq 'name', John Doe }}}}
{assoc{parent{child{ eq 'name', Billy Bob }}}}
{assoc{parent{child{grandChild{ eq 'name', Max Payne }}}}}
{assoc{parent{child{grandChild{ eq 'name', Private Ryan }}}}}

Grooovy weird error

class Area {
String name
String description
static constraints = {
}
_
class SearchIndexing {
String search
Area area
static constraints = {
}
}
_
<%
def area = cm.Area.get(1)
def si = new cm.SearchIndexing()
def concat
concat = area.name // i wanna join here more things with much bigger class
si.search = concat
si.area = area
si.save()
out << searchIndexing.list()
%>
ERROR:
No signature of method: cm.SearchIndexing.save() is applicable for argument types: () values: [] Possible solutions: wait(), any(), wait(long), any(groovy.lang.Closure), isCase(java.lang.Object), use([Ljava.lang.Object;)
Sure you don't have an initial + sign, ie:
def temp = + obj2.prop1 + " " + ...
Why not try a more groovy way like:
def temp = "$obj2.prop1 $obj2.prop2 ..."
Or:
def temp = [ obj2.prop1, obj2.prop2 ].join( ' ' )
From the way you're creating the instance of SearchIndexing
def si = new cm.SearchIndexing()
it looks like it's an inner class. I don't think a domain class can be an inner class, which would explain why it has no save() method.

How to save multiple rows of data by looping through an array or a map?

Class Buyer {
String name
static constraints = {
}
}
Class Order {
String ref
static belongsTo = [buyer:Buyer]
static constraints = {
buyer(nullable:false)
}
}
In OrderController.groovy
...
def someAction = {
//working
def data1 = ["buyer.id": 2, "ref": "xyz"]
def ord = new Order(data1);
ord.save();
def data2 = ["buyer.id": 2, "ref": "234xyz"]
def ord2 = new Order(data2);
ord2.save();
//But in a loop - its not working
def items = ['abc', 'def', 'ghi', 'jkl']
def data2 = [:]
for(e in items) {
data2 = ["buyer.id": 2, "ref" : e.value] //keeping buyer id same
def ord = new Order(data2);
ord.save();
data2 = [:] //just emptying it?
}
}
As you would notice in "working" above, if I am able to save multiple rows by copy pasting and definging new maps but If I try to loop through an array, it doesnt work. Any ideas how do I save data by looping through an array or a map?
Any questions, please let know
Thanks
First, I'm not sure about ["buyer.id": 2, "ref" : e.value], I think it should be [buyer: Buyer.get(2), ref : e.value].
Second, I would recommend using cascading save to do the task. You can try something like this(You need to define a static hasMany = [orders: Order] relation in Buyer for Buyer-Order relationship.
Buyer b = new Buyer(name: "foo")
for(e in items) {
def ord = new Order(ref: e.value);
b.addToOrders(ord)
}
b.save(flush:true)
You should just be able to do:
def someAction = {
def items = ['abc', 'def', 'ghi', 'jkl']
items.each { item ->
def ord = new Order( [ 'buyer.id':2, ref:item ] )
ord.save()
}
}
What errors (if any) are you getting?
Also, why are you doing e.value? This will get you an array of Character rather than a String (which is what your first working example is using)

Grails error: No such property: it for class:

Below is my code.
Here I am getting an error which is 'No such property: it for class: emp.EmployeeController'.
I think I am doing something wrong here.
Any advice??
def list ={
def id=params.id
def results
String employee="SELECT empName, empDate, empNo from employee where empId='id'"
String referrer="SELECT empName, empDate, empNo from referer where empId='id'"
def employeeInstanceList = new ArrayList<Employee>()
Sql sql = new Sql(dataSource)
def joining=null
joining = sql.rows( "select joining from employee_dates")
if (joining!=null)
results = sql.eachRow(employee)
employeeInstanceList=getCalculatedEmployeeData(results)
/*{
def employee = new Employee()
employee.setempName it.empName
employee.setEmpNo it.empNo
employee.setEmpDate it.EmpDate
employeeInstanceList.add employee
}*/
else
results = sql.rows (currentDaySql)
employeeInstanceList=getCalculatedEmployeeData(results)
/*{
def employee = new Employee()
employee.setempName it.empName
employee.setEmpNo it.empNo
employee.setEmpDate it.EmpDate
employeeInstanceList.add employee }*/
}
[employeeInstanceList: [employeeInstanceList: employeeInstanceTotal: Employee.count()]
}
def getCalculatedImpactData(def results){
def employee = new Employee()
employee.setempName it.empName
employee.setEmpNo it.empNo
employee.setEmpDate it.EmpDate
employeeInstanceList.add employee }*/
return [employeeInstanceList: employeeInstanceList]
}
Thanks,
Meghana
i would second leebutts answer... but just a pointer, the usage of the it keyword is usually confined to closures... so instead of doing this in java:
List l = [];
for (Iterator i = l.iterator(); i.hasNext(); ) {
...do something adressing List l at position i...
}
you could do this in groovy / grails:
list.each { it ->
...do something with each object in the list (it)...
}
but you should really read up on groovy closures at http://groovy.codehaus.org/Closures
There is so much wrong with that code, I don't know where to start...
But to avoid getting more down votes I have tried :)
I tried to copy your code into an IDE and try and work out what you are trying to achieve but couldn't.
This is as close as I could get it:
def list = {
def id = parmas.id
def results
String employee = "SELECT empName, empDate, empNo from employe"
def employeeInstanceList
Sql sql = new Sql(dataSource)
def joining = sql.rows("select joining from employee_dates")
if (joining != null) {
results = sql.eachRow(employee)
employeeInstanceList = getCalculatedEmployeeData(results)
}
else {
results = sql.rows(currentDaySql)
employeeInstanceList = getCalculatedEmployeeData(results)
}
[employeeInstanceList: employeeInstanceList, employeeInstanceTotal: Employee.count()]
}
def getCalculatedImpactData(def results) {
def employeeInstanceList = new ArrayList<Employee>()
results.each { it ->
def employee = new Employee()
employee.empName = it.empName
employee.empNo = it.empNo
employee.empDate = it.EmpDate
employeeInstanceList.add(employee)
}
return employeeInstanceList
}
but it is still referring to a variable currentDaySql which doesn't exist and I'm not sure what you're trying to do with the 'joining' result.
You really need to read up on Groovy basics.
The block of code where the error occurs is probably:
def getCalculatedImpactData(def results){
def employee = new Employee()
employee.setempName it.empName
employee.setEmpNo it.empNo
employee.setEmpDate it.EmpDate
employeeInstanceList.add employee
return [employeeInstanceList: employeeInstanceList]
}
it is not defined anywhere (hint: the compilation error told you this). Like Sebastian said, it is typically used in closures; what you've defined here is a function. Presumably you wanted to use results (or something) instead of it here.
I'm assuming that some of the things in your code (e.g. comment opening/closing) weren't in there and were added between when you saw the error and when you posted the code. Otherwise you'd get other errors.

Resources