Grails 2.4.4 removing all items from a hasmany String relationship - grails

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.

Related

How can I use Spring Data Neo4j to create a Node with new Relationships to existing Nodes

My use case is that I want to send a Message from one User to another, by creating a Message node in between two Users. Here is the JSON I would like to send from the client, and my Message class:
{"text":"Hello!","recipient":{"userId":"1823498e-e491-45fa-95bb-782a937a00ba"},"sent":{"userId":"6467b976-7ff6-4817-98e0-f3fbfca1413e"}}
#Node("Message")
#Data
public class Message
{
#Id #GeneratedValue private UUID messageId;
#NotNull
private String text;
#Relationship(type = "SENT",direction = Direction.INCOMING)
private User sent;
#Relationship("TO")
private User recipient;
}
But when I call Mono<Message> save(Message message); in the ReactiveNeo4jRepository, the DB unique constraint complains that Users with those usernames already exist! It seems like it's trying to reset all of the other properties for those two userIds to empty/defaults, presumably since they're not included in the JSON. So I get a constraint error when it sees two "FAKE" usernames.
2022-03-26 14:34:29.120 DEBUG 51863 --- [o4jDriverIO-2-4] o.n.d.i.a.o.OutboundMessageHandler : [0xb08eba68][x.server.com:7687][bolt-128] C: RUN "MERGE (user:`User` {userId: $__id__}) SET user += $__properties__ RETURN user" {__id__="6467b976-7ff6-4817-98e0-f3fbfca1413e", __properties__={lastName: NULL, credentialsNonExpired: FALSE,userId: "6467b976-7ff6-4817-98e0-f3fbfca1413e", enabled: FALSE, firstName: NULL, password: "FAKE",accountNonExpired: FALSE, email: NULL,username: "FAKE", accountNonLocked: FALSE}} {bookmarks=["FB:kcwQx2Hl5+KYQJiGdiyzOa4EWSCQ"]}
2022-03-26 14:34:29.206 DEBUG 51863 --- [o4jDriverIO-2-4] o.n.d.i.a.i.InboundMessageDispatcher : [0xb08eba68][x.server.com:7687][bolt-128] S: SUCCESS {fields=["user"], t_first=2}
2022-03-26 14:34:29.207 DEBUG 51863 --- [o4jDriverIO-2-4] o.n.d.i.a.o.OutboundMessageHandler : [0xb08eba68][x.server.com:7687][bolt-128] C: PULL {n=-1}
2022-03-26 14:34:29.301 DEBUG 51863 --- [o4jDriverIO-2-4] o.n.d.i.a.i.InboundMessageDispatcher : [0xb08eba68][x.server.com:7687][bolt-128] S: FAILURE Neo.ClientError.Schema.ConstraintValidationFailed "Node(1) already exists with label `User` and property `username` = 'FAKE'"
UPDATE: I can accomplish what I need to using an ObjectMapper from Jackson and the Neo4j Driver directly, as follows. But I still would like to know how to this with SDN.
#Override
public Mono<UUID> save(Message message)
{
String cypher = "MATCH (a:User),(b:User) where a.userId = $fromUserId and b.userId = $toUserId CREATE (a)-[:SENT]->(m:Message {messageId: $messageId, text : $text})-[:TO]->(b)";
Map<String,Object> objMap = persistenceObjectMapper.convertValue(message,mapType);
return Mono.from(driver.rxSession().writeTransaction(tx -> tx.run(cypher,objMap).records())).then(Mono.just(message.getMessageId()));
}
It seems like it's trying to reset all of the other properties for those two userIds to empty/defaults
The problem you are facing is rooted in the handling of the incoming Message object.
As far as I understood this, you are directly saving the Message with the attached, de-serialized Users.
You have to fetch the Users from the database first. SDN does no pre-fetch. One way to do this is to create an additional repository for the User entity.
Given a MovieService that gets called by the controller, you would end up with something like this:
#Transactional
public Message saveNewMessage(Message newMessage) {
UUID recipientId = newMessage.getRecipient().getUserId();
UUID senderId = newMessage.getSent().getUserId();
User recipient = userRepository.findById(recipientId).orElseGet(() -> {
User user = new User();
user.setName("new recipient");
return user;
});
User sender = userRepository.findById(senderId).orElseGet(() -> {
User user = new User();
user.setName("new sender");
return user;
});
newMessage.setRecipient(recipient);
newMessage.setSent(sender);
return messageRepository.save(newMessage);
}
Tested this with:
CREATE CONSTRAINT username ON (user:User) ASSERT user.name IS UNIQUE
for 4.3 and/or for 4.4
CREATE CONSTRAINT username FOR (user:User) REQUIRE user.name IS UNIQUE
I created an example for your use case: https://github.com/meistermeier/neo4j-issues-examples/tree/master/so-71594275
There I also a user properties erasing call stack (/clearusers) that shows what I think happens in your application.
Edit: Since I usually advice not to create a repository for every entity class, you could also use the Neo4jTemplate. I have added the saveNewMessageAlternative to the example.

