Grails conditional nullable validation or custom validator with nullable option - grails

I have a form to create a place. Depending of the country, the province (state, region) field is required or not.
When is not required, I want to be null, not empty string. I have code that makes all empty form fields, null:
def newparams = [:]
place = new Place()
params.each() { k, v ->
if (v instanceof String && place.hasProperty(k)) {
if (!v.trim().length()) {
newparams[k] = null
} else {
newparams[k] = v
}
}
}
place = new Place(newparams)
place.validate()
Now, in the place domain, I have a validator on the province:
province validator: {val, obj -> if (obj.country in obj.requiresRegionCountries() && !obj.province) return [province.required]}
With this rule, I always get "province can't be null" even if it is required or not.
I think this is because the nullable validator that is set default to false.
If I am adding nullable: true, then even if province is required, the custom validator is skipped and it is possible to save with empty province (I think that is because it gets instantiated with null)
Now, I need somehow my custom validator and also ability to specify the nullable in my validator, something like this:
province validator: {val, obj ->
if (obj.country in obj.requiresRegionCountries() && !obj.province) {
nullable: false
return [province.required] }
else {
nullable: true
}
}
How can I achieve this in Grails 2.0.3?

After lots of research and feedback I found out 2 solutions that are working. One is in controller. Do not add any validation in model and add them dynamically from controller:
class PlacesController {
def create() {
def place = new Place(params.address)
if (place.country in placesThatRequiresProvinceArray) {
place.constrains.province.nullable = false
} else {
place.constrains.province.nullable = true
}
}
The other solution is the one proposed by Tri in this thread, but put the custom validator before the nullable constraint (else the custom validator will not be called for null values):
static constraints = {
province (validator: {val, obj ->
if (obj.country == 'Canada' && !val)
return ['province.required']
}, nullable: true)
}

I can't tell with the code you've pasted but if your problem is that the default validation doesn't allow province to be null, have you tried explicitly allowing province to be null? You are allowed multiple validators for each field. So back in your original code, just specify the nullable validator as well:
province nullable: true, validator: {val, obj ->
if (obj != null && obj.country in obj.requiresRegionCountries() && !obj.province)
return [province.required]
}
EDIT:
In the custom validator, might also want to guard against the obj being null in the if condition.
EDIT2: Demo project showing the above validation working on grails 2.0.4
class Place {
String country
Province province
static constraints = {
province (nullable: true, validator: {val, obj ->
if (obj.country == 'Canada' && !val) return ['province.required']
})
}
}
Controller...
class MainController {
def index() {
def place = new Place(country: 'Canada')
if (!place.validate()) {
render "need province<br/>" + place.errors
} else {
render "cool"
}
So the idea is that I have a dummy controller where I can invoke the index action which is hardcoded to create a Place domain instance similar to your example. Notice I only defined the country string so I can key my logic on that for the custom validation. I didn't define the province when creating the Place instance so it should be null. Under that scenario, the response page will print the following...
Output snippet ...
need province
grails.validation.ValidationErrors: 1 .... does not pass custom validation]
If I remove the nullable: true constraint from Place, then the error is the null value as expected...
Output snippet ...
need province
grails.validation.ValidationErrors: 1 .... cannot be null]

Related

Grails: How do a validate POST body consisting of a list of items using a command object?

I am facing some issues with writing custom validators (Commands) in grails 3.3.3. Specifically, I am trying to validate POST request whose body is composed of a list of items. This is what I have...
The Command:
class VoteCommand implements Validateable {
List<VoteItem> postList = [].withLazyDefault { new ListItem() }
static constraints = {
postList nullable: false
}
class ListItem implements Validateable {
String tag
String some_id
static constraints = {
some_id nullable: false, blank: false
tag nullable: false, blank: false
tag inList: Tag.values() as List
}
}
}
AND the Payload:
{
"noteVotesButWorks": [
{
"tag": "good"
},
{
"tag": "bad"
}
]
}
This payload passes the validation check in my controller action.
def save(VoteCommand command) {
println(command.errors) //grails.validation.ValidationErrors: 0 errors
if (command.hasErrors()) {
respond params.errors, view: 'create'
} else {
withFormat {
'*' { render status: CREATED }
}
}
}
After making the POST request to this action, I get a 201 and grails.validation.ValidationErrors: 0 errors printed to stdout.
Please, can someone here give me some pointers?
Please, can someone here give me some pointers?
Your payload includes the key noteVotesButWorks. The data binder is going to create an instance of VoteCommand and then look to see if there is a noteVotesButWorks property on that instance, and there isn't, so the data binder doesn't really have anything to do. Your VoteCommand instance is then validated, which passes because your only constraint is postList nullable: false, which passes because postList is not null.
That all is working as designed. You probably want the key in your payload map to match the name of the List property in VoteCommand.
Separate from all of that, there is no good reason to include .withLazyDefault { new ListItem() } in your property initialization. You don't really have to initialize the property at all. The data binder will do that for you.
I don't think you want nullable: false for postList. An empty list is not null. I think you want minSize: 1.

Grails taglib that returns a list

I'm trying to create (my first ever) taglib in grails and I sort of got it down except that I'm trying to return a list/array of strings that has to be displayed in a table but all I'm getting is the first value of the list. Here's what I got:
Taglib:
def roles = { attrs, body ->
def user = User.get(attrs.user)
if(user) {
def roles = Role.findAllById(UserRole.findByUser(user).roleId)
def realRoles = []
roles.each { role ->
realRoles.add(role.authority.substring(5))
}
out << realRoles
out << body()
}
}
Html:
<table class="table table-bordered table-hover" style="background-color: #FFFFFF;">
<thead>
<tr style="background-color: #007FB2;color: #FFFFFF;">
<th>ID</th>
<th>Name</th>
<th>Surname</th>
<th>Email</th>
<th>Roles</th>
<th>Delete</th>
</tr>
</thead>
<g:each in="${users}" var="user">
<tr>
<td>${user.id}</td>
<td>${user.firstName}</td>
<td>${user.surname}</td>
<td>${user.username}</td>
<td><g:roles user="${user.id}" /></td>
<td><button type="button" class="gen-btn btn-red" style="width: 100%;" onclick="confDelete(${user.id}, '${user.firstName}');">Delete</button></td>
</tr>
</g:each>
</table>
What's supposed to happen is when the taglib gets called it's supposed to go get all the roles associated with the user id then take each role's authority property and cut the preceding "ROLE_" off of it. So ROLE_ADMIN just becomes ADMIN and ROLE_USER just becomes USER, etc. What I'd ideally like is a list that I can loop through in the gsp but I realise that's alot to ask so if I can just get help getting a comma separated list roles back from my taglib I'd really appreciated it.
To make it more clear, what I'm currently getting back is literally [ROLE_ADMIN] regardless of whether the user has more roles than that. What I want is a complete list of roles, e.g. ROLE_ADMIN, ROLE_MANAGER, ROLE_WHATEVER.
Thanks in advance
EDIT Here are my User, Role and UserRole domain classes. I am using Spring security to generate these classes.
User:
package fake.package.name
class User {
transient springSecurityService
String username
String password
String firstName
String surname
Date dateCreated
boolean enabled
boolean accountExpired
boolean accountLocked
boolean passwordExpired
static constraints = {
username blank: false, unique: true
password blank: false
firstName blank: false
surname blank: false
}
static mapping = {
password column: '`password`'
}
Set<Role> getAuthorities() {
UserRole.findAllByUser(this).collect { it.role } as Set
}
def beforeInsert() {
encodePassword()
}
def beforeUpdate() {
if (isDirty('password')) {
encodePassword()
}
}
protected void encodePassword() {
password = springSecurityService.encodePassword(password)
}
String toString(){
if((firstName)&&(surname))
firstName+ " " +surname
else
username
}
}
Role:
package fake.package.name
class Role {
String authority
static mapping = {
cache true
}
static constraints = {
authority blank: false, unique: true
}
}
UserRole:
package fake.package.name
import org.apache.commons.lang.builder.HashCodeBuilder
class UserRole implements Serializable {
User user
Role role
boolean equals(other) {
if (!(other instanceof UserRole)) {
return false
}
other.user?.id == user?.id &&
other.role?.id == role?.id
}
int hashCode() {
def builder = new HashCodeBuilder()
if (user) builder.append(user.id)
if (role) builder.append(role.id)
builder.toHashCode()
}
static UserRole get(long userId, long roleId) {
find 'from UserRole where user.id=:userId and role.id=:roleId',
[userId: userId, roleId: roleId]
}
static UserRole create(User user, Role role, boolean flush = false) {
new UserRole(user: user, role: role).save(flush: flush, insert: true)
}
static boolean remove(User user, Role role, boolean flush = false) {
UserRole instance = UserRole.findByUserAndRole(user, role)
if (!instance) {
return false
}
instance.delete(flush: flush)
true
}
static void removeAll(User user) {
executeUpdate 'DELETE FROM UserRole WHERE user=:user', [user: user]
}
static void removeAll(Role role) {
executeUpdate 'DELETE FROM UserRole WHERE role=:role', [role: role]
}
static mapping = {
id composite: ['role', 'user']
version false
}
}
Do note that because this a work project and not a personal one I'm not allowed to edit the code in here. Also, I changed the package names to fake.package.name, that's not the actual names of the packages.
I don't know, where your User<->Role Association comes from (Spring Security Framework?) or if it's coded by yourself. And you gave no examples of your domain-classes.
Let me explain another solution a bit:
If you design your domain-classes somehow like the following:
package xyz
class User {
String name
static hasMany = [authorities: Role]
static constraints = {
}
}
---------------
package xyz
class Role {
static belongsTo = User
String name
static hasMany = [user: User]
static constraints = {
}
}
a third table 'user_roles' will be created for saving the many-to-many relation/association
With this configuration, you could shorten your taglib to:
def roles = { attrs, body ->
def user = User.get(attrs.user)
if(user) {
def realRoles = []
user.authorities.each { r ->
realRoles.add(r.authority.substring(5))
}
out << realRoles.join(', ')
out << body()
}
}
Thanks to susi I managed to figure this thing out. Here: def roles = Role.findAllById(UserRole.findByUser(user).roleId) I only returned the first UserRole so I couldn't possibly bring back more than one Role. This is what my taglib method looks like now. I realize it might not be the most efficient and if someone would like to improve on it feel free.
def roles = { attrs, body ->
def user = User.get(attrs.user)
if(user) {
def userRoles = UserRole.findAllByUser(user)
def roles = []
def realRoles = []
userRoles.each { ur ->
roles.add(Role.findById(ur.roleId))
}
roles.each { r ->
realRoles.add(r.authority.substring(5))
}
realRoles = realRoles.join(", ")
println(realRoles)
out << realRoles
out << body()
}
}
Starting after the line if(user) {:
I find all the userRoles associated with the user Id
Then I loop through the list of userRoles and add each ur to a roles list
Then I loop through the list of Roles and add the formatted role's authority to the realRoles list
Lastly I use realRoles = realRoles.join(", ") to remove the square brackets around eachrealRoles` item
the root cause is the line
def roles = Role.findAllById(UserRole.findByUser(user).roleId)
because the UserRole.findByUser(user) just returnes 1 UserRole, roles is just 1 Role object inside a List, instead of all the User's Role objects.
change it to
def roles = Role.findAllByIdInList(UserRole.findAllByUser(user)*.roleId)
as you seem to be relatively new to Grails, a quick explanation: you can use dynamic finders (find*) in 2 ways: findBy* and findAllBy*. as the naming of the methods suggest, findBy* returns 1 object or null. findAllBy* returns a collection of objects.
Additionally, you can combine the property-names with operations (in our case "InList") to specify the type of search operation.
In this example we first retrieve a list of UserRole objects, transform this list into a list of role ids (using the spread operator "*.") and with these ids retrieve all Role objects having one of these IDs.
as the UserRole class contains a Role property directly, of course we can directly write
def roles = UserRole.findAllByUser(user)*.role
keep in mind, that this list could possibly contain null if the property role on UserRole class was nullable (it's not the case here, but if any reader has a similar problem but with a nullable association).
summarized, what you are doing in the taglib at the moment, could be written as:
out << UserRole.findAllByUser(user)*.role.collect({ it.substring(5) }).join(', ')
and just a little comment to the previous answer from susi:
that code shows a direct many-to-many association between User and Role.
as Burt Beckwith wrote some time ago https://mrpaulwoods.wordpress.com/2011/02/07/implementing-burt-beckwiths-gorm-performance-no-collections/
this can have impact on performance as every new User that gets added to a Role requires hibernate to load all existing User-Role-Connections, which could cause trouble when you have lots of users.
That's why the scaffolded domain classes of spring-security-core (since 2.x version iirc) now explicitely define the UserRole-class.

grails linking a cart object to a user object

Good day. I am new to grails and I have to design a music-shopping type app. I have a Cart domain and a User domain and what I'm trying to do is when I create a new User object I want to have a new Cart object created too that links to said User object in the User. I'm using static scaffolding so I want to do this in the User/save action. Here's my code:
class Cart {
String item
Integer quantity
BigDecimal price
String type
Integer typeId
User user
static constraints = {
type nullable:false, blank:false, inList:["Album", "Song", "Empty"]
typeId nullable:false, blank:false
}
}
class User {
String username
String password
String fName
String lName
String email
static constraints = {
username(nullable : false, blank : false, minSize : 1)
password(nullable : false, blank : false, minSize : 1)
fName(nullable : false, blank : false, minSize : 1)
lName(nullable : false, blank : false, minSize : 1)
email(nullable : false, blank : false, email : true)
}
}
class UserController {
//static scaffolded code (index, show, create, etc.)
#Transactional
def save(User userInstance){ //This whole method is also generated with scaffolding
if (userInstance == null) {
notFound()
return
}
if (userInstance.hasErrors()) {
respond userInstance.errors, view: 'create'
return
}
userInstance.save flush: true
//This is what I've been trying to do but it doesn't work and I don't know why :(
def myCart = new Cart(user:userInstance, item:'empty item', quantity:0, price:0, total:0, type:'Empty', typeId:0).save(flush:true)
//Rest of the generated code
}
//More generated code
}
Any help would be appreciated. Thanks!
You have 2 bugs in Cart class.
First: update typeID to typeId
Second: type should be String, not BigDecimal
I think this link explains well on how to do cascading in Grails.
Cascading determines what type of actions, when applied to a domain instance, also apply to the relations of that instance.
According to how the cascading goes, I think it makes more sense that you have a Cart object in User class and use belongsTo to let Grails know the Cart is saved or deleted with the User.

Grails: Legacy DB - Record with composite Id won't update

I'm having problems trying to update a record on a Grails 2.3.7 Project
I don't want to modify the fields that are part of the composite Id, I just want to update the other fields.
Just one class, few properties, but every time I try to update, it throws me the "not unique error" when this lines runs:
personInstance.validate()
if (personInstance.hasErrors()) {
respond personInstance.errors, view:'create'
return
}
My class looks like this:
class Person implements Serializable {
static constraints = {
name(unique: lastName)
}
static mapping = {
id generator: 'assigned'
id composite: ["name", "lastName"]
}
//Override equals and hashcode methods
boolean equals(other) {
if (!(other instanceof Person)) {
return false
}
other.name == name && other.lastName == lastName
}
int hashCode() {
def builder = new HashCodeBuilder()
builder.append name
builder.append lastName
builder.toHashCode()
}
String name
String lastName
String description
}
And the controller action:
def update() {
def personInstance = Person.get(new Person(name:params.name, lastName:params.lastName))
personInstance.properties = params
personInstance.validate()
if (personInstance.hasErrors()) {
respond personInstance.errors, view:'create'
return
}
personInstance.save flush:true
request.withFormat {/*etc...*/}
}
When I use validate(), it throws me a Grails unique key error, when I avoid validation its a BD not unique PK error.
Is like Grails didn't know if I want to do an insert or an update when I personInstance.validate().
Is there a way to manage this in a correct way that I'm not seeing?
Or am I forced to avoid validation?
am I doing something wrong?
Thanks in advance.
I believe the GORM mapping DSL expects just one id definition. Try combining your two id lines into just this one:
id generator: 'assigned', composite: ["name", "lastName"]
Also, in addition to implementing Serializable, your domain class should override equals and hashCode, as described under "Composite Primary Keys" here: http://grails.org/doc/latest/guide/single.html#identity

Validation in domain class

I have to validate whether this value of eventId already exist in mysql database.i have a database of event where the id are stored in event_id. so i need to validate this eventId already exists or not.
Domain Class:
class Event {
String eventId
static constraints = {
eventId(blank: false,nullable: false)
eventId validator: {val ->
if(val== event.event_id){
return false
}
}
You should use unique constraint provided by grails
e.g
static constraints = {
eventId unique: true
}

Resources