1

In my domain model, some classes have a collection of enums as a field. I have modeled it in two differente ways, as an embedded collection:

class A {
  String name
  Set<Enumeration> enumerations

  static embedded = ['enumerations']
}

enum Enumeration {
  ENUM_VALUE_1,
  ENUM_VALUE_2,
  ENUM_VALUE_3
}

And also as a hasMany collection:

class A {
  String name

  static hasMany = [enumerations:Enumeration]
}

enum Enumeration {
  ENUM_VALUE_1,
  ENUM_VALUE_2,
  ENUM_VALUE_3
}

In both cases, I can add enum values to the collection in BootStrap.groovy in the following way:

A.withTransaction { status ->
  def a1 = new A( name:"a1" )
  a1.addToEnumerations( Enumeration.ENUM_VALUE_1 )
  a1.addToEnumerations( Enumeration.ENUM_VALUE_2 )
}

Using scaffolding, I can see the content of the enumeration collection in the index and show pages, but in the edit and create pages, only the label is shown, no widget is displayed.

Which is the simplest way to show a widget, e.g. a multiple select, for this kind of fields in Grails 4 (I am using Grails 4.0.3)?

Thanks in advance.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • In a relational database, how would you represent the consequences of `static embedded = ['enumerations']` in a schema? I don't think that would be possible, would it? `enumerations` is a `Collection`. – Jeff Scott Brown May 04 '20 at 13:51
  • I suppose the relational schema for both modeling approaches is the same or very similar. I can save and retreive data using both of them. My problem is about the user interface and scaffolding. – Amador Durán Toro May 04 '20 at 16:49
  • "I suppose the relational schema for both modeling approaches is the same or very similar." - I don't think that is true. `embedded` causes that property to be stored in the same table as "owner" that references them so there would have to be a column for each entry in the collection, without any way to know how many there are ahead of time. We don't support that (because it isn't possible). – Jeff Scott Brown May 04 '20 at 17:05
  • @jeff-scott-brown: I have opened the in-memory H2 database and the relational schema is exactly the same for both modeling approaches. There is a table named A(ID,VERSION,NAME) with PK=ID, and another table named A_ENUMERATIONS(A_ID,ENUMERATION) with PK=(A_ID,ENUMERATION), that stores pairs(A.id,Enumeration). Think of "embedded" as the way of implementing "composition" in UML, for example. However, my question is about scaffolding the user interface, not about the underlying relational schema. – Amador Durán Toro May 04 '20 at 18:53
  • What you are describing there is not what `embedded` does but we don't support embedding collections. It can't be done. Think of a `Person` class and an `Address` class. in `Person` you have `Address homeAddress` and `static embedded = ['homeAddress']`. What that says is instead of creating an `ADDRESS` table, store all of the attributes that make up an `Address` in the same table with the `Person` attributes. That works because the properties in `Address` are finite and known about ahead of time. It would be impossible to do the same with a Collection of Enum. – Jeff Scott Brown May 05 '20 at 01:41
  • You can remove `static embedded = ['enumerations']` and I wouldn't expect anything to change. – Jeff Scott Brown May 05 '20 at 02:10
  • You are right, I have removed the `static embedded = ['enumerations']` and nothing has changed. – Amador Durán Toro May 05 '20 at 10:30

4 Answers4

1

Which is the simplest way to show a widget, e.g. a multiple select, for this kind of fields in Grails 4 (I am using Grails 4.0.3)?

You can use the select tag.

<g:select multiple="true" name="someName" from="${somePackage.SomeEnumClassName}"/>
Jeff Scott Brown
  • 26,804
  • 2
  • 30
  • 47
  • Thank you very much for your answer @jeff-scott-brown. I have added a detailed answer of what I did to solve my problem. Considering that the solution is quite simple, I think it should be the default scaffolding for collections of enumerations in Grails. – Amador Durán Toro May 05 '20 at 10:23
0

This is the process I followed after @jeff-scott-brown answer:

  1. I generated the views for class A:
generate-views A
  1. Then in grails-app/views/a/edit.gsp and grails-app/views/a/create.gsp I changed this element:
<f:all bean="a"/>

into these other elements:

<f:with bean="a">
    <f:field property="name"/>
    <f:field property="enumerations">
        <g:select 
            multiple="true" 
            name="${property}" 
            from="${Enumeration}"
            value="${a?.enumerations}"
        />
    </f:field>
</f:with>

And it works for both the embedded and the hasMany approaches.

Should not this be the default scaffolding for collections of enumerations in Grails?

  • If you want to involve the fields plugin then it would be more simple to use a property template (https://grails-fields-plugin.github.io/grails-fields/latest/guide/index.html#customizingFieldRendering). The `g:select` tag would go there and you wouldn't have to have `g:field` tags for every field. – Jeff Scott Brown May 05 '20 at 13:21
  • "Should not this be the default scaffolding for collections of enumerations in Grails?" - StackOverflow discourages asking questions in your answer. You are welcome to submit a feature request at https://github.com/grails-fields-plugin/grails-fields/issues. – Jeff Scott Brown May 05 '20 at 13:22
  • Submitted as https://github.com/grails-fields-plugin/grails-fields/issues/306 – Amador Durán Toro May 07 '20 at 17:49
0

Well if this is for validating data for your database, you can use the 'inList' constraint on your domain as the same constraint by extracting that as a 'List' for you to use:

def class = grailsApplication.getDomainClass(className)
def props = class.getConstrainedProperties()
props.each(){ k,v ->
    if(k=='theColumnName-you-are-trying-to-get'){
        return v.getInList()
    }
}
Orubel
  • 316
  • 4
  • 16
0

This is a simpler solution without having to generate the views, i.e. no need to modify edit.gsp and create.gsp. Just using the fields plugin as suggested by @jeff-scott-brown.

Assuming this is your domain class:

package mypackage

class MyClass {
  // ...
  // properties
  // ...
  Set<MyEnumeration> enumerations

  // no need to declare it as embedded
  // static embedded = ['enumerations'] 
}

enum MyEnumeration {
  ENUM_VALUE_1,
  ENUM_VALUE_2,
  ENUM_VALUE_3
}

And that you have generated a scaffold controller for it, i.e.

package mypackage

class MyClassController {
    static scaffold = MyClass
}

The only thing you have to do is creating the file grails-app/views/_fields/myClass/enumerations/_widget.gsp with the following content:

<g:select 
    multiple="true" 
    name="${property}" 
    from="${mypackage.MyEnumeration}"
    value="${myClass?.enumerations}"
/>

It also works if you declare the collection of enumerations as a hasMany association, i.e.

package mypackage

class MyClass {
  // ...
  // properties
  // ...

  static hasMany = [enumerations:MyEnumeration]
}

enum MyEnumeration {
  ENUM_VALUE_1,
  ENUM_VALUE_2,
  ENUM_VALUE_3
}