Details
-
Type:
New Feature
-
Status:
Closed
-
Priority:
Major
-
Resolution: Fixed
-
Affects Version/s: 1.3.3
-
Fix Version/s: 2.0 final
-
Component/s: None
-
Labels:None
-
Environment:Grails 1.3.3
sun jdk 1.6.0_21 b07
windows 7 64 bit
Description
I think it will be very useful to have a possibility for reusing specified set of constraints from domain class in command class. Besides being in line with DRY principle, it will prevent errors when one forget to update changed constraint in both classes.
To better explain what I mean, here is some sample code:
import org.codehaus.groovy.grails.validation.ConstrainedProperty; import org.codehaus.groovy.grails.validation.Constraint; class MyGrailsUtils { static void copyConstraintsFromDomainToCommand( def p_domainClass, def p_commandClass, String p_constrainedPropertyName, String p_constraintName) { Map domainClassConstraints = p_domainClass.getConstraints() ConstrainedProperty domainClassConstrainedProperty = domainClassConstraints[p_constrainedPropertyName] Constraint domainClassPropertyConstraint = domainClassConstrainedProperty.getAppliedConstraint(p_constraintName) String domainClassPropertyConstraintName = domainClassPropertyConstraint.getName() Object domainClassPropertyConstraintParameter = domainClassPropertyConstraint.constraintParameter def commandClassConstraints = p_commandClass.getConstraints() Map commandClassConstrainedProperties = commandClassConstraints.getConstrainedProperties() ConstrainedProperty commandClassConstrainedProperty = commandClassConstrainedProperties[p_constrainedPropertyName] commandClassConstrainedProperty.applyConstraint(p_constraintName, domainClassPropertyConstraintParameter) } } class User { String userId ... static constraints = { userId(blank:false, size: 3..20, unique: true) ... } ... } class UserRegistrationCommand { String userId ... static constraints = { userId(blank:false) ... // Copies size constraint of userId property from User domain class. MyGrailsUtils.copyConstraintsFromDomainToCommand(User, UserRegistrationCommand, "userId", "size") } }
Code is not tested, and I'm sure it could be written much better since I'm groovy/grails beginner, and code uses some non public method and properties. However, I hope it can illustrate the idea. Of course, it would be the best to add this feature in constraints DSL somehow.
-
- 0001-GRAILS-6584-Fixed-Allow-reuse-of-constrains-in-comma.patch
- 03/Nov/10 8:40 PM
- 31 kB
- Damir Murat
-
Hide
- importconstraintsdemo-bug-report-04112010.zip
- 03/Nov/10 8:40 PM
- 31 kB
- Damir Murat
-
- grails-app/conf/BootStrap.groovy 0.1 kB
- grails-app/conf/BuildConfig.groovy 1 kB
- grails-app/conf/Config.groovy 4 kB
- grails-app/conf/DataSource.groovy 0.8 kB
- grails-app/conf/UrlMappings.groovy 0.2 kB
- grails-app/conf/spring/resources.groovy 0.0 kB
- grails-app/.../PersonController.groovy 3 kB
- grails-app/domain/.../Person.groovy 0.5 kB
- grails-app/i18n/messages.properties 3 kB
- grails-app/i18n/messages_da.properties 3 kB
- grails-app/i18n/messages_de.properties 4 kB
- grails-app/i18n/messages_es.properties 3 kB
- grails-app/i18n/messages_fr.properties 2 kB
- grails-app/i18n/messages_it.properties 2 kB
- grails-app/i18n/messages_ja.properties 2 kB
- grails-app/i18n/messages_nl.properties 3 kB
- grails-app/.../messages_pt_BR.properties 3 kB
- grails-app/.../messages_pt_PT.properties 3 kB
- grails-app/i18n/messages_ru.properties 4 kB
- grails-app/i18n/messages_th.properties 5 kB
- grails-app/.../messages_zh_CN.properties 2 kB
- grails-app/views/error.gsp 2 kB
- grails-app/views/index.gsp 4 kB
- grails-app/views/layouts/main.gsp 0.8 kB
- grails-app/views/person/create.gsp 4 kB
- grails-app/views/person/edit.gsp 5 kB
- test/unit/.../PersonControllerTests.groovy 0.3 kB
- test/unit/.../PersonTests.groovy 0.3 kB
- application.properties 0.2 kB
- modifications/.../AbstractConstraint.java 10 kB
Activity
- All
- Comments
- Work Log
- History
- Activity
- Git Commits
This feature is really missing in Grails. Although above sample code works,
it would be great to have this explicitly supported in DSL.
I'm seeing a lot of repetition and misconfiguration for not having a way to inherit constraints from domain to command classes it would be really nice to have this.
Not very DRY indeed, the importFrom-like method mentioned in the forum would be very helpfull
Here is a patch which proposes a solution for this issue (some tests are included). I've managed to implement reusage of domain class' constraints in commands. General syntax is
importFrom domainClass, include:["regExp1", "regExp2", ...], exclude:["regExp3", "regExp4", ...]
domainClass parameter is required, and it is of Class type. If only domain class is specified, constraints are imported from all domain property names which are also present in command class. This parameter must be specified first.
Lists include and exclude are both optional, and can be specified in any combination (none, one of them or both) and order. Their String entries are interpreted as regular expressions for matching property names against domain class.
Here is one example. Let say we have following Person and MySpecialCommand classes:
class Person {
String firstName
String middleName
String lastName
String telephone
String email
static constraints = {
firstName(nullable:false, blank:false, maxSize:50)
middleName(nullable:true, blank:false, maxSize:50)
lastName(nullable:false, blank:false, maxSize:50)
telephone(nullable:true, blank:false, maxSize:20, matches:'^[1-9][0-9]{4,19}$')
email(nullable:true, blank:false, email:true)
}
}
class MySpecialCommand {
Long personId
Long personVersion
String firstName
String middleName
String lastName
String telephone
String email
String emailConfirmation
static constraints = {
importFrom Person, include:[".*"], exclude:["m.*Name", "telephone"]
email(nullable:false, blank:false, email:true)
emailConfirmation(nullable:false, blank:false, email:true)
emailConfirmation(validator: {emailConfirmationValue, personCommandInstance ->
return emailConfirmationValue.equals(personCommandInstance.email)
})
}
}
In this example, MySpecialCommand imports all constraints from Person except middleName and telephone. After that it replaces constraint for email, and adds one of its own, emailConfirmation. If importFrom is specified last, imported constraints will replace already defined constraints in command class.
In attachment, I've also included demo app. It will work with patch applied. For convenience, in the archive is modifications folder which contains modified ConstrainedPropertyBuilder.java file. One can place it in src/java and run application with run-war command. I think run-app will not work.
It turns out that in the previous patch was a serious bug. I've removed these attachments.
Here is a fixed version with new sample app. Description from the previous comment is still relevant.
Hi Damir
This is a significant new feature and along with all the other patches you've submitted to accept these contributions I need you to sign an external committers agreement at: https://support.springsource.com/spring_committer_signup
Just login and select the project Grails. The reason for this to avoid a Linux/SCO-like legal situation for SpringSource.
Also since you've submitted so many patches have you considered forking the Github repo and submitting pull requests? It is much easier for us to accept contributions in that way.
Thanks again for your patches
Graeme
Pull request added: https://github.com/grails/grails-core/pull/22
Thanks for the contribution: https://github.com/grails/grails-core/commit/8313225b486b99ff64d919c7ee3d6fc4169962c1
This is great! I have been bitten by command objects that has different constraints than the domain class.
Awesome this is great.
The fix version is a bit misleading since this does not seem to be implemented on the 1.3.x branch.
To clear some confusion this was not implemented in any 1.3.x branch (see discussion below). It would be helpful if someone could remove 1.3.6 from the fix version.
http://grails.1312388.n4.nabble.com/Some-fixes-are-missing-from-1-3-x-branch-td3035786.html
The commit (8313225b486b99ff64d919c7ee3d6fc4169962c1) is included in 2.0 Final, so this issue should be closed. Thanks, Bobby
What happens with the nullable constraint, since that has a different default for domain classes and command objects? If it's not specified in a domain class and a command object imports the constraint, does the command object constraint have nullable: false or nullable: true?
It was long time ago
, but as I can remember, mechanism is based on applied constraints, meaning, whichever constraint is applied on domain object, it will be applied on a command, including nullable: false as default for domain objects. I created simple app to test this, by using both DefaultConstraintEvaluator on command class and validate() method on command instance, and results confirm this.
If this is not desired, one can always modify specific constraint value in command class. For example, in domain class
class Person {
String firstName
static constraints = {
firstName(blank: false, maxSize: 50)
}
}
for firstName property, we have applied constraints of nullable: false, blank: false and maxSize: 50. If command class is specified as
class PersonCommand {
String firstName
static constraints = {
importFrom Person
firstName(nullable: true)
}
}
this will result with constraints nullable: true, blank: false and maxSize: 50 for firstName property in command class, meaning only specified constraint will be modified, others, which are copied from domain object, are left untouched.
I think that nullable constraint behavior is same for domain and command classes, with difference how grails applies it to properties of each kind of class. I mean, if no constraints are specified at all for domain class property, nullable: false will be automatically added on that property. For command class property, if no constraints are specified, nullable will not be added, but if any other constraint is specified, nullable: false will be added to command class property. I might be mistaken with this, but I believe things works that way...
It seems that this feature is desired for quite some time (long time for rapid Grails timeline
). I've found this thread on grails-dev mailing list: http://tinyurl.com/2b9xdy6
Permalinked post by Marc Palmer describes how it can look like. Additionally, it will be nice to have a possibility to filter imported constraints, for example, something like
class UserSignupCommand { ... static constraints = { ... importFrom UserProfile, [firstName:*, lastName:[size, validator]] } }firstName:* or firstName[] will mean that all constraints should be imported, while non empty list will import only specified constrains.