How to map Grails domain class properties to non-matching json string? - grails

Is there a built-in / easy way to set mappings between domain class properties and JSON strings that don't have exact matches for the property names?
For example, when I have a domain class:
class Person {
String jobTitle
String favoriteColor
static constraints = {
jobTitle(blank: false)
favoriteColor(blank: false)
}
}
And someone's giving me the following JSON:
{ "currentJob" : "secret agent", "the-color" : "red" }
I'd like to be able to still do this:
new Person(request.JSON).save()
Is there a way in groovy/grails for me to map currentJob -> jobTitle and the-color -> favorite color?
EDIT:
I've done a little experimenting, but I still haven't gotten it working. But I have found out a couple interesting things...
At first I tried overwriting the setProperty method:
#Override
setProperty(String name, Object value) {
if(this.hasProperty(name)) this[name] = value
else {
switch(name) {
'currentJob': this.jobTitle = value; break;
'the-color': this.favoriteColor = value; break;
}
}
}
But this doesn't work for two reasons: 1) setProperty is only called if there is a property that matches name and 2) "this[name] = value" calls setProperty, leading to an infinite recursive loop.
So then I thought, well screw it, I know what the incoming json string looks like (If only I could control it), I'll just get rid of the line that handles the scenario where the names match and I'll override hasProperty, maybe that will work:
#Override
void setProperty(String name, Object value) {
switch(name) {
'currentJob': this.jobTitle = value; break;
'the-color': this.favoriteColor = value; break;
}
}
#Override
boolean hasProperty(String name) {
if(name == "currentJob" || name == "the-color") return true
return false
}
But no, that didn't work either. By a random stroke of luck I discovered, that not only did I have to overwrite hasProperty(), but I also had to have an empty setter for the property.
void setCurrentJob(){ }
That hack worked for currentJob - I guess setProperty only gets called if hasProperty returns true and there is a setter for the property (Even if that setter is auto generated under the covers in grails). Unfortunately I can't make a function "setThe-Color" because of the dash, so this solution doesn't work for me.
Still stuck on this, any help would definitely be appreciated.
EDIT:
Overriding the void propertyMissing(String name, Object value){} method is called by this:
Person person = new Person()
person["currentJob"] = "programmer"
person["the-color"] = "red"
But not by this:
Person person = new Person(["currentJob":"programmer", "the-color":"red"])

Related

Grails 2.5, beforeDelete cannot access one-to-many relationship

Using Grails 2.5.6 here. I'm trying to access a Set of Strings off of my domain class in the beforeDelete GORM event. I'm seeing the deletes for this set getting issued in the database log before even getting to my breakpoint in the beforeDelete.
I'm getting a NullPointerException on my println(strings) below in my domain class.
My test domain class looks like
class DeleteTest {
Integer id
Set<String> stringSet
String prop1
String prop2
static hasMany = [stringSet: String]
static constraints = {
prop1(maxSize: 20)
prop2(maxSize: 20)
}
static mapping = {
stringSet(joinTable: [column: 'delete_test_string_set', length: 15])
}
def beforeDelete() {
withNewSession {
Set<String> strings = this."stringSet"
println(strings)
}
}
}
And I've made a test controller like this.
class DeleteTestController {
def create() {
DeleteTest test = null
DeleteTest.withTransaction {
test = new DeleteTest(
prop1: 'Test',
prop2: 'another test',
stringSet: ['str1', 'str2', 'str3']
).save()
}
render (test as JSON)
}
def delete() {
DeleteTest test = DeleteTest.findByProp1('Test')
DeleteTest.withTransaction {
test.delete()
}
render(test as JSON)
}
}
How can I get my stringSet in the beforeDelete event?
One easy way is to make sure to load stringSet before calling the delete. However, there are clearly some odd behaviors going on here and I'll describe what I have found so far.
Simple Answer
def delete() {
DeleteTest test = DeleteTest.findByProp1('Test')
test.stringSet?.size() // <-- force load here
DeleteTest.withTransaction {
test.delete()
}
render(test as JSON)
}
Other Considerations
I tried making stringSet eager loaded. This did not work as expected and in the beforeDelete code it would often be a single value or blank.
I also tried making StringSet a Set where I defined a single GORM object MyString containing the value. This did work (though I had to make it eagerly fetched), but I did not consider this to be a valid solution for your case since I assume you have data already and can't just replace it.
Based on some debug digging, I'm guessing (but it really is just a guess) that the collection is deleted before the beforeDelete event fires, and so it can't be lazily loaded at that point even in a new transaction. I would expect that someone else could weigh in on whether that's right or not, but grails 2 expertise is getting harder to find these days.

