Cannot change domain class property value in Grails - grails

I am developing a Grails 2.3.7 application and I'm having trouble changing a domain property with a select box. Every time I try to change the property and save, I get a HibernateException: identifier of an instance of Ethnicity was altered from X to Y. I don't want to change the ID of the ethnicity, I simply want to change the ApplicationPersons ethnicity from one to another.
A few things to note:
I am using the same controller action to create AND update the person.
Setting personInstance.ethnicity to null right before personInstance.properties = params will make the save work, but I
don't know why, and I don't want to do this for every association
that I want to change.
I realize the domain model seems odd. It is a legacy DB that I cannot change.
Here are my domain classes:
class ApplicationPerson implements Serializable {
Integer appId
Integer applicationSequenceNumber
String firstName
String lastName
Ethnicity ethnicity
static mapping = {
id composite: ['appId', 'applicationSequenceNumber'],
generator: 'assigned'
}
}
class Ethnicity {
String code
String description
static mapping = {
id name: 'code', generator: 'assigned'
}
}
Here is my _form.gsp to update the Ethnicity (I removed all the other properties that are saving just fine):
<div class="fieldcontain ${hasErrors(bean: personInstance,
field: 'ethnicity', 'error')} ">
<label for="ethnicity">Ethnicity</label>
<g:select id="ethnicity"
name="ethnicity.code"
from="${Ethnicity.list()}"
optionKey="code"
value="${personInstance?.ethnicity?.code}" />
</div>
And lastly, my controller action that the form POSTs to:
def save() {
Application app = applicationService.getCurrentApplication()
// Find/Create and save Person
ApplicationPerson personInstance = app.person
if (!personInstance) {
personInstance =
new ApplicationPerson(appId: app.id,
applicationSequenceNumber: app.sequenceNumber)
}
personInstance.properties = params
if (!personInstance.validate()) {
respond personInstance.errors, view:'edit'
return
}
personInstance.save flush:true
redirect action: 'list'
}

