I'm having a hard time finding information about grails functionality:
DomainClass.properties = params
In my particular case, I have these classes:
class parameterType = {
String name
String desc
static hasMany = [codes : parameterCode]
...
}
class parameterCode = {
String code
String desc
static belongsTo = [parameterType : parameterType]
}
My parameterType/edit.gsp has name, desc and an html table with its list of parameterCodes
At first, I had some variation of the scaffolded controller on the 'update' action. That (I know its wrong but it was a beginners code) it first deleted all the parameterCodes and then reassociated them (or recreated them).
With Ajax I was sending the data in this format:
id=1234&name=paramName&desc=paramDesc&codes[0].code=code1&codes[0].desc=codeDesc1&codes[1].code=code2&codes[1].desc=codeDesc2
And in the controller I had this:
def parameterTypeInstance = ParameterType.get(params.id)
def toDelete = parameterTypeInstance.parameterCodes
parameterTypeInstance.parameterCodes = []
toDelete.each{it.delete(flush: true)}
//And this "magic" line reassociated all the properties in parameterType And Created his parameterCodes in the data base:
parameterTypeInstance.properties = params
I honestly don't how it works, and I just wanted to know if there's a way of doing the same thing without having to previously delete the associated parameterCodes.
Cheers
**Update:**
I just found what I was looking for in these links:
http://www.2paths.com/2009/10/01/one-to-many-relationships-in-grails-forms/
http://omarello.com/2010/08/grails-one-to-many-dynamic-forms/
But I had another error.
These talks about LazyList and decorate(), so I just added the next lines to my ParameterType Class:
def List getExpandableCodeList() {
return LazyList.decorate(codes,FactoryUtils.instantiateFactory(ParameterCode.class))
}
But when I do this in my controller update:
parameterTypeInstance.properties = params
I'm getting this error:
groovy.lang.MissingMethodException: No signature of method: static org.apache.commons.collections.list.LazyList.decorate() is applicable for argument types: (org.hibernate.collection.PersistentSet, org.apache.commons.collections.functors.InstantiateFactory) values: [[cE - EE, cA - AA, cC - CC, cD - DD], org.apache.commons.collections.functors.InstantiateFactory#dd768d]
The data is being recieved in the controller this way:
expandableCodeList[0].desc: AA
expandableCodeList[3].code: cE
expandableCodeList[3].id: 35073
expandableCodeList[1].id: 35076
expandableCodeList[0].code: cA
expandableCodeList[2].code: cD
expandableCodeList[1].desc: CC
expandableCodeList[0].id: 35080
expandableCodeList[3].desc: EE
expandableCodeList[2].id: 35075
Any hints on what I'm doing wrong? should I be sending the data in another format?
Any help would be much appreciated. Thanks.
If I not in error .properties is a method added by groovy to the java.lang.Object , look to this javaodc
There is many way to do what you want to do. Please look to grails documentation on data binding .
For example you can do
bindData(parameterTypeInstance, params, [exclude: parameterTypeInstance.parameterCodes])
look here for more info on bindData
Ok, I'm settings this as an answer:
http://www.2paths.com/2009/10/01/one-to-many-relationships-in-grails-forms/
At last, lazylist and the decorate() method was what I was looking for. Sadly it takes to take the child collection as a list and it carries out a lot of other issues for me. But its a good solution if anyone needs to make a view with the parent and its child objects the "simple" way.
Related
I'm not a programming savvy person, so please bear with me.
I've read blog entries and docs about command object. I've never used it and was wondering if I should. (I probably should...)
My project requires parsing, sorting, calculating, and saving results into database when users upload files.
So according to one of the blog entries I read and its corresponding github code,
1) SERVICE should receive file uploads, parse uploaded files (mainly docs and pdfs), sort parsed data using RegEx, and calculate data,
2) COMMAND OBJECT should call SERVICE, collect results and send results back to controller, and save results into the database,
3) CONTROLLER should receive request from VIEW, get results from COMMAND OBJECT, and send results back to VIEW.
Did I understand correctly?
Thanks.
I found this to be the best setup. Here is an example that I use on production:
Command Object (to carry data and ensure their validity):
#grails.validation.Validateable
class SearchCommand implements Serializable {
// search query
String s
// page
Integer page
static constraints = {
s nullable: true
page nullable: true
}
}
Controller (directs a request to a Service and then gets a response back from the Service and directs this response to a view):
class SomeController {
//inject service
def someService
def search(SearchCommand cmd) {
def result = someService.search(cmd)
// can access result in .gsp as ${result} or in other forms
render(view: "someView", model: [result: result])
}
}
Service (handles business logic and grabs data from Domain(s)):
class SomeService {
def search(SearchCommand cmd) {
if(cmd.hasErrors()) {
// errors found in cmd.errors
return
}
// do some logic for example calc offset from cmd.page
def result = Stuff.searchAll(cmd.s, offset, max)
return result
}
}
Domain (all database queries are handled here):
class Stuff {
String name
static constraints = {
name nullable: false, blank: false, size: 1..30
}
static searchAll(String searchQuery, int offset, int max) {
return Stuff.executeQuery("select s.name from Stuff s where s.name = :searchQuery ", [searchQuery: searchQuery, offset: offset, max:max])
}
}
Yes, you understood it correctly except the one thing: command object shouldn't save the data to DB - let service to do that. The other advantage of command object is data binding and validation of data from the client. Read more about command objects here grails command object docs
You can also find helpful information regarding your question in this article
grails best practices
I guess not. Its not really related to whether the save is done in a service it should always attempt to carry out complex stuff and specifically db stuff in a service. so that is regardless. I tend to not use command object but have got hooked on helper classes aka beans that sit in src/main/groovy and do all of the validation and formatting. I just did a form and in it has feedback and reason.
Initially I thought I would get away with
def someAction(String feedback, String reason) {
someService.doSomething(feedback,reason)
}
But then I looked closed and my form was firstly a textarea then the selection objects were bytes so above was not working and to simply fix it without adding the complexity to my controller/service I did this:
packe some.package
import grails.validation.Validateable
class SomeBean implements Validateable {
User user
byte reason
String feedback
static constraints = {
user(nullable: true)
reason(nullable:true, inList:UsersRemoved.REASONS)
feedback(nullable:true)
}
void setReason(String t) {
reason=t as byte
}
void setFeedback(String t) {
feedback=t?.trim()
}
}
Now my controller
class SomeController {
def userService
def someService
def doSomething(SomeBean bean){
bean.user = userService.currentUser
if (!bean.validate()) {
flash.message=bean.errors.allErrors.collect{g.message([error : it])}
render view: '/someTemplate', model: [instance: bean,template:'/some/template']
return
}
someService.doSomeThing(bean)
}
}
Now my service
Class SomeService {
def doSomeThing(SomeBean bean) {
if (bean.user=='A') {
.....
}
}
All of that validation would have still had to have been done somewhere, you say no validation but in a good model you should do validation and set things to be stored in proper structures to reduce overloading your db over time. difficult to explain but in short i am talking about your domain class objects and ensuring you are not setting up String something string somethingelse and then not even defining their lenghts etc. be strict and validate
if you have a text area this will be stored in the back end - so you will need to trim it like above - you will need to ensure the input does not exceed the max character of the actual db structure which if not defined will probably be 255
and by doing
static constraints = {
user(nullable: true)
reason(min:1, max:255, nullable:true, inList:UsersRemoved.REASONS)
Has already invalidated it through the bean.validate() in the controller if the user exceeded somehow my front end checks and put in more than 255.
This stuff takes time be patient
Edited to finally add in that example byte - is one to be careful of -
When adding any String or what ever I have started to define the specific like this and in the case of byte if it is a boolean true false - fine if not then define it as a tinyint
static mapping = {
//since there is more than 1 type in this case
reason(sqlType:'tinyint(1)')
feedback(sqlType:'varchar(1000)')
// name(sqlType:'varchar(70)')
}
If you then look at your tables created in the db you should find they have been created as per definition rather than standard 255 varchar which I think is the default for a declared String.
In a controller I have this finder
User.findByEmail('test#test.com')
And works.
Works even if I write
User.findByEmail(null)
But if i write
User.findByEmail(session.email)
and session.email is not defined (ergo is null) it throw exception
groovy.lang.MissingMethodException: No signature of method: myapp.User.findByEmail() is applicable for argument types: () values: []
Is this behavior right?
If i evaluate "session.email" it give me null so I think it must work as it do when I write
User.findByEmail(null)
Even more strange....
If I run this code in groovy console:
import myapp.User
User.findByEmail(null)
It return a user that has null email but if I run the same code a second time it return
groovy.lang.MissingMethodException: No signature of method: myapp.User.findByEmail() is applicable for argument types: () values: []
You can't use standard findBySomething dynamic finders to search for null values, you need to use the findBySomethingIsNull version instead. Try
def user = (session.email ? User.findByEmail(session.email)
: User.findByEmailIsNull())
Note that even if User.findByEmail(null) worked correctly every time, it would not necessarily give you the correct results on all databases as a findBySomething(null) would translate to
WHERE something = null
in the underlying SQL query, and according to the SQL spec null is not equal to anything else (not even to null). You have to use something is null in SQL to match null values, which is what findBySomethingIsNull() translates to.
You could write a static utility method in the User class to gather this check into one place
public static User byOptEmail(val) {
if(val == null) {
return User.findByEmailIsNull()
}
User.findByEmail(val)
}
and then use User.byOptEmail(session.email) in your controllers.
Jeff Brown from grails nabble forum has identified my problem. It's a GORM bug. see jira
More info on this thread
This jira too
I tried with debugger and it looks it should be working, as you write. Maybe the groovy itself is a little bit confused here, try to help it this way:
User.findByEmail( session['email'] )
I am using the findAll() SQL-like method:
MyDomainClass.findAll("firstname='George' and lastname='kuo' and username='kjlop'"),
but I have got problem:
when value starts with number(for example,when age='2poj') it throws an exception
I use grails 1.3.2 and gorm-hbase 0.2.4 plugin and in my domain classes fields have String type.
Here is the Stack Trace:
expecting token in range: '0'..'9', found 'p'
at org.grails.hbase.ghql.LexerRules.nextToken(LexerRules.java:125)
at org.grails.hbase.finders.QueryStringTokenizer.tokenize(QueryStringTokenizer.groovy:59)
at org.grails.hbase.finders.TokenizerStrategy$tokenize.call(Unknown Source)
//---------
I wonder is there any way in groovy change findAll() method work ?
If anybody know solution please help.
Thanks in advance.
You should be able to run a dynamic finder method on the domain object to achieve what you need.
Example:
MyDomainClass.findAllByFirstnameAndAge('Dan', 25)
This works for all data types and enums.
You can try like the Grails example:
MyDomainClass.findAll("from DomainTable as b where b.firstname=:firstname and b.age=:age", [firstname:'Dan Brown', age: 25]
Notice: I don't know if you mistype it, but '25' is a string, so that it can't be age='25'
EDIT:
I don't know how this doesn't work, but in case you want to find with multiple properties, you should use createCriteria().
def c = MyDomainClass.createCriteria()
def results = c.list {
like("firstName", "George%")
like("age", "25");
}
EDIT2: Sorry, createCriteria is not supported by hbase plugin. Based on your condition, I think it's suitable to try DynamicFinderFilter (with approriate import).
// all books written by Dan Brown or J K Rowling
DynamicFinderFilter filterList = new FinderFilterList(Operator.OR)
DynamicFinderFilter filter1 = new Filter('author', 'Dan Brown')
filterList.addFilter(filter1)
DynamicFinderFilter filter2 = new Filter('author', 'J K Rowling')
filterList.addFilter(filter12)
results = Book.findAll(filterList)
The complete example can be find in the plugin page.
I want to generate a pdf report, where a column(or cell/field) is left blank(empty) on purpose. This column actually does have a value but, I'm choosing not to display it. The column title still needs to be displayed.
Example of where this could be useful:
Blank(empty) column: A comments or notes column down one side of a report.
Blank(empty) cell: A sudoku puzzle print-out.
Much appreciated. DynamicJasper is Awesome! Thanks to the dj-team.
Regards,
Pete
Glad to announce, solution found for adding an 'empty' column - and in short, it's to create a customExpression.
def cb = ColumnBuilder.getInstance()
cb = cb.setTitle("Notes")
cb = cb.setCustomExpression(new BlankExpression())
AbstractColumn columnNotes = cb.build()
Then add it to the rest of the report.
Class BlankExpression is
public class BlankExpression implements CustomExpression {
public BlankExpression() { }
public Object evaluate(Map fields, Map variables, Map parameters) {
return " ";
}
public String getClassName() {
return String.class.getName();
}
}
But there are a few issues relating to the use of customExpressions and grails.
1st issue: "getNew()" - The examples provided on the DJ website all use "getNew()"
http://dynamicjasper.sourceforge.net/docs/HOWTO%20Create%20Custom%20Expressions.html is an example of DynamicJasper v3.1.3 where as the Grails plugin is based on v.3.0.6 which only has a getInstance() method (deprecated in 3.1.3)
2nd issue: As far as I can see, groovy doesn't allow java-style inline class implementations, thus forcing us to create a separate class file. But this is not a big problem. I might be wrong about this, and please correct me.
Hope this helps you too.
Regards,
Pete
I'm following the code examples in 'The Definitive Guide to Grails' by Graeme Keith Rocher, and have come across a rather unusual stumbling block.
Essentially, 2 domain classes exist - Bookmark & Tag.
Bookmark:
class Bookmark {
static hasMany = [tags:Tag]
URL url
String title
String notes
Date dateCreated = new Date()
}
Tag:
class Tag{
static belongsTo= Bookmark
Bookmark bookmark
String name
}
I'm instructed to launch the Grails Console (is this the same as the groovy console)and create a new object as follows.
def b = new Bookmark(url: new URL('http://grails.org/'), title:'Grails', notes:'Groovy')
This results in:
Result: Bookmark : null
According to the book, GORM automatically provides an implementation of an addTag method. So I code...
b.addTag( new Tag(name: 'grails'))
Only to get whammed with the error message:
Exception thrown: No such property: b for class: ConsoleScript1
groovy.lang.MissingPropertyException: No such property: b for class: ConsoleScript1 at ConsoleScript1.run(ConsoleScript1:2)
The author hasn't accounted for this in the book. I was wondering if anyone could help me out?
Thanks.
Are you reading the 1st edition of the book? If so it's quite outdated. The add* methods have been deprecated since 0.5. It was replaced by addTo* so do this instead:
b.addToTags( new Tag(name: 'grails'))
Assuming your code example shouldn't have Bookmarks defined twice (copy and paste error?) and Tag might look like this:
class Tag {
String name
}
The groovy console is not the same as the grails console. To access the grails console, type grails console in your application directory - you should get a Java GUI app. It's possible that the example will work then because grails add some stuff to the standard Groovy.
Also, your problem isn't the addTag method, but the item b that you defined which cannot be found. Try entering the whole script into the console at once and executing it, instead of executing it line by line.