Grails Criteria - list contents

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.

Grails Web flow with Multiple domain

I am new to Grails development. I started doing the web flow for the user registration. It has two forms that in first form i get the basic details and get the bank details in second one. And both of them going to save in different domains.
class Customer {
String Name
Integer Age
Date DateOfBirth
String FatherOrHusbandName
String IdProof
String IdProofNumber
Boolean IsDeleted
static hasOne = [bankdetail:BankDetails]
static hasMany = [property:Property]
static constraints = {
}}
Bank details
class BankDetails {
String bank_name
String account_number
String account_holder_name
String branch_name
String IFSC_code
String account_type
Customer customer
static constraints = {
customer unique:true
}
static mapping = {
customer insertable: false
customer updateable: false
}}
These two domain classes are used for the customer. I got the nice tutorial for web flow implementation from http://ridingthetiger.wikia.com/wiki/Creating_the_Example_Grails_WebFlow_Project. But in this tutorial only one domain is used. I want the grails webflow example with two or more domain classes. Please suggest if a have any or give one simple example....
Thanks in advance
Basically, what I do is create two command objects and pass them around through next and previous. One for each step. But in this case the domain objects are simple you can just pass them around in the flow object. e.g
def createCustomerFlow = {
basicInfo{
on('next'){Customer cust ->
flow.customer = cust
if(!cust.validate()){
flow.errorbean = cust
return error()
}else{
return success()
}
}.to 'bankInfo'
on("cancel").to 'cancel'
}
bankInfo{
on('finish'){BankDetails bank ->
flow.bank = bank
if(!bank.validate()){
flow.errorbean = bank
return error()
}else{
try{
//create customer. Implement this method yourself
def cust = createCustomer(flow.customer, flow.bank)
flow.customer = cust
}catch(ValidationException e){
log.error("Validation exception", e)
flow.errorbean = e
return error()
}
return success()
}
}.to 'summary'
on("previous"){BankDetails bank ->
flow.bank = bank
}.to 'basicInfo'
on("cancel").to 'cancel'
}
}

Strange Grails behaviour populating List at bootstrap

