Grails data binding - command objects with Lists - grails

Grails 1.3.7
Trouble with data binding Command objects that have List content. Example Command:
class Tracker {
String name
String description
List<Unit> units = new ArrayList()
}
class Unit {
String name
Long unitMax
Long unitMin
}
create GSP for Tracker has the Unit fields. One example:
<g:textField name="units[0].unitMax" value=""/>
TrackerController save method:
def save = { Tracker trackerInstance ->
trackerInstance = trackingService.saveOrUpdateTracker(trackerInstance)
}
But, always java.lang.IndexOutOfBoundsException
Alternatively, if I update controller to:
def save = {
Tracker trackerInstance = new Tracker()
trackerInstance.properties = params
....
Then groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: properties for class: com.redbrickhealth.dto.Tracker
Any ideas?
There seems to be a difference between binding in GORM vs Command objects.
Maybe I need to extend and register a PropertyEditorSupport for Unit?
-Todd

Since Groovy 1.8.7 the List interface has a method called withLazyDefault that can be used instead of apache commons ListUtils:
List<Unit> units = [].withLazyDefault { new Unit() }
This creates a new Unit instance every time units is accessed with a non-existent index.
See the documentation of withLazyDefault for more details. I also wrote a small blog post about this a few days ago.

Grails requires an command with existing list, that will be filled with data from reques.
If you know exact number of units, say 3, you can:
class Tracker {
String name
String description
List<Unit> units = [new Unit(), new Unit(), new Unit()]
}
or use LazyList from apache commons collections
import org.apache.commons.collections.ListUtils
import org.apache.commons.collections.Factory
class Tracker {
String name
String description
List<Unit> units = ListUtils.lazyList([], {new Unit()} as Factory)
}

Related

Executing scripts in a grails Job

I am working on two parts of a program. One that creates small groovy scripts and another that executes them in a Job. Each script is used to convert information from a map to a Domain object. The job will then save the domain object for future use.
Here is a small example.
Domain
class Report {
Date date
Country country
}
Map
Map<String, String> map = new HashMap<String, String>();
map.put("date", "2015-04-21 11:31:11");
map.put("country", "United States");
Date Script
String script = "x.date = Date.parse('y-M-d h:m:s', y['date'])"
The script is currently executed using Eval.
Eval(report, map, script)
There are other more complicated scripts that need to look up information and make decisions based on values.
The date script works fine but when the country script executes I get an error.
Country Script
String script = "import app.Country\nx.country = Country.findByName(y['country'])"
Error
Script1.groovy: 1: unable to resolve class app.Country
It seems like the Country class is not getting loaded in the call to Eval. How can I do an import in Eval? Should I use Eval or GroovyShell?
You need to instantiate a GroovyShell passing as argument the grailsApplication class loader, see the example below:
Bootstrap.groovy
Map bindingValues = new HashMap()
bindingValues.sessionFactory = sessionFactory
bindingValues.propertyInstanceMap = propertyInstanceMap
bindingValues.dataSource = dataSource
bindingValues.ctx = grailsApplication.mainContext
GroovyShell gs = new GroovyShell(grailsApplication.classLoader, new Binding(bindingValues))
gs.evaluate new File("docs/CargaAvaliacoes.groovy")
CargaAvaliacoes.groovy
import avaliacao.Colaborador
import modelo.ModeloAvaliacao
import programa.Programa
def programa = Programa.get(1)
def modelo = ModeloAvaliacao.get(1)
def avaliadores = ["02270789332":"1020016","11388449681":"1010002","02231772331":"1010004","04247774332":"1020002"]
def avaliacaoService = ctx.getBean("avaliacaoService")
avaliadores.each {
def avaliador = Colaborador.findByCpf(it.key)
def avaliados = Colaborador.findAllBySetorAndCpfNotEqual(it.value,it.key)
avaliados.each {
avaliacaoService.cadastrar(programa, modelo, avaliador, it)
}
}
** You need to specify the grailsApplication.classLoader because this class loader is used to load the Grails domain classes.

Basic problems to use Vaadin 7 + Grails 2.3 (Persistence, domain class design, get Hibernate Session)

I'm using Vaadin 7 + Grails 2.3, there is some questions
My domain classes
class Base {
private static final Date NULL_DATE = new Date(0)
Date createdAt = NULL_DATE;
Date updatedAt = NULL_DATE;
def beforeInsert(){
createdAt = new Date();
updatedAt = new Date();
}
def beforeUpdate(){
updatedAt = new Date();
}
static mapping = {
autoTimestamp true
}
}
abstract class Person extends Base{
String name;
String name2;
String phone1;
String phone2;
static constraints = {
name2 nullable:true
phone1 nullable:true
phone2 nullable:true
}
}
class Customer extends Person {
double credit;
}
THE PROBLEMS
PROBLEM 1
In my Vaadin class UI, if I try this
class MyUI extends UI {
#Override
protected void init(VaadinRequest vaadinRequest) {
Customer customer = new Customer()
customer.name="RODRIGO"
customer.save()
}
}
Show this error
Servlet.service() for servlet [VaadinServlet 0] in context with path [/AgileWeb] threw exception [com.vaadin.server.ServiceException: groovy.lang.MissingPropertyException: No such property: name for class: agileweb.Customer
Possible solutions: all] with root cause
Message: No such property: name for class: agileweb.Customer
Possible solutions: all
there is no "name" property? The class Customer extends Person that has this property.
PROBLEM 2
If I try this
Customer customer = new Customer()
Customer.setName("RODRIGO")
Customer.save()
Show thos error : Message: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here
I have seach about this error but I didn't understand to fix it, maybe I'm new with Grails
PROBLEM 3 - ACTUALLY IS A QUESTION
I know that I can use BeanItemContainer, just from List object, that is possible with no problem, but BeanItemContainer is not lazy load, I'd like to use HbnContainer addon (https://vaadin.com/directory#addon/hbncontainer) becase it just need a hibernate session, so How Can I get the "Hibernante session", is there any example and how to do it?
PROBLEM 4 - ACTUALLY IS A QUESTION (AGAIN)
Following this tutorial https://vaadin.com/wiki/-/wiki/Main/Vaadin%20on%20Grails%20-%20Database%20access
It works to save object in the database, but the questions
- Is it really necessary to create a service for each domain class? I have read that it's recomend to put domain logical in the services, I agree with this, but what about simple domain that no need any logical?
so, is there possible to create something like DAO for services? is there any service design to avoid repeted code just to save objects?
I know that are many questions but I think these questions are the same of others, I really want to use Vaadin + Grails to enjoy the better of both, but is not easy to me at the moment!
Before I start answering your question, let me question you domain model. Base class is generally fine, but I want to talk about Person and Customer. You may have good reasons why you picked up inheritance, but please think of composition. Maybe you could have Person that contains a reference to an enum, that states type of the person. You could start here with that: http://en.wikipedia.org/wiki/Composition_over_inheritance
I think you have a typo there. Call save() method on 'customer' not 'Customer', which is a class
When there is a request coming to Grails application, it opens a session and the session is available during that request. There is not this kind of behavior like that in Vaadin. You need to put it into a Service. Yes, you can make generic service to save an object
class GenericService { def save(def domain) { domain.save(failOnError:true) }}
You can get the session factory like this
import org.codehaus.groovy.grails.commons.ApplicationHolder as AH
def ctx = AH.application.mainContext
def sessionFactory = ctx.sessionFactory
or
ApplicationContext applicationContext = grailsApplication.mainContext
ConfigurableLocalSessionFactoryBean factory = applicationContext.getBean('&sessionFactory')
As I wrote, you could create GenericService or a service per domain object. Just to keep in mind that GenericService should only save an object and contain no other logic that would be specific for a domain object.

my own id in GORM

I tried to change the standard 'id' in grails:
calls Book {
String id
String title
static mapping {
id generator:'assigned'
}
}
unfortunately, I soon noticed that this breaks my bootstrap. Instead of
new Book (id:'some ISBN', title:'great book').save(flush:true, failOnError:true)
I had to use
def b = new Book(title:'great book')
b.id = 'some ISBN'
b.save(flush:true, failOnError:true)
otherwise I get an 'ids for this class must be manually assigned before calling save()' error.
but that's ok so far.
I then encountered the same problem in the save action of my bookController. But this time, the workaround didn't do the trick.
Any suggestions?
I known, I can rename the id, but then I will have to change all scaffolded views...
That's a feature of databinding. You don't want submitted data to be able to change managed fields like id and version, so the Map constructor that you're using binds all available properties except those two (it also ignores any value for class, metaClass, and a few others).
So there's a bit of a mismatch here since the value isn't managed by Hibernate/GORM but by you. As you saw the workaround is that you need to create the object in two steps instead of just one.
I can't replicate this problem (used Grails 2.0.RC1). I think it might be as simple as a missing equal sign on your static mapping = { (you just have static mapping {)
Here's the code for a domain object:
class Book {
String id
String name
static mapping = {
id generator:'assigned'
}
}
And inside BootStrap.groovy:
def init = { servletContext ->
new Book(name:"test",id:"123abc").save(failOnError:true)
}
And it works fine for me. I see the id as 123abc.
You need to set the bindable constraint to true for your id prop, e.g.
class Employee {
Long id
String name
static constraints = {
id bindable: true
}
}

Groovy Dynamic List Interaction

I am using an older version of grails (1.1.1) and I am working on a legacy application for a government client.
Here is my question (in psuedo form):
I have a domain that is a Book. It has a sub domain of type Author associated with it (1:many relationship). The Author domain has a firstName and lastName field.
def c = Book.createCriteria()
def booklist = c.listDistinct {
author {
order('lastName', 'asc')
order('firstName', 'asc')
}
}
Let's say I have a list of fields I want to use for an excel export later. This list has both the author domain call and the title of the column I want to use.
Map fields = ['author.lastName' : 'Last Name', 'author.firstName', 'First Name']
How can I dynamically call the following code--
booklist.eachWithIndex(){
key, value ->
println key.fields
}
The intent is that I can create my Map of fields and use a loop to display all data quickly without having to type all of the fields by hand.
Note - The period in the string 'author.lastName' throws an error when trying to output key['author.lastName'] too.
I don't recall the version of Groovy that came with Grails 1.1, but there are a number of language constructs to do things like this. If it's an old version, some things may not be available - so your mileage may vary.
Map keys can be referenced with quotes strings, e.g.
def map = [:]
map."person.name" = "Bob"
The above will have a key of person.name in the map.
Maps can contain anything, including mixed types in Groovy - so you really just need to work around string escapes or other special cases if you are using more complex keys.
You can also use a GString in the above
def map = [:]
def prop = "person.name"
map."${prop}" = "Bob"
You can also get a map of property/value off of a class dynamically by the properties field on it. E.g.:
class Person { String name;String location; }
def bob = new Person(name:'Bob', location:'The City')
def properties = bob.properties
properties.each { println it }

Setting Grails domain id in Bootstrap.groovy

Is it possible to explicitly set the id of a domain object in Grails' Bootstrap.groovy (or anywhere, for that matter)?
I've tried the following:
new Foo(id: 1234, name: "My Foo").save()
and:
def foo = new Foo()
foo.id = 1234
foo.name = "My Foo"
foo.save()
But in both cases, when I print out the results of Foo.list() at runtime, I see that my object has been given an id of 1, or whatever the next id in the sequence is.
Edit:
This is in Grails 1.0.3, and when I'm running my application in 'dev' with the built-in HSQL database.
Edit:
chanwit has provided one good solution below. However, I was actually looking for a way to set the id without changing my domain's id generation method. This is primarily for testing: I'd like to be able to set certain things to known id values either in my test bootstrap or setUp(), but still be able to use auto_increment or a sequence in production.
Yes, with manually GORM mapping:
class Foo {
String name
static mapping = {
id generator:'assigned'
}
}
and your second snippet (not the first one) will do the job (Id won't be assigned when passing it through constructor).
What I ended up using as a workaround was to not try and retrieve objects by their id. So for the example given in the question, I changed my domain object:
class Foo {
short code /* new field */
String name
static constraints = {
code(unique: true)
name()
}
}
I then used an enum to hold all of the possible values for code (which are static), and would retrieve Foo objects by doing a Foo.findByCode() with the appropriate enum value (instead of using Foo.get() with the id like I wanted to do previously).
It's not the most elegant solution, but it worked for me.
As an alternative, assuming that you're importing data or migrating data from an existing app, for test purposes you could use local maps within the Bootstrap file. Think of it like an import.sql with benefits ;-)
Using this approach:
you wouldn't need to change your domain constraints just for
testing,
you'll have a tested migration path from existing data, and
you'll have a good data slice (or full slice) for future integration tests
Cheers!
def init = { servletContext ->
addFoos()
addBars()
}
def foosByImportId = [:]
private addFoos(){
def pattern = ~/.*\{FooID=(.*), FooCode=(.*), FooName=(.*)}/
new File("import/Foos.txt").eachLine {
def matcher = pattern.matcher(it)
if (!matcher.matches()){
return;
}
String fooId = StringUtils.trimToNull(matcher.group(1))
String fooCode = StringUtils.trimToNull(matcher.group(2))
String fooName = StringUtils.trimToNull(matcher.group(3))
def foo = Foo.findByFooName(fooName) ?: new Foo(fooCode:fooCode,fooName:fooName).save(faileOnError:true)
foosByImportId.putAt(Long.valueOf(fooId), foo) // ids could differ
}
}
private addBars(){
...
String fooId = StringUtils.trimToNull(matcher.group(5))
def foo = foosByImportId[Long.valueOf(fooId)]
...
}

Resources