I am in the process of upgrading my grails 2 app to grails 4. I have been able to get all compile time errors corrected and now the app runs. It throws this error on hitting the controller action.
No enum constant com.company.compositeEvent.ResultsStatus.2
in this line in controller
if (idList){
result = RaceGroup.createCriteria().list([sort: 'startDateTime', order: 'asc', offset: offset, max: max]){
inList('id', idList)
join 'address'
}
}
RaceGroup has resultsstatus property
ResultsStatus resultsStatus
and ResultsStatus is an enum defined as
enum ResultsStatus {
NO_RESULTS(1),
RACE_RESULTS(2),
EXTERNAL_RESULTS(3)
int id
private ResultsStatus(int id){
this.id = id
}
}
It works fine in grails 2. My doubt is something has changed in grails 4, maybe the way enum is defined is wrong. I appreciate any guide.
UPDATE:
RaceGroup domain is defined as
class RaceGroup extends CompositeEvent implements Serializable{
List<Race> races
static hasMany = [races: Race]
static mappedBy = [races: 'raceGroup']
static mapping = {
discriminator RaceGroup.getSimpleName()
races cascade: 'all-delete-orphan'
}
static constraints = {
}
List<SubEvent> getSubEvents(){
this.races
}
}
CompositeEvent is defined as
abstract class CompositeEvent extends BaseEntity implements Serializable, Named, Addressable{
EventGroup eventGroup
/**
* Currency to use for this Event. Defaults to 'USD'
*/
final Currency currency = Currency.getInstance('USD')
/**
* User that created this event.
*/
User user
/**
* Name of this overall event.
* <p>
* 'compositeEventName' is used instead of just 'name' because when sorting
* by name, billingPerson.name is used by grails.
*/
String compositeEventName
/**
* General Information that is entered by the director and displayed on
* the registration page.
*/
String displayMessage
/**
* Location name of the address of this event
*/
String locationName
/**
* Address of this event.
* <p>
* Address is stored as one of its sub-classes: USAddress, CanadaAddress,
* etc.
*/
Address address
/**
* Start date and time of this event.
*/
Date startDateTime
/**
* Time zone to display the formatted date in.
*/
TimeZone timeZone
/**
* End date of this event.
*/
SqlDate endDate
/**
* The total number of possible participants, number reserved spots,
* and the number of allocated spots.
*/
Capacity capacity = new Capacity(reference: this)
String token = UUID.randomUUID().toString().replaceAll('-', '').take(12)
Boolean autoTransferToV6Active
Boolean showMaxParticipantsLimitInRegistrationPage = false
Integer showMaxParticipantsLimitBelowThreshold
/**
* Director on the registration page listed as being in charge of
* this event.
*/
Director director
/**
* Person or organization responsible for handling billing and payment
* in association with this event.
*/
BillingPerson billingPerson
String displayLiveRegistrationsPassword
Boolean displayLiveRegistrations
/**
* Whether the list of individuals registered for this event or any of
* its sub-events will be displayed on the registration page.
*/
Boolean displayParticipants
/**
* Whether the number of individuals registered for this event or any
* sub-events will be displayed on the registration page.
*/
Boolean displayEventRegNum
/**
* Whether the teams registered for this event or any sub-events will
* be displayed on the registration page
*/
Boolean displayTeams
/**
* Whether the number of people in each team will displayed on the
* registration page.
*/
Boolean displayTeamRegNum
/**
* Whether seed marks will be displayed in the list of participants
*/
Boolean displaySeedMarks
/**
* Whether to display the product photos in the public event page
*/
Boolean displayProductImages = true
/**
* Whether this event is listed among active events and can the
* registration page can be accessed.
*/
Boolean published = false // whether this composite event can be viewed
Boolean unlisted = false // whether this composite event is listed on the website
Boolean emailReceipts = true // email a receipt each time someone registers
/**
* The date from which age will be calculated for participants.
*
* The date must be:
* <p><i>
* this.startDateTime - 1 year <= this.ageAsOf <= this.startDateTime
* </i>
*/
SqlDate ageAsOf
/**
* External web-site with information regarding this event
*/
String homePage
/**
* Required fields of each participant that are set by the director.
*/
RequiredParticipantFields requiredFields
/**
* Logo for this event
*/
Image logo
/**
* Taxes applied to this event
*/
Tax tax
/**
* Access code to be access registration for all subEvents. An access code
* set in an order template would be used instead of this if both are set.
*/
String accessCode
/**
* Disclaimer that must be accepted by all participants before registering.
*/
Disclaimer disclaimer
/**
* TimingDetails contains data in regards to requesting and providing
* timing services from RunnerCard
*/
TimingDetails timingDetails
//Status of Referral. Whether or not to show the referral or share on facebook button
ReferralStatus referralStatus
String bibsRange
Boolean assignBibNumbers
//in registration form whether to allow user to select more than 1 shirt
Boolean allowMultiProductSelectionInRegistration
//in event registration page show products only purchase
Boolean enableProductsOnlyPurchase = true
Boolean enableVirtualRace
/**
* Ids of past events all separated by a delimiter comma (,)
* eg 51243, 51234, 64345, 43454
*/
String eventHistory
Set<Product> products
String resultsLink
ResultsStatus resultsStatus
// automatically updated by GORM
Date dateCreated
Date lastUpdated
abstract List<SubEvent> getSubEvents()
static transients = ['name', 'nameIdAndParticipants']
static hasMany = [products: Product]
//static belongsTo = [eventGroup: EventGroup]
static mapping = {
address cascade: 'all'
director cascade: 'save-update,merge,refresh,evict,lock'
displayMessage type: 'text'
requiredFields unique: true
capacity cascade: 'refresh,delete'
bibsRange type: 'text'
}
static namedQueries = {
withAddress{
join 'address'
}
withBilling{
join 'billingPerson'
}
withRequiredFields{
join 'requiredFields'
}
}
static constraints = {
compositeEventName blank: false
showMaxParticipantsLimitInRegistrationPage nullable: true
showMaxParticipantsLimitBelowThreshold nullable: true
autoTransferToV6Active nullable: true
enableVirtualRace nullable: true
eventGroup nullable: true
eventHistory blank:true, nullable: true
displayMessage blank: false, nullable: true
locationName blank: false
referralStatus nullable: true
bibsRange nullable:true
assignBibNumbers nullable: true
allowMultiProductSelectionInRegistration nullable: true
enableProductsOnlyPurchase nullable: true
address()
timeZone bindable: true, inList: TimeService.TIME_ZONES
endDate validator: { SqlDate endDate, CompositeEvent obj ->
if (obj.startDateTime == null) return
Calendar start = Calendar.getInstance()
start.setTime(obj.startDateTime)
start.set(Calendar.HOUR_OF_DAY, 0)
start.set(Calendar.MINUTE, 0)
start.set(Calendar.SECOND, 0)
start.set(Calendar.MILLISECOND, 0)
Date startDay = start.getTime()
DateFormat formatter = DateFormat.getDateInstance(DateFormat.SHORT, LCH.getLocale())
if (startDay.compareTo(endDate) > 0){
return ["default.invalid.min.message", formatter.format(startDay)]
}
}
director validator: { Director person, CompositeEvent compositeEvent, Errors errors ->
UtilityService.cascadeValidation(person, 'director', errors)
}
billingPerson validator: { BillingPerson person, CompositeEvent compositeEvent, Errors errors ->
UtilityService.cascadeValidation(person, 'billingPerson', errors)
}
displayParticipants()
displayEventRegNum()
displayTeams()
displayTeamRegNum validator: {Boolean val, CompositeEvent obj, Errors errors ->
if (obj.displayTeams != null && val && !obj.displayTeams){
errors.rejectValue("displayTeamRegNum", "compositeEvent.invalid.displayTeamRegNum")
}
}
displaySeedMarks()
displayProductImages()
published()
homePage blank: false, nullable: true, validator: { String homePage, CompositeEvent compositeEvent ->
// If it is a url that isn't http:// or https://
if (compositeEvent.homePage != null
&& !compositeEvent.homePage.matches('^http(s)?://.*')
&& compositeEvent.errors.getFieldErrors('homePage').size() == 0){
return "default.invalid.url.message"
}
}
ageAsOf nullable: true // if null my.calculateAge() will calculate age based on the current Date
requiredFields nullable: true
logo nullable: true
tax nullable: true, validator: { Tax tax, obj, Errors errors ->
UtilityService.cascadeValidation(tax, 'tax', errors)
}
accessCode blank: false, nullable: true
displayLiveRegistrationsPassword nullable: true
displayLiveRegistrations nullable: true
resultsLink nullable: true
resultsStatus nullable: true
disclaimer()
timingDetails nullable: true
}
/**
* Saves capacity of this event at initial save
*/
void beforeInsert(){
this.capacity.save()
}
void afterInsert(){
CompositeEvent.withNewSession{
def productOwnerSettingService = this.getBean("productOwnerSettingService")
productOwnerSettingService.createSetting(this)
}
}
/**
* Checks that the logged in user has delete or admin privileges on
* this object and then deletes all AutoUpdateJob instances and acl
* objects that reference this object.
*/
void beforeDelete(){
CompositeEvent.withNewSession{
def posc = ProductOwnerSettingCompositeEvent.createCriteria().get(){
eq('event', this)
}
def setting = posc?.productOwnerSetting
if(posc){
posc.delete(flush: true)
}
if(setting) {
setting.delete(flush: true)
}
}
super.beforeDelete()
}
}
private Object getBean(String beanName){
try{
return this.domainClass.grailsApplication.mainContext.getBean(beanName)
}
catch(NoSuchBeanDefinitionException e){
return null
}
catch(MissingPropertyException e){
return null
}
}
/* (non-Javadoc)
* @see com.runnercard.Named#getName()
*/
String getName(){ this.compositeEventName }
void setName(String name){ this.compositeEventName = name }
}
Address domain is defined as
abstract class Address extends BaseEntity implements Serializable{
String address1
String address2
String city
String area
String postalCode
// automatically updated by GORM
Date dateCreated
Date lastUpdated
static belongsTo = [User, BillingPerson, CompositeEvent, RaceParticipant,
EmbeddedRaceParticipant]
static mapping = {
discriminator value: Address.getSimpleName(), column: 'country'
}
static constraints = {
address1 nullable: true
address2 nullable: true
city nullable: true
area nullable: true
postalCode nullable: true
}
static List getCountries(){
return ['usa', 'can', 'other']
}
boolean equals(Object obj){
if (!Address.isInstance(obj)) return false
Address other = (Address) obj
if (this.address1 == other.address1
&& this.address2 == other.address2
&& this.city == other.city
&& this.area == other.area
&& this.postalCode == other.postalCode){
return true
}
else{
return false
}
}
int hashCode(){
HashCodeBuilder builder = new HashCodeBuilder(1333, 1353)
if (this.address1) builder.append(this.address1)
if (this.address2) builder.append(this.address2)
if (this.city) builder.append(this.city)
if (this.area) builder.append(this.area)
if (this.postalCode) builder.append(this.postalCode)
builder.toHashCode()
}
}
the problem was i had to put this in mapping
resultsStatus enumType: 'ordinal'