I have the following problem:
I have a domain class called customer, with a field discount embedded.
class Customer {
...
String username
Discount discount
static constraints = { ... }
}
class Discount {
Integer item1
Integer item2
static constraints = { item1 min:1, max:100, nullable:true }
}
I have a controller, where a customer's data can be modified. The code goes something like this:
def edit() {
Customer c = Customer.findByUsername(params.userName)
if(request.method != 'GET'){
bindData(c, params)
if(c.validate()) {
//save the result
}
}
println c.dump()//1
model:[customer:c]
}
Then in the edit.gsp I put the following code:
${customer.dump()}//2
${customer.discount.dump()}
Now my problem is, if I have a validation error, for example the user enters 123 for item1, I get the appropriate errors object which says that Customer bean has 1 field error on field discount.item1 when I call println c.dump()//1
In the edit.gsp on the other hand, the customer bean doesn't have any field errors but customer.discount has the mentioned error. This is a big inconvenience, because I want to render errors next to the fields like so:
<g:renderErrors bean="${customer}" field="discount.item1"/>
But the customer bean doesn't have any errors, just the discount bean (therefore I don't get any errors rendered).
Has this problem occured to any of you ?
It seems that #Validatable classes use functionality of spring framework's AbstractBindingResult, which doesn't support this usage (as far as I can tell).
However I was able to create a workaround, which could be used as a taglib to achieve the same effect:
Given two validateable classes
#Validateable
class TestA {
TestB b
static constraints = {
b validator: {
it.validate()
}
}
}
#Validateable
class TestB {
int c
static constraints = {
c min: 100
}
}
One can simply resolve the bean and the attribute and delegate the rendering to the existing taglib.
import test.*
import org.codehaus.groovy.grails.plugins.web.taglib.*
ValidationTagLib validationTagLib = ctx.getBean(ValidationTagLib)
renderErrorsWithNestedFields = { attrs, body ->
def modifiedAttrs = attrs
String[] path = attrs?.field?.split('\\.')
if(path?.size() > 1) {
Object resolvedBean
resolvedBean = attrs.bean
path[0..-2].each {
//Some error handling would not hurt, but this is just a proof of concept
resolvedBean = resolvedBean[it]
}
modifiedAttrs = new HashMap(attrs)
modifiedAttrs.bean = resolvedBean
modifiedAttrs.field = path.last()
}
validationTagLib.renderErrors (modifiedAttrs, null)
}
TestA a = new TestA(b:new TestB(c:3))
a.validate()
renderErrorsWithNestedFields([bean:a, field:'b.c'], null)
And this should render an error for violating the min constraint on c.
Related
I have a Rulebook that contains Rules:
class Rulebook(val rules:MutableList<Rule>)
I have an ItemViewModel for it, as it's used in a multiply-nested selection UI.
class RulebookModel : ItemViewModel<Rulebook> {
val rulesProperty = bind // ... here's my problem
}
What is the correct binding to be able to initialize a tableview with the property?
A naive bind yields the wrong type:
val rulesProperty = bind(Rulebook::rules)
has type Property<MutableList<Rule>>, which tableview() doesn't take.
From another answer here I got Link
val rulesProperty = bind(Rulebook::rules) as ListProperty<Rule>
This yields the correct type, so we get through compilation, but at runtime I get this:
java.lang.ClassCastException: java.util.ArrayList cannot be cast to javafx.collections.ObservableList
Note: The RulebookModel does start life without an item in it yet. I've seen ArrayLists come from empty list factory calls before. Is that possibly my actual problem?
What is the correct way to perform this binding?
Your model needs to have a SimpleListProperty to bind into an itemViewModel
Here is some sample code for how to write the classes and a table view:
data class rule(val name: String, val def: String)
class RuleBookModel{
val rulesProperty = SimpleListProperty<rule>()
var rules by rulesProperty
}
class RuleBookViewModel: ItemViewModel<RuleBookModel>() {
val rules = bind(ruleBook::rulesProperty)
}
class TestView : View("Test View") {
val myRuleBook: RuleBookViewModel by inject()
init {
// adding a rule so the table doesn't look lonely
myRuleBook.rules.value.add(rule("test", "fuga"))
}
val name = textfield()
val definition = textfield()
override val root = vbox{
hbox {
label("Name")
add(name)
}
hbox {
label("Definition")
add(definition)
}
button("Add a rule").action{
myRuleBook.rules.value.add(rule(name.text, definition.text))
}
tableview(myRuleBook.rules) {
column("name", rule::name)
column("def", rule::def)
}
}
}
I try to validate a nested domain class instance on a command object.
Having the following command object
package demo
import grails.databinding.BindingFormat
class SaveEventCommand {
#BindingFormat('yyyy-MM-dd')
Date date
Refreshment refreshment
static constraints = {
date validator: { date -> date > new Date() + 3}
refreshment nullable: true
}
}
And having the following domain class with its own constraints
package demo
class Refreshment {
String food
String drink
Integer quantity
static constraints = {
food inList: ['food1', 'food2', 'food3']
drink nullable: true, inList: ['drink1', 'drink2', 'drink3']
quantity: min: 1
}
}
I need when refreshment is not nullable the command object validates the date property and check the corresponding restrictions in refreshment instance
For now try with this code in the controller:
def save(SaveEventCommand command) {
if (command.hasErrors() || !command.refreshment.validate()) {
respond ([errors: command.errors], view: 'create')
return
}
// Store logic goes here
}
Here through !command.refreshment.validate() I try to validate the refresh instance but I get the result that there are no errors, even when passing data that is not correct.
Thank you any guide and thank you for your time
I typically just include some code that will use a custom validator to kick off validation for any property that is composed of another command object. For example:
thePropertyInQuestion(nullable: true, validator: {val, obj, err ->
if (val == null) return
if (!val.validate()) {
val.errors.allErrors.each { e ->
err.rejectValue(
"thePropertyInQuestion.${e.arguments[0]}",
"${e.objectName}.${e.arguments[0]}.${e.code}",
e.arguments,
"${e.objectName}.${e.arguments[0]}.${e.code}"
)
}
}
})
This way it's pretty clear that I want validation to occur. Plus it moves all the errors up into the root errors collection which makes things super easy for me.
Two things I could think of:
Implement grails.validation.Validateable on your command object
What happens when you provide an invalid date? Can you see errors while validating?
We have a class like this in a Grails 2.4.3 application (migrated from 2.3.8):
#Validateable
class Foo {
Integer noDefault;
Integer withDefault = 1;
static constraints = {
noDefault(nullable:false)
withDefault(nullable:false)
}
}
This class is being instantiated in a complex configuration mechanism using a Map like this:
[
noDefault: 0,
withDefault: 2
]
(In fact the Map is part of a huge one, but the class constructor sees this small one.) Formerly the class worked if we omitted the withDefault entry from the config map, using the default value which is not null. In Grails 2.4.3, however, it tells me that this field cannot be null. I can fix it by letting it be null in the constraint, but it lets setting the explicite value null (and overwrite the default value), which causes problem during operation.
Do you know some workaround, which preserves the semantics and correct operation?
Thanx in advance, best regards: Balázs
What you are describing is not consistent with what I would expect and not consistent with the behavior I am seeing. The project at https://github.com/jeffbrown/validatedefaults contains the following code.
At https://github.com/jeffbrown/validatedefaults/blob/master/src/groovy/demo/Foo.groovy
// src/groovy/demo/Foo.groovy
package demo
import grails.validation.Validateable
#Validateable
class Foo {
Integer noDefault;
Integer withDefault = 1;
static constraints = {
noDefault(nullable:false)
withDefault(nullable:false)
}
}
The test at https://github.com/jeffbrown/validatedefaults/blob/master/test/unit/demo/FooSpec.groovy passes:
// test/unit/demo/FooSpec.groovy
package demo
import spock.lang.Specification
import grails.test.mixin.TestMixin
import grails.test.mixin.support.GrailsUnitTestMixin
#TestMixin(GrailsUnitTestMixin)
class FooSpec extends Specification {
void 'test validating default values'() {
given:
def map = [noDefault: 0]
def foo = new Foo(map)
expect:
foo.validate()
}
}
When I run the app I get the same behavior.
// grails-app/conf/BootStrap.groovy
import demo.Foo
class BootStrap {
def init = { servletContext ->
def map = [noDefault: 0]
def foo = new Foo(map)
// this prints true...
println "Foo is valid? : ${foo.validate()}"
}
def destroy = {
}
}
I hope that helps.
I'm new to Spock and I have this class that I want to unit test. In the class there's a method that validates a product. To pass the validation the product must have fullPrice and this must contain all the other prices, otherwise an exception should be thrown.
class PriceValidator {
private final Logger logger = Logger.getLogger(MyService.class)
void validate (Product product) throws SubsystemException {
if (!product.fullPrice || !product.fullPrice.priceInclVAT || !product.fullPrice.priceExclVAT || !product.fullPrice.vat) {
String message = "No price found!"
logger.error(message)
throw new SubsystemException(
Subsystem.MySystem,
FailureCause.NO_PRICE_FOUND,
message
)
}
}
}
I have tried testing this in several ways without any luck. I'm guessing I need mocks, but that is also new to me. This is an example of a test that I have tried, resulting in "Test framework quit unexpectedly" (and all the prices are Strings):
class PriceValidatorTest extends Specification {
#Unroll
def "No price should throw an exception"() {
given:
PriceValidator priceValidator = new PriceValidator()
Product product = Mock()
when:
product.fullPrice != null
product.fullPrice.priceInclVAT = "100"
product.fullPrice.priceExclVAT = "70"
product.fullPrice.vat = null
priceValidator.validate(product)
then:
thrown(SubsystemException)
}
}
Anyone having a suggestion how to test the PriceValidator?
You need to test several cases, one of them would be:
def "No price should throw an exception"() {
given:
PriceValidator priceValidator = new PriceValidator()
Product product = Mock() {
getFullPrice() >> null
}
when:
priceValidator.validate(product)
then:
thrown(SubsystemException)
}
What You need to to is to mock the behavior of Product class (the line with >> rightShift operator). No it seems ready to be tested. Other scenarios, when price is filled should be tested in separate method. Any further questions?
I'm having a hard time figuring this validation problem. I have one parent domain class defined as follows:
class Person {
String fullName
List telephones = []
static hasMany = [telephones : Telephone]
static constraints = {
fullName(size:3..50, blank:false, nullable:false)
}
}
Then a sublcass:
class SalesAdvisor extends Person{
Float comission //In percentage
Portfolio customerPortfolio
Inventory inventory
static constraints = {
comission(range:0..100, scale:2, nullable:false)
customerPortfolio(nullable:false)
inventory(nullable:false)
}
}
In the SalesAdvisorController I save SalesAdvisor instances:
def save = {
def portfolio = new Portfolio()
def inventory = new Inventory(name:'${params.fullName}Inventory', description:"${params.fullName}'s Inventory")
params.customerPortfolio = portfolio
params.inventory = inventory
def salesAdvisor = new SalesAdvisor(params)
if(!salesAdvisor.hasErrors() && salesAdvisor.save()){
log.info("New instance of SalesAdvisor saved.")
redirect(action:show, id:salesAdvisor.id)
}else{
log.error("There was an error saving the sales advisor.")
salesAdvisor.errors.allErrors.each{
println it.code
}
render(view:'create', model:[salesAdvisor:SalesAdvisor])
}
}
In order to display any errors, in the 'create' view I have:
<g:hasErrors bean="${salesAdvisor}">
<div class="errors">
<g:renderErrors bean="${salesAdvisor}" as="list" />
</div>
</g:hasErrors>
Validation seems to be working fine. However if I submit a string instead of a float for the comission field, in logs I can see "typeMismatch" but the view renders nothing! The message.properties file has a default entry for typeMismatch. Same thing for the fullName field, in logs I can see "nullable" and "blank" errors, but the view renders nothing.
I'm guessing it's more the view's fault than the controller or the domain, since unit tests behave like they should.
I'd say the problem is a simple typo in your model-passing code:
render(view:'create', model:[salesAdvisor:SalesAdvisor])
(note the uppercase SalesAdvisor value). Try
render(view:'create', model:[salesAdvisor:salesAdvisor])
As a side note, there is a bug in your Inventory constructing code:
name:'${params.fullName}Inventory'
You should use double-quotes here (GString).