Skip to content

Commit 26f98f9

Browse files
authored
GORM should not check Entity's dirtiness on formula and transient type of properties (#1697)
1 parent ade4210 commit 26f98f9

File tree

4 files changed

+146
-5
lines changed

4 files changed

+146
-5
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import grails.gorm.annotation.Entity
2+
import grails.gorm.tests.GormDatastoreSpec
3+
4+
class DirtyCheckingSpec extends GormDatastoreSpec {
5+
6+
void "When marking whole class dirty, then derived and transient properties are still not dirty"() {
7+
when:
8+
TestBook book = new TestBook()
9+
book.title = "Test"
10+
and: "mark class as not dirty - to clear previous dirty tracking"
11+
book.trackChanges()
12+
13+
then:
14+
!book.hasChanged()
15+
16+
when: "Mark whole class as dirty"
17+
book.markDirty()
18+
19+
then: "whole class is dirty"
20+
book.hasChanged()
21+
22+
and: "The formula and transient properties are not dirty"
23+
!book.hasChanged('formulaProperty')
24+
!book.hasChanged('transientProperty')
25+
26+
and: "Other properties are"
27+
book.hasChanged('id')
28+
book.hasChanged('title')
29+
30+
}
31+
32+
void "Test that dirty tracking doesn't apply on Entity's transient properties"() {
33+
when:
34+
TestBook book = new TestBook()
35+
book.title = "Test"
36+
and: "mark class as not dirty, clear previous dirty tracking"
37+
book.trackChanges()
38+
39+
then:
40+
!book.hasChanged()
41+
42+
when: "update transient property"
43+
book.transientProperty = "new transient value"
44+
45+
then: "class is not dirty"
46+
!book.hasChanged()
47+
48+
and: "transient properties are not dirty"
49+
!book.hasChanged('transientProperty')
50+
}
51+
52+
@Override
53+
List getDomainClasses() {
54+
[TestBook]
55+
}
56+
}
57+
58+
@Entity
59+
class TestBook implements Serializable {
60+
61+
Long id
62+
String title
63+
64+
String formulaProperty
65+
66+
String transientProperty
67+
68+
static mapping = {
69+
formulaProperty(formula: 'name || \' (formula)\'')
70+
}
71+
72+
static transients = ['transientProperty']
73+
}

grails-datastore-gorm/src/main/groovy/org/grails/compiler/gorm/DirtyCheckingTransformer.groovy

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,18 +69,24 @@ class DirtyCheckingTransformer implements CompilationUnitAware {
6969
this.compilationUnit = compilationUnit
7070
}
7171

72-
void performInjectionOnAnnotatedClass(SourceUnit source, ClassNode classNode) {
72+
void performInjectionOnAnnotatedClass(SourceUnit source, ClassNode classNode, Class traitToInject = DirtyCheckable) {
7373
// First add a local field that will store the change tracking state. The field is a simple list of property names that have changed
7474
// the field is only added to root clauses that extend from java.lang.Object
75-
final ClassNode changeTrackableClassNode = new ClassNode(DirtyCheckable).getPlainNodeReference()
75+
final ClassNode changeTrackableClassNode = new ClassNode(traitToInject).getPlainNodeReference()
76+
if (traitToInject != DirtyCheckable) {
77+
changeTrackableClassNode.setSuperClass(new ClassNode(DirtyCheckable).getPlainNodeReference())
78+
}
7679
final MethodNode markDirtyMethodNode = changeTrackableClassNode.getMethod(METHOD_NAME_MARK_DIRTY, new Parameter(ClassHelper.STRING_TYPE, "propertyName"), new Parameter(ClassHelper.OBJECT_TYPE, "newValue"))
7780

7881

7982
ClassNode superClass = classNode.getSuperClass()
8083
boolean shouldWeave = superClass.equals(OBJECT_CLASS_NODE)
8184

82-
ClassNode dirtyCheckableTrait = ClassHelper.make(DirtyCheckable).getPlainNodeReference()
83-
85+
ClassNode dirtyCheckableTrait = ClassHelper.make(traitToInject).getPlainNodeReference()
86+
if (traitToInject != DirtyCheckable) {
87+
dirtyCheckableTrait.setSuperClass(new ClassNode(DirtyCheckable).getPlainNodeReference())
88+
}
89+
8490
while(!shouldWeave) {
8591
if(isDomainClass(superClass) || !superClass.getAnnotations(DIRTY_CHECK_CLASS_NODE).isEmpty()) {
8692
break

grails-datastore-gorm/src/main/groovy/org/grails/compiler/gorm/GormEntityTransformation.groovy

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import org.codehaus.groovy.transform.AbstractASTTransformation
5656
import org.codehaus.groovy.transform.GroovyASTTransformation
5757
import org.grails.datastore.gorm.GormEnhancer
5858
import org.grails.datastore.gorm.GormEntity
59+
import org.grails.datastore.gorm.GormEntityDirtyCheckable
5960
import org.grails.datastore.gorm.query.GormQueryOperations
6061
import org.grails.datastore.mapping.model.config.GormProperties
6162
import org.grails.datastore.mapping.reflect.AstUtils
@@ -240,7 +241,7 @@ class GormEntityTransformation extends AbstractASTTransformation implements Comp
240241

241242
// now apply dirty checking behavior
242243
def dirtyCheckTransformer = new DirtyCheckingTransformer()
243-
dirtyCheckTransformer.performInjectionOnAnnotatedClass(sourceUnit, classNode)
244+
dirtyCheckTransformer.performInjectionOnAnnotatedClass(sourceUnit, classNode, GormEntityDirtyCheckable)
244245

245246

246247
// convert the methodMissing and propertyMissing implementations to $static_methodMissing and $static_propertyMissing for the static versions
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.grails.datastore.gorm
17+
18+
import groovy.transform.CompileStatic
19+
import groovy.util.logging.Slf4j
20+
import org.grails.datastore.mapping.config.Property
21+
import org.grails.datastore.mapping.dirty.checking.DirtyCheckable
22+
import org.grails.datastore.mapping.model.PersistentEntity
23+
import org.grails.datastore.mapping.model.PersistentProperty
24+
25+
import javax.annotation.Generated
26+
27+
/**
28+
*
29+
* Special trait meant only for GORM entities to override default behaviour of DirtyCheckable.
30+
* Applied manually during GormEntity transformation
31+
*
32+
* @since 7.3
33+
*/
34+
@CompileStatic
35+
trait GormEntityDirtyCheckable extends DirtyCheckable {
36+
37+
@Override
38+
@Generated
39+
boolean hasChanged(String propertyName) {
40+
PersistentEntity entity = currentGormInstanceApi().persistentEntity
41+
42+
PersistentProperty persistentProperty = entity.getPropertyByName(propertyName)
43+
if (!persistentProperty) {
44+
// Not persistent property, transient. We don't track changes for transients
45+
return false
46+
}
47+
48+
Property propertyMapping = persistentProperty.getMapping().getMappedForm()
49+
if (propertyMapping.derived) {
50+
// Derived property cannot be changed, ex. sql formula
51+
return false
52+
}
53+
54+
return super.hasChanged(propertyName)
55+
}
56+
57+
@Generated
58+
private GormInstanceApi currentGormInstanceApi() {
59+
(GormInstanceApi) GormEnhancer.findInstanceApi(getClass())
60+
}
61+
}

0 commit comments

Comments
 (0)