I have to keep track of sequential submissions per fiscal year. Given the fiscal year '2015' then the numbering should be '2015001, 2015002, 2015003, etc'.
I have defined a domain class to keep track of these settings:
class MYAPPConfig {
String fiscalYear
Integer requestCount
static constraints = {
fiscalYear (size: 4..4, nullable: false)
requestCount(max: 999, nullable: false)
}
}
The idea is that for a new fiscal year I will add a new record and the 'requestCount' will be reset to 0 (or 1 depending on how Grails wants to manage it).
Ideally this field should be mapped to an Oracle sequence field. If that's not possible then should I manage the increment logic in a service method?
My Grails version is 2.4.2
Thanks.
I figured it out.
I took a different route (after more googling). I added dateCreated to my model which is managed by Grails so its updated automatically. So all I needed to do is get the record with the latest date (every year we will add a new record for the coming fiscal year). Take note of the [0] at the end of the call, that flattens the array of arrays returned and allows me to deal with a single object.
My model now looks like this ( MYAPPConfig.groovy ) :
class MYAPPConfig {
String fiscalYear
Integer requestCount
Date dateCreated
static constraints = {
fiscalYear (size: 4..4, nullable: false)
requestCount(max: 999, nullable: false)
}
}
I created the following service ( ManageRequestsService.groovy )
import grails.transaction.Transactional
#Transactional
class ManageRequestService {
def getNextTrackingId() {
def latestConfig = MYAPPConfig.listOrderByDateCreated(max:1, order: "desc")[0]
def latestFiscal = latestConfig.fiscalYear
Integer sequence = latestConfig.requestCount
sequence++
latestConfig.requestCount = sequence
latestConfig.save(flush:true)
return latestFiscal + sprintf('%03d', sequence)
}
}
And in my controller ( MyTestController.groovy ) I have:
class MyTestController {
def manageRequestsService
def test() {
def trackingId = manageRequestsService.getNextTrackingId()
render "Next id is: ${trackingId}"
}
}
Giving the following output ( http://localhost:8080/MYAPP/myTest/test ):
Next id is: 2015001
Refresh page!
Next id is: 2015002
Refresh again!
Next id is: 2015003
Related
I have an app with the following entities:
Topic:
class Topic {
UUID id
String description
String name
boolean visibility = true
// Relation
static hasMany = [tests:Test]
...
}
Test:
class Test {
UUID id
boolean active = true
String description
...
static hasMany = [evaluationsTest: Evaluation]
static belongsTo = [topic: Topic, catalog: Catalog]
}
When I show all visible topics to the user I request the query:
def visibleTopics = Topic.findAllByVisibility(true, [sort:"name", order:"asc"])
This query returns me for example: [['English'], ['Spanish']]. Then, I can show the full information about each topic to the user.
But I also want to indicate to the user the number of active test in each visible topic.
For example:
English topic has 2 active test.
Spanish topic has a total of 2 test. One is active and the other is not.
German topic has not any active test.
Then I need a query whose result is: def activeTotalEachTopic = [[2],[1],[0]] and I can pass the activeTotalEachTopic variable to the view (.gsp).
Solution:
From the first query where I can obtain all visible topics, I get the number of active test.
def visibleTopics = Topic.findAllByVisibility(true, [sort:"name", order:"asc"])
def numberActiveTest = []
activeTopics.each { topic ->
def result = Test.findAllByTopicAndActive(topic, true).size()
numberActiveTest.push(result)
}
And I pass to the view both variables.
render view: 'home', model: [activeTopics: activeTopics, numberActiveTest: numberActiveTest]
What you are missing is grouping so that you get the count per group, rather than a total count.
You also need to change the join type from the default inner join to an outer join in order for topics without an active test to return 0. However, a side-effect of this is that it changes how association properties are referenced due to the alias that's created by the join. Something like this:
import static org.hibernate.sql.JoinType.*
def activeTotalEachTopic = Topic.createCriteria().list() {
createAlias('tests', 't', LEFT_OUTER_JOIN)
eq 'visibility', true
or {
eq 't.active', true
isNull 't.id'
}
projections {
groupProperty 'name'
count()
}
order ("name", "asc")
}
Now, another issue to address is that the output would be something like this due to the grouping: [['English', 2],['Spanish', 1],['German', 0]]. So what you can do is collect the second item in each sub-list:
activeTotalEachTopic*.getAt(1)
// Which is the equivalent of...
activeTotalEachTopic.collect { it[1] }
I'd like to know which is better to use, a database view or simply persist fields in the database.
For instance, I'm initializing win_count in the following view vw_stats:
...
CASE
WHEN game.in_decision = true AND game.user_score > game.opponent_score THEN 1
ELSE 0
END AS win_count,
...
Which is then mapped to a domain class:
package com.x
class Stat {
//fields here...
static mapping = {
table 'vw_stats'
version false
}
}
Or, should I persist the field winCount in the database using this domain class and manipulate it before saving?
package com.x
class Game {
//fields here...
Short winCount = 0
static constraints = {
//constraints here...
winCount nullable: false, range: 0..99
}
def beforeInsert(){
this.beforeUpdate()
}
def beforeUpdate(){
//other manipulations here...
if inDecision and userScore > opponentScore
winCount = 1
}
}
The issue with the view I find, is that it will generate a table when running the application and then I have to manually remove the table and run the code to generate the view.
Update #1
There might be an IO cost saving by persisting them in the database instead of the view?
Update #2
Forgot to mention, I should be able to apply an aggregate function on the resulting field in a service.
A third approach is to use a derived property. Like the view, the value is calculated on the fly.
package com.x
class Game {
//fields here...
Short winCount = 0
static constraints = {
//constraints here...
winCount nullable: false, range: 0..99
}
static mapping = {
winCount formula: 'CASE WHEN in_decision = true AND user_score > opponent_score THEN 1 ELSE 0 END'
}
}
Not fully understanding the exact application you are trying to achieve, have you thought about using transients and letting the domain instance calculate it when it is actually needed? This will avoid pre-calculating data that may not even be used.
i'm making a tables cleaning service that takes the table name and the date field as arguments , here is the service code :
def cleanTables(tableName , dateField) {
def comparisonDate
def numOfRecordsDeleted
use (TimeCategory){
comparisonDate=new Date() -1.year
}
numOfRecordsDeleted=tableName.where { dateField <=comparisonDate }.deleteAll()
log.info("deleted : " +numOfRecordsDeleted)
}
i'm successfully passing to this service the table name but i can't pass the date field , so how to get a specific property from a domain for example a domain named Payments got a property dateCreated , so i pass to my service Payments and dateCreated.
With where queries you have access to criteria query methods such as eq(), or in this case, le(). Those methods take the name of the property as an argument, which is what you need. I tweaked the code a bit because you're actually interacting with domain classes, not tables. Small distinction, until you start working with HQL.
def cleanDomainClass(String domainClassName, String dateField) {
def domainClass = Class.forName("some.package.$domainClassName")
def comparisonDate = use (TimeCategory) { new Date() -1.year }
def numOfRecordsDeleted = domainClass.where { le(dateField, comparisonDate) }.deleteAll()
log.info("deleted : $numOfRecordsDeleted")
}
Is there any way to set a default value to domain class property?
I have a class called PayMethod, where I want the name property to default to "Cash" and I want this default value when I create this table, is this possible using Constraints?
package abc
import util.UserUtil
import embed.AuditUser
class PayMethod {
String name = "Cash"
AuditUser audit = new AuditUser()
static embedded = ['audit']
static constraints = {
name blank: false, size: 5..30, unique: true
}
static mapping = {
table 't01i0010'
id column: 'F_ID', precision: 4, scale: 0
name column: 'F_NAME', length: 30, defaultValue: 'Cash'
version column: 'F_REVISION'
}
def authUserService
int insertIndex = 0
int updateIndex = 0
static transients = ['authUserService', 'insertIndex', 'updateIndex']
def beforeInsert = {
audit.entryUser = UserUtil.user()
audit.entryDate = new Date();
}
def beforeUpdate = {
audit.reviseUser = UserUtil.user()
audit.reviseDate = new Date();
}
def afterInsert = {
if(insertIndex == 0){
def user = audit.entryUser
def date = audit.entryDate
log.info "POST INSERT => ENTERER: ${user} ENTERED: ${date}"
}
insertIndex++
}
def afterUpdate = {
if(updateIndex == 0){
def user = audit.reviseUser
def date = audit.reviseDate
log.info "POST UPDATE => REVISE: ${user} REVISED: ${date}"
}
updateIndex++
}
}
This will be possible in 2.2 which should be released this week or next. See http://jira.grails.org/browse/GRAILS-5520 for the relevant feature request. The syntax will be
static mapping = {
name defaultValue: "'Cash'"
}
For now you'll need to do what you're doing - set the value as the default value of the field. You can manually update the database schema, or do the work as part of a migration.
To build on the previous answer, you can use the defaultValue attribute in Grails 2.2 but you need to be careful to put double and single quotes around default values for String properties and double quotes around integer properties so that the default values appear correctly in the DDL. So, for instance, you need to use:
static mapping = {
myStringProperty defaultValue: "'Cash'"
myIntProperty defaultValue: "0"
}
If you only use single quotes, you will end up with an error like "Column "CASH" not found"
Also, as far as I can tell, default values do not work for properties that are enums.
Had the same issue and using static mapping didn't work for me either (using 2.2.3); the below link provided me a functional answer (set the default value in your object declarations):
http://grails.1312388.n4.nabble.com/How-to-set-a-default-value-for-column-td1383753.html
For String, encapsulate with quotes; int/integer should just be the value.
Hope this helps!
i have domain classes:
package test
class Credit {
String name;
static hasMany = [debts : Debt]
static constraints = {
}
}
and
package test
class Debt {
Integer amount;
Date date;
static belongsTo =[credits: Credit]
static constraints = {
}
}
Need: select max: 10; order: "desc"; sort: "date" rows of Debt associated with the Сredit.get(id)
How can i do it?
solution:
Debt.findAllByCredits(Credit.get(params.id),[max:10, sort:"date",order:"desc"])
but next question about this example:
why, this code work:
def ok = Debt.findAllByCredits(Credit.get(params.id),[max:10, sort:"date",order:"desc"])
println "true:" + ok
but this code not work correct:
def dd = new Debt(credits: Credit.get(params.id))
def wrong =Debt.findAll(dd)
println "no: "+ wrong
all time return all records in table, why?
you could do something like
def hql = "select d from Debt d where credits = ? order by d.date desc"
Debt.findAll(hql, [credit], [max:10])
you might have to tweak that, but something similar should work. Also note, I am assuming that you have an instance of credit that is the parent of the Debts.
You can also use the methods Grails generates dynamically at runtime, based on the properties of your classes
Debt.findAllByCredit(credit, [max:10,sort:"date",order:"desc"]
again, you will need a reference to a credit object.
Check out the docs at
http://grails.org/doc/latest/
specifically the section on findAll and findAllBy under Domain Classes in the left hand nav.