Spring Data Neo4j 4 and dynamic properties

At my Neo4j/SDN 4 project I have a following entity:
#NodeEntity
public class Value extends BaseEntity {
#Index(unique = false)
private Object value;
private String description;
...
}
During the application run-time I want to be able to add a new dynamic properties to Value node, like for example value_en_US, value_fr_FR.
Right now I don't know what exact properties will be added to a particular Value node during application run-time so I can't define these properties at the code as a separate fields in Value.
Is there at SDN 4 any mechanisms to define these properties during the application run-time? I need something similar to DynamicProperties from SDN 3.
There is no such functionality in SDN 4, but it will be added in SDN 5 through a #Properties annotation on Map.
It will be available for testing in snapshot version very soon.
Check out this commit for more details
You might also want to look at this response to a similar question.
https://stackoverflow.com/a/42632709/5249743
Just beware that in that answer the function:
public void addAllFields(Class<?> type) {
for (Field field : type.getDeclaredFields()) {
blacklist.add(field.getName());
}
if (type.getSuperclass() != null) {
addAllFields(type.getSuperclass());
}
}
is not bullet proof. For one thing it doesn't look at #Property annotations. So if you want to go down that route keep your eyes open.
An 'improvement' is
public void addAllFields(Class<?> type) {
for (Field field : type.getDeclaredFields()) {
blacklist.add(findName(field));
}
if (type.getSuperclass() != null) {
addAllFields(type.getSuperclass());
}
}
private String findName(Field field) {
Property property = field.getAnnotation(Property.class);
if(property == null || "".equals(property.name())) {
return field.getName();
} else {
return property.name();
}
}
But this obviously doesn't look for the annotation on methods...

Inherited grails domain classes missing dynamic properties