Modify the name in the select element from ethnicity.code to personInstance.etnicity.code as shown below:
<g:select id="ethnicity"
name="personInstance.ethnicity.code"
from="${Ethnicity.list()}"
optionKey="code"
value="${personInstance?.ethnicity?.code}" />
The name of selected option gets bound to params as key and the selected value as the
value against the key. Using ethnicity.code would try to modify the primary key of an existing ethnicity instead of modifying the ethnicity of an application person.
UPDATE
Above change in name is optional (can be used in case you don't need params to be assigned as properties of domain class). Previous name ethnicity.code should work as well but below changes are also required in the controller action in order to set ethnicity:
//or use params.ethnicity.code
//if name is ethnicity.code
person.ethnicity = Ethnicity.load(params.personInstance.ethnicity.code)
//or use params if name is ethnicity.code
person.properties = params.personInstance
Load an existing Ethnicity based on the passed in code and then set it in person before setting properties.
The issue lies with code being the primary key of Ethnicity. If Ethnicity had a separate identity column (for example, the default Long id) then your exact implementation in question would work with the help of data binding. But since it is mentioned that you are working with legacy database, I suppose you won't be able to modify the tables to add another column for id. So your best bet will be to load(cheap compared to get, as row is loaded from hibernate cache) ethnicity from the passed in code and then set it to person.
You can also see try caching the Ethnicity domain if possible because that will be master data and a good candidate for caching.

Related

Grails data won't bind if the id column is renamed

Grails will create an id and a version columns from a domain class automatically. I want to use my own column for the primary key. So, I follow the doc to change the mapping.
class book {
String isbn
static mapping = {
id generator: 'assigned', name: 'isbn'
}
}
So far so good. The isbn column is now the primary key.
I use generate-all to create the view and controller. However, the data binding won't work anymore.
Create and Save work no problem. It binds a book to the view. I can add a new book to the database no problem.
def create() {
respond new Book(params)
}
def save(Book book) {
if (book == null) {
notFound()
return
}
...
}
But the Update action does not bind. book is null after I click the Update button from the Edit view.
def update(Book book) {
if (book == null) {
notFound()
return
}
...
}
The codes generated by generate-all in the Save and Update actions are the same. I don't understand why it will bind the book to the Save action but not to Update action.
Would you show me the problem please?
Many Thanks!
I think I figure it out. When I bind an object to a view, Grails is hardcoded to look for the id property. It has to be spelled "id". If there is no "id" property in the domain class, Grails will not bind.
The way I figure this out is to look at the actual HTML generated by the server.
If there is an id property bind to the view, I see the HTML has the ../controller/action/id link.
If the id property is missing, the HTML link is just ../controller/index
I am new to Grails. So, I guess in order for the binding to work, I need to have an id property for Grails to put in the link.
I think this is a REST call. I don't know what REST is though.
So, I will try to add an dummy id property to my Book domain class to see if Grails will take the bait. I will set up the domain so Grails won't generate the id column in the database table. The id property is used locally only. No need to save it to the database.
class book {
String isbn
String id
static mapping = {
id generator: 'assigned', name: 'isbn'
}
}
I will copy the isbn value to the id property. I am not sure if this will work or not. I hope Grails will generate the link in the view with the isbn string in the id property instead of the default integer id value.
../controller/action/978-3-16-148410-0

Grails can't see database table's content

I'm using Grails version 2.4.4 and postgresql. When I run app, I see error message Cannot get property 'myname' on null object. I know that table is not empty and database connected correctly, because I can upload and see data using scaffolding.
domain class code:
class My_table {
//Integer id
String myname
static constraints = {}
}
Controller code:
class My_tableController {
def index() {
def my_table = My_table.list()
[my_table:my_table]
}
My index.gsp file:
<g:select name="name" from="${my_table}"/><br/>
<label>${my_table.myname} </label><br/>
In the form that I see the error happens in this line: <label>${my_table.myname} </label><br/>.
Here you are calling the property name out of context
<g:select name="name" from="${my_table}"/><br/>
<label>${my_table.myname} </label><br/>
You get an error because my_table is a list of My_table instances and does not have a property named name.
A way to fix this problem could be:
<select name="name">
<g:each in="${my_table}" var="table">
<option value="${table.name}">${table.name}</option>
</g:each>
</select>
Also remember label tags are not valid inside a select, as you can read Permitted content Zero or more <option> or <optgroup> elements. in https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select
Table is a reserved word in most databases. Either change the name of the class and property or map them to something else that is not reserved:
static mapping = {
table 'my_table'
}
Adding the column named table to a table named table is problematic because in the static mapping block a method named table already exists. Newer versions of grails have an ORM mapping block that is more flexible.
static mapping = {
table 'my_table'
table column: 'my_table' // unsure if this would work since it overlaps with the other mapping method
}

Mapping a generated database column to a Grails domain class property

In my Grails 2.5.X app, I have a domain class that looks like this:
class FormData {
String submittedFields
Boolean submitted
static constraints = {
submittedFields nullable: true
}
static mapping = {
// can I do something here to map submitted to a generated
// column of the form_data table
}
}
I would like to map the submitted property to a generated column of the form_data table, i.e. a column that would be created by the SQL statement
alter table form_data add submitted tinyint
GENERATED ALWAYS AS (if(submitted_fields is null,0,1));
Specifically, this generated column should be created when I create the schema from the domain model, e.g. by running the schema-export script.
A consequence of submitted being mapped to a generated column is that the corresponding domain class property should be read-only, or at least, assigning a value to it should have no effect.
If you want to handle the value of the column on database side only, and dont want it to be inserted or updated from grails/hibernate side. you can make the column as insertable:false updatetable:false
static mapping = {
submitted insertable:false, updateable:false
}
Now, even if the value is changed in grails, the new value will not be updated in database.

Retrieving value from database in Grails

I have one column named invoice_number in my database.
class Customer {
String name
int invoiceNumber
static constraints = {
name(blank: false)
invoiceNumber(unique: true)
}
}
There is no field for invoice_number in index.gsp file.
<g:form controller="customer">
Name:<br>
<g:textField name="name"></g:textField><br>
<g:actionSubmit value="Submit" action="Save"></g:actionSubmit><br>
</g:form>
I want to generate one invoice number and increase it with a difference of 5. For example, when first customer submits form, the invoice number may be generated as 105. When the second customer submits form, the invoice number should be 110. They should be saved in a database and they must be unique.
Then, I want to retrieve the invoice number from database for the customer who submits the form, and then, pass that invoice number to another gsp file.
How can I do it?
You need to add the logic of generating / incrementing the invoiceNumber in controller's action to which you are giving call on form submit.
Might get you started on your way (but I still don't get the 1 invoice per customer part, or incrementing by 5).
Yes, as mentioned by Abhinandan, you can put logic of ID creation in the controller, but a more reusable route might be to create a custom key generator, and point your class to use this generator for record IDs.
Suppose we have:
package tester2
class Custo {
String id // string just to show it can be anything
String name
// tell GORM about your id attribute, and setup your 'id' column
// to use a custom id generator function
static mapping = {
id column:"id", generator:"tester2.CustomIdGenerator"
}
}
then in src/groovy/tester2/CustomIdGenerator.groovy
package tester2
import org.hibernate.id.IdentifierGenerator
import org.hibernate.engine.spi.SessionImplementor
class CustomIdGenerator implements IdentifierGenerator {
public synchronized Serializable generate(SessionImplementor session, Object obj) {
// here's where you would have to access some persistent store to
// remember your last generated ID, fetch the last value, add 5,
// and store that new value to be ready for the next call
// -- don't know what DBMS you intend to use, but some have
// sequence support that will let you tinker with the starting
// point and increment. Maybe it's a simple as setting up a
// customized sequence and asking for nextVal()
//
// for this example, I just use a random UUID
return UUID.randomUUID().toString()
}
}

How can I skip bean validation if all fields for the entity are empty?

I have a address entity like this:
#Entity
public class Address implements java.io.Serializable {
#Id #GeneratedValue(strategy = IDENTITY)
private Long id;
#Size(max = 100)
private String street;
#Size(max = 15)
private String nr;
#Field
#Size(min = 1, max = 20) #NotBlank
private String city;
}
This is part of several other entities. For one such entity the address is optional.
I have a view where our users can edit the whole entity via several form inputs, including several address fields.
To be able to edit the address, I initialize the parent entity with an empty address entity.
Now the problem is: I don't want to persist an Address on the entity when all address fields are empty. For that to work, I need to skip validation for the entity if (and only if) all fields of Address are empty. How do I do that in the most elegant way?
What I'm currently doing is to skip bean validation altogether with <o:validateBean disabled="true" /> for the submit button, setting the address to null if it comes out empty. But this sounds like a total hack as it disables all validation in all cases. How can I do better?
You can just use EL in disabled attribute of <f:validateBean> (and <o:validateBean>). You can use EL to check if any of those fields have been filled. You can use binding attribute to reference a component in EL. You can check if a field has been filled by checking the value of the request parameter associated with component's client ID.
Then, to disable bean validation on those fields when #{addressIsEmpty} evaluates true, it's easier to use <f:validateBean> instead of <o:validateBean> as the former can be used to wrap multiple input components.
So, all with all, this should do:
<c:set var="addressIsEmpty" value="#{empty param[street.clientId] and empty param[nr.clientId] and empty param[city.clientId]}" />
<f:validateBean disabled="#{addressIsEmpty}">
<h:inputText binding="#{street}" value="#{bean.address.street}" />
<h:inputText binding="#{nr}" value="#{bean.address.nr}" />
<h:inputText binding="#{city}" value="#{bean.address.city}" />
</f:validateBean>
The <o:validateBean> is only easier to use if you intend to control it on a per-input or per-command basis.
Cross validation of multiple fields can be a tough issue. But in this situation it is not validation per se that bothers you in the first place but rather possibility to nullify some instance on some condition in your bean before you'd persist the entity.
The most straightforward way would be to mark all fields associated with Address entity as required="false" in your view not to care of fields being filled in (or just omit it as it's the default) and do all the checks of, and modifications to the placeholder in your action method, right before calling your EJB service. Like so:
public String save() {
//do some checks
if((entity.getAddress().getStreet() == null) && ...) {
//assign address to null
entity.setAddress(null);
}
entityService.persist(entity);
return "success";
}

Resources