I'm trying to use an enum in a Grails 2.1 domain class. I'm generating the controller and views via the grails generate-all <domain class> command, and when I access the view I get the error shown below. What am I missing here?
Error
Failed to convert property value of type java.lang.String to required type
com.domain.ActionEnum for property action; nested exception is
java.lang.IllegalStateException: Cannot convert value of type
[java.lang.String] to required type [com.domain.ActionEnum] for property action:
no matching editors or conversion strategy found
Enum (in /src/groovy)
package com.domain
enum ActionEnum {
PRE_REGISTER(0), PURCHASE(2)
private final int val
public ActionEnum(int val) {
this.val = val
}
int value() { return value }
}
Domain
package com.domain
class Stat {
ActionEnum action
static mapping = {
version false
}
}
View
<g:select name="action"
from="${com.domain.ActionEnum?.values()}"
keys="${com.domain.ActionEnum.values()*.name()}" required=""
value="${xyzInstance?.action?.name()}"/>
EDIT
Now getting error Property action must be a valid number after changing the following.
View
<g:select optionKey='id' name="action"
from="${com.domain.ActionEnum?.values()}"
required=""
value="${xyzInstance?.action}"/> // I tried simply putting a number here
Enum
package com.domain
enum ActionEnum {
PRE_REGISTER(0), PURCHASE(2)
final int id
public ActionEnum(int id) {
this.id = id
}
int value() { return value }
static ActionEnum byId(int id) {
values().find { it.id == id }
}
}
Domain
package com.domain.site
class Stat {
static belongsTo = Game;
Game game
Integer action
static mapping = {
version false
}
static constraints = {
action inList: ActionEnum.values()*.id
}
String toString() {
return "${action}"
}
}
Take a look here ...
Grails Enum Mapping
Grails GORM & Enums
Also you may be hitting this as well. From the docs:
1) Enum types are now mapped using their String value rather than the ordinal value. You can revert to the old behavior by changing your mapping as follows:
static mapping = {
someEnum enumType:"ordinal"
}
Related
I have an abstract class in a groovy file:
Implementation 1
public abstract class Item {
public String testStr;
public String getBigTestStr(){
String s = "___" + this.testStr;
return s;
}
}
Which is inherited by
class Material extends Item {
public String testStr;
static marshalling = {
detail {
includes "bigTestStr"
}
summary {
includes "bigTestStr"
}
}
static mapping = {
table 'materialset'
id column: 'NODEID'
testStr column: 'MATERIALTYPE'
version false
}
}
The idea is that hitting the endpoint for a material will return the return value of Item.bigTestStr(). However, when I trace through Item.bigTestStr(), the debug's variables table shows a value for this.testStr, but is null when it is added to s. See here:
I tried taking the testStr property out of Material
Implementation 2
class Material extends Item {
static marshalling = {
detail {
includes "bigTestStr"
}
summary {
includes "bigTestStr"
}
}
static mapping = {
table 'materialset'
id column: 'NODEID'
testStr column: 'MATERIALTYPE'
version false
}
}
but I still get the same problem.
For both implementations the endpoint returns
{
bigTestStr: ____null
}
How can I get the actual value of Material.testStr to be used by the function in its parent class?
UPDATE
As Emmanuel pointed out, Implementation 2 is the right way to use properties from a parent class. However, this implementation does not seem to work with mapping the parent class' properties to a database column. So the real question is: How can I get Material.testStr to map to a database column?
It looks like your problem is in how you initialized your Material instance. Here's an example:
public abstract class Item {
public String testStr
public String getBigTestStr(){
"___$testStr"
}
}
class MaterialA extends Item {
public String testStr
static marshalling = {
detail {
includes 'bigTestStr'
}
summary {
includes 'bigTestStr'
}
}
static mapping = {
table 'materialset'
id column: 'NODEID'
testStr column: 'MATERIALTYPE'
version false
}
}
class MaterialB extends Item {
static marshalling = {
detail {
includes 'bigTestStr'
}
summary {
includes 'bigTestStr'
}
}
static mapping = {
table 'materialset'
id column: 'NODEID'
testStr column: 'MATERIALTYPE'
version false
}
}
Shown above are three classes Item, MaterialA, and MaterialB. The two material classes simulate your two tests: MaterialA has a testStr property, while MaterialB inherits a property with the same name from Item instead. Here's what happens when instances of both classes are initialized and getBigTestStr() is tested:
new MaterialA(testStr: 'Hello').with {
assert bigTestStr == '___null'
}
new MaterialB(testStr: 'Hello').with {
assert bigTestStr == '___Hello'
}
In short, your second approach, inheriting the property, works. A super class does not (and should not) have access to anything in its subclasses. It doesn't even know about its subclasses. The approach works because initializing testStr in an instance of MaterialB actually initializes the inherited property from Item; which of course is accessible within the Item class.
In your case, Grails is initializing the instances for you using the values stored in the database. So I'd check your database.
Update
Here's an example using a trait rather than an abstract class:
public trait Item {
String testStr
public String getBigTestStr(){
"___$testStr"
}
}
class Material implements Item {
static marshalling = {
detail {
includes 'bigTestStr'
}
summary {
includes 'bigTestStr'
}
}
static mapping = {
table 'materialset'
id column: 'NODEID'
testStr column: 'MATERIALTYPE'
version false
}
}
new Material(testStr: 'Hello').with {
assert bigTestStr == '___Hello'
}
This makes it so that there's no need for an Item table.
In my unit test under grails 2.2.4 I'm attempting to pass in an invalid enum to see if it gets rejected. It does not.
Here is my enum:
public enum CertificationStatus {
N("No"),
Y("Yes - Unverified"),
V("Yes - Verified")
final String value
CertificationStatus(String value) {
this.value = value
}
public String toString() {
value
}
public String getKey() {
name()
}
public String getValue() {
value
}
}
Here is my domain:
class Profile
CertificationStatus certFosterCertified
static constraints = {
certFosterCertified(blank: true, nullable: true)
}
Here is the unit test:
instance = new Profile(certFosterCertified: '#')
assertFalse instance.validate(['certFosterCertified'])
assertNotNull instance.errors.getFieldError('certFosterCertified')
The instance.validate returns true, but I'm passing in an invalid value for the enum in the Profile constructor ('#'). Shouldn't the validate fail because of the invalid enum? The enum is setup with only Y,N, and V as valid values. I didn't think I had to set those in the constraint because the field is defined as an enum.
With the constructor you set the value of certBackgroundCheck to #.
However, in your domain class your enum is named certFosterCertified. So certFosterCertified should be null, because it isn't initialized. According to the constraints null is a valid state (nullable: true).
Maybe you just need to change certBackgroundCheck to certFosterCertified?
I am not sure if I understand custom create method in Dozer mapper correctly. I need to translate bean property of type int to TransTypeCodebook object instance. But I am getting:
2013-09-13 15:47:27,009 [main] ERROR org.dozer.MappingProcessor - Field mapping error -->
MapId: null
Type: null
Source parent class: cz.jaksky.dozer.a.HolderA
Source field name: transType
Source field type: class java.lang.Integer
Source field value: 0
Dest parent class: cz.jaksky.dozer.b.HolderB
Dest field name: transTypeCodebook
Dest field type: cz.jaksky.dozer.b.codebook.TransTypeCodebook
org.dozer.MappingException: Illegal object type for the method 'setTransTypeCodebook'.
Expected types:
cz.jaksky.dozer.b.codebook.TransTypeCodebook
Actual types:
java.lang.String
My TransTypeCodebook class
public class TransTypeCodebook extends Codebook {
private int code;
private String label;
private TransTypeCodebook(int code, String label) {
this.code = code;
this.label = label;
}
public int getCode() {
return code;
}
public String getLabel() {
return label;
}
public static TransTypeCodebook getCodebook(int code) {
TransTypeCodebook result;
switch (code) {
case 0:
result = new TransTypeCodebook(0, "Case0");
break;
case 1:
result = new TransTypeCodebook(1, "Case1");
break;
default:
result = new TransTypeCodebook(code, "Not a valid code");
}
return result;
}
}
Mapper portion
<field>
<a>transType</a>
<b create-method="getCodebook">transTypeCodebook</b>
</field>
I manged to solve this issue by custom converters but I am not sure if I understand the concept of custom create method and more over I am wondering from where that String is comming. Can anyone put light on that?
I am not exactly sure, but to use static methods like that, you need to specify its fully qualified name.
<b create-method="your.domain.TransTypeCodebook.getCodebook">transTypeCodebook</b>
That's stated in the Dozer documentation.
I have a custom toString method in my enum:
enum TaxRate implements Serializable {
RATE23(23.0),
...
private String s
private BigDecimal rate
private TaxRate(BigDecimal s) {
this.s = s + "%"
this.rate = s * 0.01
}
public String toString() {
return s
}
Now when I display the rates in HTML I get nice output like TAX: 23.0%.
But what happens when a user selects the tax from a <select> and the sent value is i.e. 23.0% is that Grails can't create/get the TaxRate instance...
What should I override to support this custom mapping? Trying to override valueOf(String) ended with a error..
Have you seen the entry at the bottom of this page?
If you want to use a Enum with a "value" String attribute (a pretty common idiom) in a element, try this:
enum Rating {
G("G"),PG("PG"),PG13("PG-13"),R("R"),NC17("NC-17"),NR("Not Rated")
final String value
Rating(String value) { this.value = value }
String toString() { value }
String getKey() { name() }
}
Then add optionKey="key" to your tag.
in Grails, Is there a way to limit the size of the column to which the enum is mapped. In the following example, i would like the column type to be char(2)
enum FooStatus {
BAR('br'), TAR('tr')
final static String id
}
class Foo {
FooStatus status
static constraints = {
status(inList:FooStatus.values()*.id,size:2..2)
}
}
both inList and size do not have any effect when exporting the schema, the column type keeps its default value (varch(255))
Maybe i could do that if i define a new UserType. Any idea ?
Thank you
-ken
I don't think it's directly possible given the way enums are mapped internally in GORM. But changing the code to this works:
enum FooStatus {
BAR('br'),
TAR('tr')
private FooStatus(String id) { this.id = id }
final String id
static FooStatus byId(String id) {
values().find { it.id == id }
}
}
and
class Foo {
String status
FooStatus getFooStatus() { status ? FooStatus.byId(status) : null }
void setFooStatus(FooStatus fooStatus) { status = fooStatus.id }
static transients = ['fooStatus']
static constraints = {
status inList: FooStatus.values()*.id
}
static mapping = {
status sqlType: 'char(2)'
}
}
Adding the transient getter and setter allows you to set or get either the String (id) or enum value.
Grails ships with an undocumented (as far as I can tell anyway) custom Hibernate mapping for enums. The class is org.codehaus.groovy.grails.orm.hibernate.cfg.IdentityEnumType. It won't let you set the column size but does make it easy to change what is stored in the DB for each enum value without having to add transient fields to your model.
import org.codehaus.groovy.grails.orm.hibernate.cfg.IdentityEnumType
class MyDomainClass {
Status status
static mapping = {
status(type: IdentityEnumType)
}
enum Status {
FOO("F"), BAR("B")
String id
Status(String id) { this.id = id }
}
}
You can run an 'alter table' in Bootstrap.groovy to shrink the column:
DataSource dataSource
...
Sql sql = new Sql(dataSource)
sql.execute("alter table my_domain_class change column status status varchar(1) not null")
Even easier (works at least in Grails 2.1.0+)
class DomainClass {
Status status
static mapping = {
status(enumType: "string")
}
}
enum Status {
OPEN ("OPEN"),
CLOSED ("CLOSED"),
...
String name
Status (String name) {
this.name = name
}
}
Since GORM 6.1 identity enum mapping can be enabled with such construct
static mapping = {
myEnum enumType:"identity"
}