grailsenumsgrails-4

grails 4 no enum constant?


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()
    }
}

Solution

  • the problem was i had to put this in mapping

        resultsStatus enumType: 'ordinal'