Grails hasMany bulk create - grails

I am trying to implement a "bulk" insert for hasMany. And got it 90% there, but hit a hiccup with my polyclass design. I have a model like
class Parent {
static hasMany = [references: Reference]
}
class Reference {
static belongsTo = [parent: Parent]
String name
}
class ConcreteReference extends Reference{
String childName
}
I was able to get the create working(defined by creating records) by having markup like
<form action='reference/1/edit>
<input name='references[0].name' value='name1' />
<input name='references[0].childName' value='childName1' />
<input name='references[1].name' value='name2' />
<input name='references[1].childName' value='childName2' />
</form>
But the issue is it always creates the concrete type(class='domain.Reference'), so "child name is always null.
I used:
def edit(def id){
def parent = Parent.get(id)
//i actually clear all the old references first (didn't show that code)
parent.properties = params
parent.save(flush:true)
}
How can I either specify the "concrete type"( I tried a hidden value of class it didn't work) or is there an easy way to loop through params.reference[idx] create it and then add it to Parent?
hopefully that is clear, let me know if you need any clarification.

My manual way of parsing params(not I hard code ConcreteReferences, but i was able to group the params):
def refRegex = /^([a-zA-Z]+\[\d+\])/
for (ref in params.groupBy { it -> it.key.find(refRegex)}.findAll{it.key}){
//this will be like references[0]: [references[0].name : 'test', etc]
//for each one create a FreeFormReference
def _ref = new ConcreteReference(ref.value.collectEntries{it -> [it.key.replace(ref.key + '.', ''), it.value]})
log.info('created reference ${_ref}')
parent.addToReferences(_ref)
}

Related

How to add a multiple enum field to the create and edit forms in Grails 4?

In my domain model, some classes have a collection of enums as a field. I have modeled it in two differente ways, as an embedded collection:
class A {
String name
Set<Enumeration> enumerations
static embedded = ['enumerations']
}
enum Enumeration {
ENUM_VALUE_1,
ENUM_VALUE_2,
ENUM_VALUE_3
}
And also as a hasMany collection:
class A {
String name
static hasMany = [enumerations:Enumeration]
}
enum Enumeration {
ENUM_VALUE_1,
ENUM_VALUE_2,
ENUM_VALUE_3
}
In both cases, I can add enum values to the collection in BootStrap.groovy in the following way:
A.withTransaction { status ->
def a1 = new A( name:"a1" )
a1.addToEnumerations( Enumeration.ENUM_VALUE_1 )
a1.addToEnumerations( Enumeration.ENUM_VALUE_2 )
}
Using scaffolding, I can see the content of the enumeration collection in the index and show pages, but in the edit and create pages, only the label is shown, no widget is displayed.
Which is the simplest way to show a widget, e.g. a multiple select, for this kind of fields in Grails 4 (I am using Grails 4.0.3)?
Thanks in advance.
Which is the simplest way to show a widget, e.g. a multiple select,
for this kind of fields in Grails 4 (I am using Grails 4.0.3)?
You can use the select tag.
<g:select multiple="true" name="someName" from="${somePackage.SomeEnumClassName}"/>
This is the process I followed after #jeff-scott-brown answer:
I generated the views for class A:
generate-views A
Then in grails-app/views/a/edit.gsp and grails-app/views/a/create.gsp I changed this element:
<f:all bean="a"/>
into these other elements:
<f:with bean="a">
<f:field property="name"/>
<f:field property="enumerations">
<g:select
multiple="true"
name="${property}"
from="${Enumeration}"
value="${a?.enumerations}"
/>
</f:field>
</f:with>
And it works for both the embedded and the hasMany approaches.
Should not this be the default scaffolding for collections of enumerations in Grails?
Well if this is for validating data for your database, you can use the 'inList' constraint on your domain as the same constraint by extracting that as a 'List' for you to use:
def class = grailsApplication.getDomainClass(className)
def props = class.getConstrainedProperties()
props.each(){ k,v ->
if(k=='theColumnName-you-are-trying-to-get'){
return v.getInList()
}
}
This is a simpler solution without having to generate the views, i.e. no need to modify edit.gsp and create.gsp. Just using the fields plugin as suggested by #jeff-scott-brown.
Assuming this is your domain class:
package mypackage
class MyClass {
// ...
// properties
// ...
Set<MyEnumeration> enumerations
// no need to declare it as embedded
// static embedded = ['enumerations']
}
enum MyEnumeration {
ENUM_VALUE_1,
ENUM_VALUE_2,
ENUM_VALUE_3
}
And that you have generated a scaffold controller for it, i.e.
package mypackage
class MyClassController {
static scaffold = MyClass
}
The only thing you have to do is creating the file grails-app/views/_fields/myClass/enumerations/_widget.gsp with the following content:
<g:select
multiple="true"
name="${property}"
from="${mypackage.MyEnumeration}"
value="${myClass?.enumerations}"
/>
It also works if you declare the collection of enumerations as a hasMany association, i.e.
package mypackage
class MyClass {
// ...
// properties
// ...
static hasMany = [enumerations:MyEnumeration]
}
enum MyEnumeration {
ENUM_VALUE_1,
ENUM_VALUE_2,
ENUM_VALUE_3
}

how do I write a set for g:select tag

How do I write options so I can generate it into an HTML select? The problem with this is "options" needs a set, not an array
Here is everything I have. I know the naming convention is bad, and I will fix that, but for right now I've been on this issue for days now.
Controller Class
import org.springframework.dao.DataIntegrityViolationException
import grails.plugin.mail.*
class EmailServiceController {
static defaultAction = "contactService"
def contactService() {
def options = new ArrayList()
options.push("Qestions about service")
options.push("Feedback on performed service")
options.push("Other")
options.push("Why am I doing this")
options
}
def send() {
sendMail(){
to "mygroovytest#gmail.com"
from params.email
subject params.subject
body params.information
}
}
}
Domain class
class EmailService {
static constraints = {
}
}
g:select call from the gsp
<g:select name = "subject" from = "${options}" noSelection="Topic"/>
also tried the following with "${selectOptions}" instead of "${options}" with no luck
def selectOptions() {
def options = new ArrayList()
options.push("Qestions about service": "QAS")
options.push("Feedback on performed service":"FoPS")
options.push("Other":"Other")
options.push("Why am I doing this":"WHY")
return options
}
Ok, I think I might know what is going on here. The missing piece to the question is what gsp is being called. Here is the appropriate way:
class EmailServiceController {
def contactService() {
def options = ["Qestions about service", "Feedback on performed service", "Other"]
// assumes you are going to render contactService.gsp
// you have to push the options to the view in the request
[options:options]
}
}
And then in contactService.gsp you would have:
<g:select name="subject" from="${options}" noSelection="['Topic': 'Topic']"/>
Your options is neither an array nor map. There is a syntax error. That's why you have only one option in your select. You need to enter either a real list or a map, like that:
def selectOptions() {
def options = [:]
options["Qestions about service"] = "QAS"
options["Feedback on performed service"] = "FoPS"
[options:options]
}
Using a map you can use it in the view like this:
<g:select name="subject" from="${options.entrySet()}"
optionValue="key" optionKey="value"
noSelection="['Topic': 'Topic']"/>
You need to use double quotes in your tags, not single quotes. With single quotes, you're just passing a String that looks like '${options}' instead of passing a GString with the value of options.
<g:select name="subject" from="${options}" noSelection="Topic"/>
In addition, assuming you're calling the contactService action, you need to return options instead of returning options.push("Other"). push() returns a boolean, which means the implicit return of contactService is the boolean result of push() instead of options.

Couldn't able to save a domain class instance; getting type mismatch error

I have domain classes as:
package mnm.schedule
class Project {
static hasMany = [ tasks : Tasks , users : User ]
String name
static constraints = {
name(nullable:false)
tasks(nullable:true)
}
}
User.groovy
package mnm.schedule
import org.example.*;
class User extends SecUser {
//relationships. . . .
static belongsTo = [ company : Company, role : Role, resource : Resource]
static hasMany = [ holidays : Holiday, tasks : Tasks, pt:String ]
Profile profile
Project project
String username
String password
boolean enabled
List pt
boolean ptTaken
}
I have a view file, inside which i have this code snippet :
<g:each in="${ans}">
<li>${it.username.toUpperCase()}<g:checkBox name="checkedUsers" value="${ans}" checked="false" /></li>
</g:each>
The variable ans is the arraylist, that has user objects. I use g:checkBox, so that end user can "check" the required users name.When the user submits this form, I do this action in my controller:
def users = params.checkedUsers
users.each { index ->
new Project(name:"testing",users:index).save()
}
The idea is that I need to add the choose user(via checkbox) to the project.
But this throws the error as :
2012-02-03 10:13:08,173 ["http-bio-8080"-exec-4] ERROR errors.GrailsExceptionResolver - TypeMismatchException occurred when processing request: [POST] /scheduleNew/project/project - parameters:
_checkedUsers:
_checkedUsers:
Add: Add
checkedUsers: anto2
Provided id of the wrong type for class mnm.schedule.User. Expected: class java.lang.Long, got class java.lang.String. Stacktrace follows:
Message: Provided id of the wrong type for class mnm.schedule.User. Expected: class java.lang.Long, got class java.lang.String
Whats going on? Where I went wrong?
Seems a few things a bit odd to me (but maybe I am not fully understanding what you are trying to achieve).
View:
<g:each in="${ans}">
<li>${it.username.toUpperCase()}<g:checkBox name="checkedUsers"
value="${it.id}" checked="false" /></li>
</g:each>
I would pass the id rather than the entire list (you had value="${ans})
In the controller, the passed params are of type String, that's why you are getting the type mismatch. There are several ways to do this, one solution is below:
Controller:
def actionCalled = {
def project = new Project(name:"testing")
def users = params.checkedUsers
users.each { index ->
def user = User.findById(index.toLong())
project.addToUsers(user)
}
project.save()
}
I'm sure you can simplify this further.

failed to lazily initialize a collection of role

Hey all, I'm building a GRAILS application with a m:m db relation. As I try to show the entries the well known "failed to lazily initialize a collection of role ... no session or session was closed" error is shown.
One class is:
class Hazzard{
static hasMany = [warning:Warning]
static constraints = {
text(size:1..5000)
}
String name
String text
String toxicity
}
the other:
class Warning{
static hasMany = [hazzard:Hazzard]
static belongsTo = Hazzard
static constraints = {
text(size:1..5000)
}
String code
String text
}
In Hazzard/show the following code works fine
<g:each in="${hazzardInstance.warning}" var="p">
<li><g:link controller="Warning" action="show" id="${p.id}">${p?.encodeAsHTML()}</g:link></li>
</g:each>
but on other pages the following code will provide the error:
<g:set var="haz" value="${Hazzard.get(params.id)}" />
<h1>${haz.name}</h1>
<p>${haz.text}</p>
<h1>Toxiciteit</h1>
<p>${haz.toxicity}</p>
<br/>
<h1>Gevaren(H) en voorzorgen(P)</h1>
<g:each in="${haz.warning}" var="p"> --> This is where the error pops-up
${p.text}
</g:each>
Any clues on where this fails?
A more favorable approach to what you're trying to do would be to perform the get in the controller and pass the found domain object to the view for rendering. Something like:
// MyController.groovy
class MyController {
def myAction = {
def haz = Hazzard.get(params.id)
render(view: 'myview', model: [hazzardInstance: haz])
}
}
// my/myview.gsp (the view from your second GSP code block)
<h1>${hazzardInstance?.name.encodeAsHTML()}</h1>
...
<h1>Gevaren(H) en voorzorgen(P)</h1>
<g:each in="${hazzardInstance?.warning}" var="p">...</g:each>
Doing GORM lookups in the view can sometimes lead to the exception you're getting, although I thought many problems like that had been fixed in more recent versions of Grails. Nonetheless, using a more correct idiom for querying and rendering views will help you to avoid this error.

In Grails how do I capture multiple selected items in g:select?

I have a Contact Domain class that can be associated with multiple Organizations, which are also domain classes. I want to use a multiple selection box to allow the user to select the organizations that are associated with the current contact. The selection box is populated with the available organizations. How do I assign the selected items to the organizations list in my Contact class?
<g:select name="organizations.id"
multiple="multiple"
optionKey="id"
from="${com.ur.Organization.list()}"
value="${contact?.organizations}" />
The above is what I am currently trying, and while it does populate the selection with organizations it doesn't seem to put the selected items in my organizations field.
Thanks for any advice.
Edit:
Incorporated comments from krsjunk and omarello.
Here's an abbreviated version of the domain classes.
class Contact{
static searchable = true
static mapping = {
sort "lastName"
}
String firstName
String lastName
.
.
.
static belongsTo = [organizations:Organization, projects:Project]
}
class Organization {
static searchable = true
static mapping = {
sort "name"
}
String name
static hasMany = [contacts:Contact]
}
Well just change the name to
<g:select name="organizations" multiple="multiple"
optionKey="id"
from="${com.ur.Organization.list()}"
value="${contact?.organizations}" />
Should work fine, just tried it.
Note my domain definitions look like this, (just in case you have something different
class Contact {
static constraints = {
}
static hasMany = [organizations:Organization]
String name
}
class Organization {
static constraints = {
}
static hasMany = [contacts:Contact]
static belongsTo = [Contact]
String name
}
one problem is that value="contact?.organizations" should be value="${contact?.organizations}" — not sure if that is the whole problem or not. (also, the attribute multiple=".." is not necessary if value is a collection)
You may also need name="contact.organizations" to be name="contact.organizations.id" and another attribute optionKey="id"
In your newly edit domain example you don't have a one-to-many relationship between contact and organization. You have a one-to-many from organization to contact.
So
value="${contact?.organizations}"
will always be a single item, never a list.
Trying to select/assign multiple organization to a contact will never be valid.

Resources