Skip to content

Explicit bindable: true constraint on id/version declared in an abstract @DirtyCheck base is not inherited by domain subclasses #15795

Description

@jdaugherty

Issue description

Summary

When an abstract base class (e.g. a @DirtyCheck class in src/main/groovy) declares an explicit bindable: true constraint on one of the default-excluded special properties (id, version, dateCreated, lastUpdated), a concrete domain subclass that does not redeclare the constraint fails to inherit it. The property is silently excluded from data binding even though the parent explicitly opted it in.

This is a regression introduced by the fix for #15681 (PR #15699).

Affected version

Root cause

PR #15699 added a filter in DefaultASTDatabindingHelper.getPropertyNamesToIncludeInWhiteList that strips the default-excluded special properties from the inherited parent whitelist for any domain class:

        if (isDomainClass && DOMAIN_CLASS_PROPERTIES_TO_EXCLUDE_BY_DEFAULT.contains(parentPropertyName)) {
            continue;
        }

This correctly prevents GORM-injected id/version from leaking out of a non-domain @DirtyCheck base into the subclass whitelist. However, the filter is unconditional: it cannot tell the difference between a special property that landed in the parent whitelist by default (the case #15681 fixed) and one the parent made bindable via an explicit bindable: true constraint (which should be inherited). As a result, the explicit constraint is discarded.

Steps to reproduce

  1. Define an abstract @DirtyCheck base in src/main/groovy that opts id into binding:

     @DirtyCheck
     abstract class AbstractBindableIdRecord {
         String description
         static constraints = {
             id bindable: true
         }
     }
    
  2. Define a concrete domain subclass that declares no constraint for id:

         class BindableIdRecord extends AbstractBindableIdRecord {
             static constraints = { description nullable: true }
         }
    
  3. Bind a request that includes id:

     def record = new BindableIdRecord()
     bindData(record, params)   // params: id=99&description=...
    

Expected behavior

id is bound (the inherited bindable: true constraint is honored): record.id == 99.

Actual behavior

id is not bound: record.id == null. The inherited explicit constraint is ignored.

Notes

Metadata

Metadata

Assignees

Type

No fields configured for Bug.

Projects

Status
No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions