These are my domain objects
User{
hasMany = {roles: UserRole}
}
UserRole{
User user
Role role
}
Role{
String authority
}
I need to find users based on their Role. For that I am trying to use the following criteria:
def role_admin = Role.findByAuthority('ROLE_ADMIN')
def criteria = new DetachedCriteria(User).build {
roles{
role{
idEq(role_admin.id)
}
}
}
result.users = criteria.list(params)
result.total = criteria.count()
The above will always return one result, even though I have verified by looking at the database directly that there should be more results. The params passed to list are correct, but I tried removing them just to be sure. I can't see what is wrong with the above, any suggestions ?
I also tried this
roles{
role{
eq("authority","ROLE_ADMIN")
}
}
But it is throwing exception:
Unknown column 'role_alias2_.authority' in 'where clause'
this works for me:
def criteriaUsers = UserRole.createCriteria()
def users = criteriaUsers.listDistinct{
eq("role", role_admin)
}.user
Side note: from the nomenclature of your classes it looks like you are using spring-security-core plugin. In my humble opinion the hasMany = [roles: UserRole] is redundant as the association User - Role is already being modeled in the UserRole class.
Related
I have a User class that hasMany organizations, and organizations are String UIDs.
class User implements Serializable {
...
List organizations
static hasMany = [organizations: String]
...
}
To update an User instance, I need to remove all the Strings from "organizations" before adding what the updated uids, but it doesn't work. The same organizations are resaved into the collection after I delete them and save the User instance.
I tried many ways:
// 1
user.organizations.clear()
// 2
def orgsToDelete = [] // this is to avoid concurrent modification exception
orgsToDelete += user.organizations
orgsToDelete.each { orguid ->
user.removeFromOrganizations(orguid)
}
After any of 1 or 2, the user.organizations is empty, but when I do:
user.save(flush:true)
I get the same organizations that where before the clear/removeFromOrganizations
I can't do user.organizations.each { it.delete() } because the items are not domain classes but Strings.
Another weird thing is I have a custom validator to check if the collection has any items, and it seems it doesn't gets any errors, event the organizations is empty, this is my validator:
organizations validator: { val, obj ->
if (obj.organizations.size() == 0) return false
return true
}
When I do this on the controller update action, it says hasErrors() == false
if (userInstance.hasErrors())
{
println "has errors"
respond userInstance.errors, view:'edit'
return
}
Any ideas?
Here is the controller: https://github.com/ppazos/cabolabs-ehrserver/blob/master/grails-app/controllers/com/cabolabs/security/UserController.groovy#L199-L237
Use your own domain class instead of String as collection's elements, like:
class Organization {
String name
static belongsTo = [ User ]
}
then you should be able to properly remove the children by
user.organisations.clear()
user.save()
The reason for the behaviour you a facing is, that the String instances are mapped to a table which has no back-refs to the user. That leads to the situation, that the records in that string table are not actually removed and are loaded upon the next database call.
I have read your project code and you have the DataSource bad set. You are using a H2 memory BBDD, but using the MySQL driver instead (in the development environment). It doesnt look good. Set it properly, and try again.
I have test your model, and used:
user.organizations.clear()
without any problems, even using your custom validator.
In other hand you should use a Service instead for all the business logic, not a controller. Because a service is transactional by default.
Here are the changes I did, and works:
// List organizations = []
static hasMany = [organizations: String]
static mapping = {
password column: '`password`'
// organizations lazy: false
}
Removing List organizations. Using the H2 datasource with this config at DataSource development environment:
dbCreate = "create-drop"
url = "jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000"
username = "sa"
password = ""
And this code works like a charm:
def clearOrganizationFromUser(){
//creating user with timestamp
User user = new User(username: 'username' + new Date(), password: 'password', email: 'email#email.es', accountExpired: false, accountLocked: false,
passwordExpired: false)
List<String> organizations = []
10.times{
organizations << "organization $it"
}
organizations.each{ String organization ->
user.addToOrganizations(organization)
}
user.save()
println "Organizations after saving: ${user.organizations}"
//Its not neccesary but I did it for showing that organization are persisted in DDBB
User newUserFromDDBB = User.get(user.id)
//deleting organization
newUserFromDDBB.organizations.clear()
newUserFromDDBB.save()
println "---> Organizations after deleting: ${newUserFromDDBB.organizations}"
}
Hope It works. If do, please mark as solved.
Say, for a system, I had Student and User objects, like so:
User {
}
Student {
User user //not nullable
static belongsTo = [User]
}
Teacher {
static belongsTo = [user:User]
}
I have a lot of code written against these models, and plenty of dynamic finders in it
def u1 = new User().save( flush: true)
new Student( user: u1 ).save( flush: true)
...
def s = Student.findByUser( springSecurityService.currentUser)
//s can be null
def t = Teacher.findByUser( springSecurityService.currentUser)
//t can be null
if( s != null)
//do something with 's'
if( t != null)
//do something with 't'
...
And this all worked fine. But I wanted to update my domain model to add cascading access from User to Student. So I did:
User {
Student student //nullable
}
And I would save a user like this:
new User( student: new Student() ).save( flush: true)
But the problem is, when I try to access 's' like I did above.
If the Student.findByUser can find one such student, then it works.
But if it should return null, it gives an error instead.
def s = Student.findByUser( springSecurityService.currentUser)
will result in:
org.h2.jdbc.JdbcSQLException
Parameter #2 is not set; SQL statement:
select this_.id as id18_0_, this_.version as version18_0_, from student this_ where this_.id=? limit ? [90012-173]
I know I can access the student object through the user object like user.student, but I would like to be able to keep the existing code as it is.
I've seen this thread, and it had a similar problem, but no resolution grails email list
Is this a bug? What can I do to get around it? Am I not setting up my domain models properly?
Thanks
As an alternative, you could try something like this using hasOne instead of belongsTo.
class User {
static hasOne = [student:Student, teacher:Teacher]
static constraints = {
student unique:true, nullable: true
teacher unique:true, nullable: true
}
}
class Student {
User user
}
class Teacher {
User user
}
It still cascades and appears to handle null searches fine.
A domain Staff has a User.
class Person {
User user
}
class Staff extends Person {
//other properties
}
class User {
String username
String password
}
I know a user logged in, now I want to find the Staff by the same logged in User. No relation is maintained from User's side.
The code I am implementing is :
def createdBy = User.get(springSecurityService.principal.id)
log.info("User id : "+createdBy.id) // it works
def staff = Staff.findByUser(createdBy) //it returns null
Is this not applicable in GORM or I'm missing something?
grails findBy documentation has nothing to tell about findByDomain().
The question is CLOSED as the error was while inserting a Staff with a User which was not heppening in proper way.(poor grails didn't notify me.)
Above code works perfectly.
def createdBy = User.get(springSecurityService.principal.id)
def staff = Staff.findByUser(createdBy)
But, meanwhile implemented another way of finding Staff in criteria way :
def createdBy = User.get(springSecurityService.principal.id)
def staff = staffCriteria.get{
user{
idEq(createdBy.id)
}
}
What is the correct way to handle domain save errors on domain classes under the hasMany relationship? It seems that calling save() on the owning side of the relation will return true even if there are validation errors on the owned objects.
How should I test this in running code or integration test?
I have reduced my problem to the following simple case.
class User {
static hasMany = [emails: Email]
static constraints = { }
}
.
class Email {
static belongsTo = [user: User]
String emailAddress
static constraints = {
emailAddress unique: true
}
}
Here are two suggestions on how to do this. Neither is really elegant.
First one calls save() individually to each on the hasMany relationship. What is good here is that we get the exact error out of the test case. This pretty cumbersome.
#Test
void testHasManyConstraintsOwned(){
def john = new User(login: 'johnDoe')
def email = new Email(emailAddress: 'john#gmail.com')
def duplicateEmail = new Email(emailAddress: 'john#gmail.com')
john.save() // otherwise: NULL not allowed for column "USER_ID" is thrown for email.save()
john.addToEmails(email).addToEmails(duplicateEmail)
assert email.save()
assert !duplicateEmail.save()
assert "unique"== duplicateEmail.errors.getFieldError("emailAddress").code
}
Another approach uses try/catch to detect the excepted fail. Problem here is that we have no way of knowing what went wrong and thus cannot actually test that the domain constraints are working as we expect.
#Test
void testHasManyConstraintsOwningExcp(){
def john = new User(login: 'johnDoe')
def email = new Email(emailAddress: 'john#gmail.com')
def duplicateEmail = new Email(emailAddress: 'john#gmail.com')
john.addToEmails(email).addToEmails(duplicateEmail)
try {
john.save(flush: true, failOnError: true)
assert false // should not reach here
}catch(Exception e){
}
}
What is the correct way to test and to react in the application code?
I have the following domain classes (shortened version)
class TalkingThread {
static hasMany = [comments:Comment]
Set comments = []
Long uniqueHash
}
and
class Comment {
static belongsTo = [talkingThread:TalkingThread]
static hasOne = [author:CommentAuthor]
Long uniqueHash
static constraints = {
uniqueHash(unique:true)
}
}
and
class CommentAuthor {
static hasMany = [comments:Comment]
Long hash
String name
String webpage
}
the following methods
public TalkingThread removeAllComments(TalkingThread thread){
def commentsBuf = []
commentsBuf += thread.comments
commentsBuf.each{
it.author.removeFromComments(it)
thread.removeFromComments(it)
it.delete()
}
if(!thread.save()){
thread.errors.allErrors.each{
println it
}
throw new RuntimeException("removeAllComments")
}
return post
}
public addComments(TalkingThread thread, def commentDetails){
commentDetails.each{
def comment = contructComment(it,thread)
if(!comment.save()){
comment.errors.allErrors.each{ println it}
throw new RuntimeException("addComments")
}
thread.addToComments(comment)
}
return thread
}
Sometimes I need to remove all of the comments from a TalkingThread and add comments that share the same uniqueHashes. So I call the removeAllComments(..) method, and then the addComments(..) method. This causes a
Comment.uniqueHash.unique.error.uniqueHash which caused by a supposedly deleted comment and a 'fresh' comment being added.
Should I be flushing? Maybe there is something wrong with my domain classes?
Edit Expansion of question.
Maybe this is a different question, but I thought that the session has deleted all associations and objects. Therefore the session state is aware that all TalkingThread comments have been deleted. Of course this has not been reflected in the database. I also assumed that the 'saving' of new Comments would be valid given that such 'saving' is consistent with the session state. However such 'saving' would be inconsistent with the database state. Therefore, my understanding of how grails validates objects in relation to session and database state is flawed! Any help in understanding the process of validating saves with respect to session and database states would also be appreciated.
If you want to remove all the Comments from a TalkingThread then you can use Hibernate's cascade behaviour.
Add
static mapping = {
comments cascade: 'all-delete-orphan'
}
to TalkingThread and then you can call comments.clear() followed by thread.save() which will delete the comments that were in the association.
There's a good article on Grails one-to-many-relationships here. The official Grails docs on it are here.