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()
}
}
Related
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
I have a project with a form that is completed over several parts. E.g. Part 1 might include your name, and part 2 your address.
In MVC I have several views which represent each of the parts and one model to hold all the form data.
I would like to save the form data at the completion of each part, and retrieve the saved record on the next part (so that the whole model is available at each stage).
I am using Post-Redirect-Get to avoid the 'form may be resubmitted' warnings from browsers using the back and forward buttons.
Controller code
[HttpGet]
[Route("Form/Part/{id}")]
public ActionResult Part(int id, int? recordId)
{
var model = new MyVM();
if(recordId.HasValue) {
// get record from DB
// map record to 'model'
}
return View("Form.{id}", model);
}
[HttpPost]
[Route("Form/Part/{id}")]
public ActionResult Part(int id, MyVM model)
{
// map model to entity
// save/update entity
return RedirectToAction($"Form/Part/{id + 1}", new { recordId = recordId })
}
My question is how best to retain the ID of the newly created / updated record and pass it to the next form part.
Whilst passing the recordId as a routeValue works, it breaks when I use the browser back button as the recordId in the querystring is lost.
I have considering storing this in the session perhaps, but don't really want to bring the HttpContext into play if I don't have to for testing reasons.
Is there a recommended way to achieve this kind of thing? Any thoughts on the various ways to keep track of the current record would be greatly appreciated.
You can either use the TempData or Session to pass Data between requests.
I Would create a big class / object with all the fields from the wizard, throw it in the session eg key "MyWizardData" and retrieve it in the other parts of the wizard.
After
// save/update entity
you have the ID, throw it in the session and grab it back in the next action.
If you dont want to use the session, get the id after save / update and store it in a field in the database in the user table. Or just store the UserId with your created entity row. Then you can grab it in any action.
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.
When you generate grails views, grails looks at your relationships and generates the right html for your form data to be automatically binded to the back end domain. For one to one associations grails creates a drop down list.
However, you might not want to present that property as a drop down list but something more custom (for example a text field with autocomplete). As soon as you do that the value that comes to the controller from that field, comes in as a String and you have to first:
Clear errors
Perform a findBy based on a given param and assign it to the property of the domain
I really want to avoid doing findBys in the controller as much as possible because it seems like I am doing logic/things that should not go there. The controller should delegate to the Service layer. It is not clear to me from the grails documentation how would I do that by using bindData which seems to work really well with String, date, Integer properties etc.. but I do not see how bindData is used for properties that are other domains.
I also really want to avoid passing the params object to the Service layer as it seems less reusable (or maybe not, correct me if I am wrong). I guess that I do not like how it looks semantically. I would prefer the first over the second:
#Transactional
class WithdrawService {
def addWithdraw(Withdraw withdraw) {
//perform business logic here
}
def createWithdraw(Map params){
//perform business logic here
}
}
Let's take the following example:
class Withdraw {
Person person
Date withdrawDate
}
and the parent lookup table
class Person {
String name
String lastName
static constraints = {
}
#Override
public String toString() {
return "$name $lastName"
}
}
In order for the bind to happen automatically without any extra work grails passes in the following request params to automatically bind the one to one:
person.id
a person map with the id.
[person.id:2, person:[id:2], withdrawDate:date.struct, withdrawDate_month:11, create:Create, withdrawDate_year:2015, withdrawDate_day:10, action:save, format:null, controller:withdraw]
What is the best way to go about this?
Pass two hidden fields that look exactly like this: person.id:2, person:[id:2] that get populated as a result of the Ajax call that populates the autocomplete?
In the controller do a Person.findBySomeKnownProperty(params.someKnownValue)
Or any other approach?
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.