I'm becoming crazy on a strange Grails behaviour.
I get a domain class user :
class User {
String firstName
String token
List roles = [] as List
static constraints = {
}
}
I modify my BootStrap.groovy like this to populate 5 user instance :
class BootStrap {
def init = { servletContext ->
switch (Environment.getCurrent()) {
case 'DEVELOPMENT':
def user1 = new User(
id: 1,
firstName: 'Enricot',
token: 'L\'abricot'
)
user1.roles.add('ROLE_USER')
user1.save(failOnError: true)
def user2 = new User(
id: 2,
firstName: 'Arnaud',
token: 'Dauphin')
user2.roles.add('ROLE_USER')
user2.roles.add('PERM_WRITE')
user2.save(failOnError: true)
def user3 = new User(
id: 3,
firstName: 'Magalie',
token: 'La banane')
user3.roles.add('ROLE_USER')
user3.roles.add('PERM_READ')
user3.save(failOnError: true)
def user4 = new User(
id: 4,
firstName: 'Jeremy',
token: 'Wistiti')
user4.roles.add('ROLE_USER')
user4.roles.add('ROLE_ADMIN')
user4.save(failOnError: true)
def user5 = new User(
id: 5,
firstName: 'Jimini',
token: 'Criquet')
user5.roles.add('ROLE_USER')
user5.roles.add('ROLE_INTERACTIONS')
user5.save(failOnError: true)
break
case "test":
DataBuilder.init()
break
}
}
def destroy = {
}
}
In my controller, when I'm getting one user, all fields are corect, except the List.
If I do :
def user = User.get(1)
println user.firstName // Output is "Enricot"
println user.token // Output is "L'abricot"
println user.roles // Output is "[]" and not "[ROLE_USER]"
Trying populate object like this did not solve my problem :
def user1 = new User(
id: 1,
firstName: 'Enricot',
token: 'L\'abricot',
roles: ['ROLE_USER']).save(failOnError: true)
I'have try with double quote, simple quote.
The most funny part is when I'm making "user.roles" INTO BootStrap.groovy, I get the correct List.
It's making no sense for me, any help is welcome,
It's not strange behavior, you're doing it wrong :)
When you add a field that you want to be persistent, you need to give Grails information about how to store it. Just a simple List isn't enough information. If it's a list of other domain classes, it will create a foreign key and store the ids. If it's a list of Strings, it will store them as varchars. As shown in the documentation, you need to do something like this:
class User {
String firstName
String token
static hasMany = [roles: String]
}
This changes the backing collection to a Set, but this is likely correct for roles since you don't care about ordering and want uniqueness. If you do need ordering, add an uninitialized List field to signal to Grails that you don't want a Set:
class User {
String firstName
String token
List roles
static hasMany = [roles: String]
}
This changes how you populate the data. Instead of directly adding to the collection, use the dynamic addToRoles method that gets added for you:
class BootStrap {
def init = { ctx ->
environments {
development {
def user1 = new User(firstName: 'Enricot', token: 'L\'abricot')
user1.addToRoles('ROLE_USER')
user1.save(failOnError: true)
def user2 = new User(firstName: 'Arnaud', token: 'Dauphin')
user2.addToRoles('ROLE_USER')
user2.addToRoles('PERM_WRITE')
user2.save(failOnError: true)
def user3 = new User(firstName: 'Magalie', token: 'La banane')
user3.addToRoles('ROLE_USER')
user3.addToRoles('PERM_READ')
user3.save(failOnError: true)
def user4 = new User(firstName: 'Jeremy', token: 'Wistiti')
user4.addToRoles('ROLE_USER')
user4.addToRoles('ROLE_ADMIN')
user4.save(failOnError: true)
def user5 = new User(firstName: 'Jimini', token: 'Criquet')
user5.addToRoles('ROLE_USER')
user5.addToRoles('ROLE_INTERACTIONS')
user5.save(failOnError: true)
}
test {
DataBuilder.init()
}
}
}
}
See http://grails.org/doc/latest/guide/conf.html#environments for information about the environment block support in BootStrap.groovy.
Also note that I removed your explicit id values since you are not using assigned ids, so those values are ignored.
Finally, it looks like you're rolling your own security. Don't do this. Use a proven framework. Both Spring Security Core and Shiro are powerful and easy to get started with.
It's normal behaviour for grails. The code below just adds item to list in current object (Important! not to database reference):
user1.roles.add('ROLE_USER')
To add to database mapping to roles use dynamic method like this:
user1.addToRoles('ROLE_USER')
Assumption: you mean for your domain
List<Strings> roles
static hasMany = [roles:String]

Grails save() passes although objects in hasMany relationship has constraint errors

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?

Resources