I'm having a problem where the related table id fields return 'null' from my domain objects when using inheritance. Here is an example:
In /src/groovy/
BaseClass1.groovy
class BaseClass1 {
Long id
static mapping = {
tablePerConcreteClass true
}
}
BaseClass2.groovy
class BaseClass2 extends BaseClass1 {
String someOtherProperty
static constraints = {
someOtherProperty(maxSize:200)
}
static mapping = BaseClass1.mapping
}
In /grails-app/domain
ParentClass.groovy
class ParentClass extends BaseClass2 {
ChildClass myChild
static mapping = BaseClass2.mapping << {
version false
}
}
ChildClass.groovy
class ChildClass extends BaseClass1 {
String property
static mapping = BaseClass1.mapping
}
The problem appears here:
SomeotherCode.groovy
print parentClassInstance.myChild.id // returns the value
print parentClassInstance.myChildId // returns null
Any ideas what might be going on to get those dynamic properties to break like this?
After debugging into the get(AssociationName)Id source, I found the following:
The handler for this is:
GrailsDomainConfigurationUtil.getAssociationIdentifier(Object target, String propertyName,
GrailsDomainClass referencedDomainClass) {
String getterName = GrailsClassUtils.getGetterName(propertyName);
try {
Method m = target.getClass().getMethod(getterName, EMPTY_CLASS_ARRAY);
Object value = m.invoke(target);
if (value != null && referencedDomainClass != null) {
String identifierGetter = GrailsClassUtils.getGetterName(referencedDomainClass.getIdentifier().getName());
m = value.getClass().getDeclaredMethod(identifierGetter, EMPTY_CLASS_ARRAY);
return (Serializable)m.invoke(value);
}
}
catch (NoSuchMethodException e) {
// ignore
}
catch (IllegalAccessException e) {
// ignore
}
catch (InvocationTargetException e) {
// ignore
}
return null;
}
It threw an exception on the related class (value.getClass().getDeclaredMethod), saying NoSuchMethod for the method getId(). I was unable to remove the id declaration from the base class without Grails complaining that an identifier column was required. I tried marking id as public and it also complained that it wasn't there. So, I tried this
BaseClass {
Long id
public Long getId() { return this.#id }
}
and things worked on some classes, but not on others.
When I removed the ID declaration, I go an error: "Identity property not found, but required in domain class". On a whim, I tried adding #Entity to the concrete classes and viola! everything started working.
class BaseClass {
//Don't declare id!
}
#Entity
class ParentClass {}
#Entity
class ChildClass {}
I still think it is a grails bug that it needs to be added, but at least it is easy enough to work around.
I'm not sure why you are seeing this behavior, but I'm also not sure why you are doing some of the things you are doing here. Why have a domain class extend a POGO? Domains, Controllers, and Services are heavily managed by the Grails machinery, which probably was not designed for this sort of use. Specifically, I believe Grails builds the dynamic property getters for the GrailsDomainProperty(s) of GrailsDomainClass(es), not POGO's. In this case, you have an explicitly declared id field in BaseClass1 that is not a GrailsDomainProperty. I suspect that this POGO id property is not picked up by the Grails machinery that creates the dynamic property getters for Domains.
You might try putting BaseClass1/2 in /grails-app/domain, perhaps making them abstract if you don't want them instantiated, then extending them as you are and seeing if you observe the behavior you want.

combining criteria from params array

This example here is from the grails docs:
def emeaCriteria = {
eq "region", "EMEA"
}
def results = Airport.withCriteria {
emeaCriteria.delegate = delegate
emeaCriteria()
flights {
like "number", "BA%"
}
}
My webpage is passing back a checkbox group of ethnicities, returning the row ids. So what the server gets is:
ethnicity:[1, 4]
or if the user only picks one ethnicity:
ethnicity:4
def criteria = { params ->
//handle case where only one ethnicity is returned as just a string, not a list of strings
def list = params.ethnicty instanceof String ? [params.ethnicty] : params.ethnicity
if (list) {
inList('ethnicity', list)
}
}
I'm getting an error: java.lang.String cannot be cast to java.lang.Enum.
If I didn't have a list I think I could figure it out. The params are sending back string values, and they need to be converted to the enum class. But within the closure, how do you convert each entry into a list to the enum?
I figured it out through a combination of multiple website posts and with help from dmahapatro above.
def genderCriteria = {
if (params.gender) {
inList('gender', params.list('gender').collect { Gender.valueOf(it)} )
}
}
If a webpage passes back one or more enums (a single string or a list of strings) and you want criteria to check values from the list passed back, you have to provide a list of enum types (not strings or ints).
Here is my enum class for reference:
public enum Gender {
M('Male'),
F('Female'),
U('Unknown')
final String value
Gender(String value) {
this.value = value
}
public String toString() {
value
}
public String getKey() {
name()
}
public String getValue() {
value
}
}
And my criteria builder:
def c = MyDomain.createCriteria()
results = c.list {
genderCriteria.delegate = delegate
genderCriteria(params)
}
Even if no values are passed back for the gender field, it still works (because of the if statement in genderCriteria.
It may not be the best or cleanest solution, but it does work.

How to make a query with usage of like operator over a string collection in GORM

Assume a domain class called User. User class looks like this:
class User {
List<String> values
}
The collection values contains strings like "http://example.com/x", "http://google.com/y", "http://google.com/z" and so on...
Let's say we want to build a query which gets all the users that have specific string in the collection values (e.g. "google.com"). Something like this:
def criteria = User.createCriteria()
def results = criteria.listDistinct () {
and {
user.values.each { like('someField', '%${it}%') }
}
}
Any ideas?
I have found the answer by experimentation. The solution is:
def criteria = User.createCriteria()
def results = criteria.listDistinct () {
and {
user.values.each { like('someField', '%'+it+'%') }
}
}
I am not sure what you are doing with your suggested answer.
I have never seen that usage of each in the criteria query before.
This question has been asked many times before but never given an answer.
The problem is that you are queriyng a String association, which is not a domain class. If you would make your own String domain class for example ondrej.String { String strValue } then you would be able to do :
User.withCriteria {
values { ilike("strValue", "...") }
}
The problem is not having access to the value of the String object. The value of the String class is called value, but it is a char array, so I do not believe the following will work:
User.withCriteria {
values { ilike("value", "...") }
}
You could try using :
User.withCriteria {
values { ilike("toString", "...") }
}
or something else instead of toString ... I do not have the possibility to test this right now.
Hope that helps
After a lot of trying and researching, I found this will work with Grails 2.4.0, I don't know about older versions.
Cat.withCriteria {
createAlias('nicknames', 'n')
ilike 'n.elements', '%kitty%'
}
The trick is to use 'n.elements'

Resources