public class Service {
String reviewChanges
String comment
static constraints = {
reviewChanges (inList:['NO','YES'])
comment validator: { val, obj ->
if(reviewChanges=='YES') {
(nullable:false, blank:false, minSize:1, maxSize:500)
} else {
(nullable:true, blank:true, minSize:1, maxSize:500)
}
}
}
}
Above comment validator does not work for me.
I want if reviewChanges field selected YES then Comment field must be Mandatory field else Comment filed Non-Mandatory
The best way working with custom validators would be like this..
static constraints = {
reviewChanges(inList:['NO','YES'])
comment validator: { val, obj,errors ->
if (obj.reviewChanges == 'YES' && StringUtils.isEmpty(val)) {
errors.rejectValue('comment',"some.custom.validation.key")
}
}
}
errors.rejectValue will allow you to give proper field errors using propertyName and also you can use it for parameterized error...
errors.rejectValue('propertyName','errorCode',errorArgs as Object[],'defaultMessage')
and define errorCode is message.properties to access errorArgs like
errorCode = This is {0} first parameter being passed as errorArgs.
Thanks
You could do something like this I think (I haven't tested this, but you get the idea):
static constraints = {
reviewChanges(inList:['NO','YES'])
comment validator: { val, obj ->
if (obj.reviewChanges == 'YES' && StringUtils.isEmpty(val)) {
return "some.custom.validation.key"
}
}
}
Unless there is a requirement to have reviewChanges as a String, I would make it a Boolean field and using Groovy truth, you should be able to do something like:
class Service {
Boolean reviewChanges
String comment
static constraints = {
comment nullable:true, minSize:1, maxSize:500, validator: { val, obj ->
if (obj.reviewChanges && (!val)){
return "comments.required"
}
}
}
}
using Grails 2.3.3
Related
How can I input a null value in Specflow through a table?
Let's look at an overly simplistic example:
When a tire is attached to a car
| CarId | TireModel | FabricationDate | Batch |
| 1 | Nokian Hakka R | 2015-09-1 | |
The empty string in the Batch column is interpreted as text by specflow and as such, empty string. Is there a special syntax to mark that column as null?
You can create your own IValueRetriever and replace default one with yours
public class StringValueRetriver : IValueRetriever
{
public bool CanRetrieve(KeyValuePair<string, string> keyValuePair, Type targetType, Type propertyType)
{
return propertyType == typeof(string);
}
public object Retrieve(KeyValuePair<string, string> keyValuePair, Type targetType, Type propertyType)
{
return string.IsNullOrEmpty(keyValuePair.Value) ? null : keyValuePair.Value;
}
}
Some where in your scenario steps
[BeforeScenario]
public void BeforeScenario()
{
Service.Instance.ValueRetrievers.Unregister<TechTalk.SpecFlow.Assist.ValueRetrievers.StringValueRetriever>();
Service.Instance.ValueRetrievers.Register(new StringValueRetriver());
}
older syntax:
[BeforeScenario]
public void BeforeScenario()
{
var defaultStringValueRetriever = Service.Instance.ValueRetrievers.FirstOrDefault(vr => vr is TechTalk.SpecFlow.Assist.ValueRetrievers.StringValueRetriever);
if (defaultStringValueRetriever != null)
{
Service.Instance.UnregisterValueRetriever(defaultStringValueRetriever);
Service.Instance.RegisterValueRetriever(new StringValueRetriver());
}
}
From SpecFlow 3 on-wards, in your Steps class, you can just put the following code. And in the feature file just put null value like this. Now when you use the CreateSet function then it will be deserialized correctly.
Id | Value
1 | <null>
[Binding]
public static class YourStepClass
{
[BeforeTestRun]
public static void BeforeTestRun()
{
Service.Instance.ValueRetrievers.Register(new NullValueRetriever("<null>"));
}
}
I don't believe there is a special syntax for null and I think you'll have to just handle the conversion yourself. The value retrievers have been revised in the v2 branch and you might be able to handle this by deregistering the standard string value retriever and registering your own implementation which looks for some special syntax and returns null.
In the current 1.9.* version though I think you'll just have to check for empty string and return null yourself.
I've just chosen to do this on a case by case manner using a simple extension method.
In the handler I convert the passed in example value parameter and call NullIfEmpty()
Example usage
AndICheckTheBatchNumber(string batch) {
batch = batch.NullIfEmpty();
//use batch as null how you intended
}
Extension method
using System;
namespace Util.Extensions
{
public static class StringExtensions
{
public static string NullIfEmpty(this string str)
{
if (string.IsNullOrEmpty(str))
{
return null;
}
return str;
}
}
}
Combining answers, I did the following:
using TechTalk.SpecFlow;
using TechTalk.SpecFlow.Assist;
using TechTalk.SpecFlow.Assist.ValueRetrievers;
namespace Util.Extensions
{
public class NullValueComparer : IValueComparer
{
private readonly string _nullValue;
public NullValueComparer(string nullValue)
{
_nullValue = nullValue;
}
public bool CanCompare(object actualValue)
{
return actualValue is null || actualValue is string;
}
public bool Compare(string expectedValue, object actualValue)
{
if (_nullValue == expectedValue)
{
return actualValue == null;
}
return expectedValue == (string)actualValue;
}
}
}
And referenced it like this:
[Binding]
public class MyStepDefinitions
{
private MyTestDto _testDto;
private AnotherDtoFromElsewhere _actual;
[BeforeScenario]
public void BeforeTestRun()
{
Service.Instance.ValueRetrievers.Register(new NullValueRetriever("<null>"));
Service.Instance.ValueComparers.Register(new NullValueComparer("<null>"));
}
[When(#"Some test with table:")]
public void WhenTestWithTable(Table table)
{
_testDto = table.CreateInstance<MyTestDto>();
var actual = new AnotherDtoFromElsewhere();
table.CompareToInstance(actual);
}
[Then(#"X should match:")]
public void ThenShouldMatch(Table table)
{
table.CompareToInstance(_actual);
}
}
In Config.groovy one can specify default constraints that will apply to all properties, e.g.
grails.gorm.default.constraints = {
'*'(nullable: true)
}
Is there a way to specify default constraints for properties of a certain type? For example, if I want to add a (blank: false) constraint for all String properties is there a way to do that?
Not sure about adding a default constraint for a certain type, but in Programming Grails, Burt suggests a filter to handle blank fields:
class SiteFilters {
def filters = {
blankToNullAndTrim(controller: '*', action: '*') {
before = {
if (request.post) {
convertBlanksToNullsAndTrim(params)
}
true
}
}
}
private static void convertBlanksToNullsAndTrim(Map map) {
def keys = [] + map.keySet() // copy to avoid
//ConcurrentModificationException
for (name in keys) {
def value = map[name]
if (value instanceof String) {
value = value.trim()
if (value.length() == 0) {
map[name] = null // don't remove - explicity set to null
} else {
map[name] = value // update if trimmed
}
} else if (value instanceof Map) {
// recurse with empty nested param, e.g., "location":["id":""]
convertBlanksToNullsAndTrim value
}
}
}
}
With that there's no need to add blank:false.
Also, in Grails 2.3.x this behavior changed: by default all blank and empty Strings will be converted to null during data binding (configurable).
Recently I have started refactoring my Grails application, everything looked good until I got 'object references an unsaved transient instance' errors. While debugging I have found that id for one of my domain object is always null for every instance. Even for instances loaded from database.
I have found that composite id (which I use in that class) is known to cause problems, but it worked fine few days ago, since then, I have done a lot changes to this class but none of them should cause this kind of problems... at least I couldn't find change that could cause it.
class QuestionPriority implements Serializable{
static int maxPriority = 6
static int minPriority = 1
int priority = maxPriority
Date lastTestDate
Date nextTestDate = new Date()
static belongsTo = [question:Question, subscription:Subscription]
static constraints = {
nextTestDate(nullable: true)
lastTestDate(nullable: true)
}
static mapping = {
id composite: ['subscription', 'question']
version false
}
static QuestionPriority create(Subscription sub, Question question, boolean flush = false) {
assert sub != null
assert question != null
assert sub.id != null
assert question.id != null
QuestionPriority qp = new QuestionPriority(subscription: sub, question: question)
sub.addToPriorities(qp)
question.addToPriorities(qp)
assert (qp.save(flush: flush, insert: true, failOnError: true))
return qp
}
boolean equals(other) {
if (!(other instanceof QuestionPriority)) {
return false
}
other.subscription?.id == subscription?.id &&
other.getQuestionToAsk?.id == question?.id
}
int hashCode() {
def builder = new HashCodeBuilder()
if (subscription) builder.append(subscription.id)
if (question) builder.append(question.id)
builder.toHashCode()
}
void markAsCorrect() {
priorityDown()
calculateNextTestDate()
}
void markAsWrong() {
priorityUp()
calculateNextTestDate()
}
String getQuestionToAsk() {
return question.question
}
String getExpectedAnswer() {
return question.answer
}
Date calculateNextTestDate() {
DailyTestMode mode = subscription.testMode
if (!lastTestDate) {
nextTestDate = new Date(0, 0, 0)
} else {
nextTestDate = mode.calculateNextAskDate(this)
}
return nextTestDate
}
static void delete(User user, Question q) {
QuestionPriority qp = get(user.id, q)
if (qp) {
qp.delete()
}
}
static QuestionPriority get(Subscription sub, Question question) {
assert sub != null
assert question != null
assert sub.id != null
assert question.id != null
QuestionPriority qp = find 'from QuestionPriority where subscription.id=:subId and question.id=:questionId',
[subId: sub.id, questionId: question.id]
if (!qp) {
qp = create(sub, question)
}
return qp
}
private void priorityUp() {
priority = Math.min(priority + 1, maxPriority)
lastTestDate = new Date()
calculateNextTestDate()
}
private void priorityDown() {
priority = Math.max(priority - 1, minPriority)
lastTestDate = new Date()
calculateNextTestDate()
}
}
I would really appreciate any help
-----EDIT-------
Question domain oject:
class Question {
String question
String answer
boolean deleted
static transients = [ 'deleted']
static belongsTo = [studyList: StudyList]
static hasMany = [priorities: QuestionPriority]
static constraints = {
question(blank:false)
answer(blank:false)
}
static mapping = {
priorities(cascade: 'all-delete-orphan')
}
void setAnswer(String a){
answer = a.trim()
}
String toString(){
return "${question} = ${answer}"
}
}
Subscription domain object
class Subscription implements Serializable {
boolean active = true
Integer rating = null
Date subscriptionStartDate = new Date()
String dailyTestModeLiteral
static belongsTo = [user:User, studyList:StudyList]
static hasMany = [priorities:QuestionPriority]
static constraints = {
priorities(nullable: true)
rating(nullable: true)
subscriptionStartDate(nullable: true)
}
static mapping = {
priorities(cascade: 'all-delete-orphan')
}
void setDailyTestMode(DailyTestMode mode){
dailyTestModeLiteral = mode.getModeLiteral()
for(QuestionPriority priority:priorities){
priority.calculateNextTestDate()
}
}
DailyTestMode getTestMode(){
return DailyTestMode.getMode(dailyTestModeLiteral)
}
public static Subscription create(User user, StudyList list, String testModeLiteral = NormalDailyTestStrategy.literal, boolean flush = false){
Subscription subscription = get(user.id, list.id)
if(!subscription){
withTransaction {
subscription = new Subscription(user: user, studyList: list, dailyTestModeLiteral: testModeLiteral)
user.addToSubscriptions(subscription)
list.addToSubscriptions(subscription)
subscription.save(failOnError: true, flush: true)
}
subscription.studyList.questions.each {
QuestionPriority.create(subscription, it)
}
}
return subscription
}
int getScore(){
float prioritySum = 0
float priorityMax = 0
float priorityMin = 0
priorities.each {
prioritySum += it.priority
priorityMin += 1
priorityMax += QuestionPriority.maxPriority
}
return Math.round(100 * (prioritySum-priorityMax)/(priorityMin - priorityMax))
}
public static void delete(User user, StudyList list){
Subscription subscription = get(user.id, list.id)
if(subscription){
subscription.delete()
}
}
static Subscription get(long userId, long studyListId) {
find 'from Subscription where user.id=:userId and studyList.id=:studyListId',
[userId: userId, studyListId: studyListId]
}
boolean equals(other) {
if (!(other instanceof Subscription)) {
return false
}
other.user?.id == user?.id &&
other.studyList?.id == studyList?.id
}
int hashCode() {
def builder = new HashCodeBuilder()
if (user) builder.append(user.id)
if (studyList) builder.append(studyList.id)
builder.toHashCode()
}
boolean checkIfMatches(QuestionPriority questionPriority, String questionAsked, String answerGiven) {
return(questionPriority.question.question==questionAsked &&
questionPriority.question.answer == answerGiven)
}
QuestionPriority checkIfMatchesAny(String questionAsked, String answerGiven) {
Question q = Question.withCriteria {
and {
eq('question', questionAsked)
eq('answer', answerGiven)
eq('studyList', studyList)
}
}
if(q){
List<QuestionPriority> qp = QuestionPriority.withCriteria {
and {
eq('question', q)
eq('subscription', this)
}
}
if(qp.size()>0){
return qp[0]
}else{
return null
}
}
}
}
A few hours of debuging later I still know only that executing save(flush: true, failOnError: true) on QuestionPriority object returns unsaved object (no validation errors, no exceptions... no helpful information).
Any idea where to look or how to search for cause of this problem would be helpful, because I'm 100% stuck on this...
After hours of searching, changing framework to newest version and fixing new problems resulting from version change, I fixed this problem. It looks like changing composite ID into normal ID + composite unique key solved the problem. I couldn't find other solution, even thou my code worked fine with composite id for first couple of months... (any part of code related to composite id was changed when it stopped working) and I'm almost sure that I tried removing composite ID earlier and it didn't help then...
I have a problem in registering custom property editor. I register it like this:
class BooleanEditorRegistrar implements PropertyEditorRegistrar {
public void registerCustomEditors(PropertyEditorRegistry registry) {
registry.registerCustomEditor(Boolean.class,
new CustomBooleanEditor(CustomBooleanEditor.VALUE_YES, CustomBooleanEditor.VALUE_NO, false))
registry.registerCustomEditor(Boolean.class,
new CustomBooleanEditor(CustomBooleanEditor.VALUE_ON, CustomBooleanEditor.VALUE_OFF, true))
}
}
But the only first is applied. Is it possible to register more then one?
You can only set one property editor per class. If you are using Spring's CustomBooleanEditor, you can either use the default values ("true"/"on"/"yes"/"1", "false"/"off"/"no"/"0") with the one-arg constructor, or exactly one string each for true and false. If you need something more flexible, you'll have to implement your own property editor. For example:
import org.springframework.beans.propertyeditors.CustomBooleanEditor
class MyBooleanEditor extends CustomBooleanEditor {
def strings = [
(VALUE_YES): true,
(VALUE_ON): true,
(VALUE_NO): false,
(VALUE_OFF): false
]
MyBooleanEditor() {
super(false)
}
void setAsText(String text) {
def val = strings[text.toLowerCase()]
if (val != null) {
setValue(val)
} else {
throw new IllegalArgumentException("Invalid boolean value [" + text + "]")
}
}
}
I need to perform some initialization when new instances of my domain class are created.
class ActivationToken {
String foo
String bar
}
When I do this I want bar to be initialized by code inside ActivationToken:
def tok = new ActivationToken(foo:'a')
I cannot see how to 'override' the 'constructor' to make this happen. I know in this case I could just add a normal constructor but this is just a simple example.
The map constructor is coming from Groovy - not Grails in this case. I did some experimentation, and this is what I came up with:
class Foo {
String name = "bob"
int num = 0
public Foo() {
this([:])
}
public Foo(Map map) {
map?.each { k, v -> this[k] = v }
name = name.toUpperCase()
}
public String toString() {
"$name=$num"
}
}
assert 'BOB=0' == new Foo().toString()
assert 'JOE=32' == new Foo(name:"joe", num: 32).toString()
Basically, it appears that you'll have to manually override the constructors if you need to process the property after construction.
Alternately, you can override individual setters, which is cleaner and safer in general:
class Foo {
String name = "bob"
int num = 0
public void setName(n) {
name = n.toUpperCase()
}
public String toString() {
"$name=$num"
}
}
assert 'bob=0' == new Foo().toString()
assert 'JOE=32' == new Foo(name:"joe", num: 32).toString()
Note that the default value isn't processed, but that should be OK in most instances.
The solution above is also good for cases where initializing an object from parameters in a web request, for example, where you wish to ignore extraneous values, catching Missing property exceptions.
public Foo(Map map) {
try {
map?.each { k, v -> this[k] = v }
}
catch(Exception e){
}
}