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

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?

Related

Grails 2.4.4 removing all items from a hasmany String relationship

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.

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'
}
}

Writing an Integration Test in Grails that will test DB persistence

My Integration-Test for my grails application is returning a null object when I try to get a domain object using grails dynamic get method.
This is a simplified example of my problem. Lets say I have a controller TrackerLogController that uses a service TrackerLogService to save an updated Log domain for another Tracker domain.
Domain Tracker:
class Tracker {
int id
String name
static hasMany = [logs: Log]
}
Domain Log:
class Log {
int id
String comment
static belongsTo = [tracker: Tracker]
}
Controller TrackerLogController save:
def TrackerLogService
def saveTrackerLog() {
def trackerId = params.trackerId
def trackerInstance = Tracker.get(trackerId)
Log log = TrackerLogService.saveTrackerLogs(trackerInstance, params.comment)
if( log.hasErrors() ){
//render error page
}
//render good page
}
Service TrackerLogService save:
Log saveTrackerLogs( Tracker tracker, String comment) {
Log log = new Log(tracker: tracker, comment: comment)
log.save()
return log
}
So now I want to write an Integration-Test for this service but I'm not sure if I should be writing one just for the simple logic in the controller (if error, error page else good page) I would think I would write a Unit test for that, and an Integration-Test to check the persistence in the Database.
This is what I have for my Integration-Test:
class TrackerLogServiceTests {
def trackerLogService
#Before
void setUp(){
def tracker = new Tracker(id: 123, name: "First")
tracker.save()
//Now even if I call Tracker.get(123) it will return a null value...
}
#Test
void testTrackerLogService() {
Tacker trackerInstance = Tracker.get(123) //I have tried findById as well
String commit = "This is a commit"
//call the service
Log log = trackerLogService.saveTrackerLogs(trackerInstance , commit)
//want to make sure I added the log to the tracker Instance
assertEquals log , trackerInstance.logs.findByCommit(commit)
}
}
So for this example my trackerInstance would be a null object. I know the Grails magic doesn't seem to work for Unit tests without Mocking, I thought for Intigration-Tests for persistence in the DB you would be able to use that grails magic.
You can't specify the id value unless you declare that it's "assigned". As it is now it's using an auto-increment, so your 123 value isn't used. It's actually ignored by the map constructor for security reasons, so you'd need to do this:
def tracker = new Tracker(name: "First")
tracker.id = 123
but then it would get overwritten by the auto-increment lookup. Use this approach instead:
class TrackerLogServiceTests {
def trackerLogService
private trackerId
#Before
void setUp(){
def tracker = new Tracker(name: "First")
tracker.save()
trackerId = tracker.id
}
#Test
void testTrackerLogService() {
Tacker trackerInstance = Tracker.get(trackerId)
String commit = "This is a commit"
//call the service
Log log = trackerLogService.saveTrackerLogs(trackerInstance , commit)
//want to make sure I added the log to the tracker Instance
assertEquals log , trackerInstance.logs.findByCommit(commit)
}
}
Also, unrelated - don't declare the id field unless it's a nonstandard type, e.g. a String. Grails adds that for you, along with the version field. All you need is
class Tracker {
String name
static hasMany = [logs: Log]
}
and
class Log {
String comment
static belongsTo = [tracker: Tracker]
}

Grails service not saving Domain Object When triggered by Message Queue

I have a grails application that has a service that creates reports. The report is defined as:
class Report {
Date createDate
String reportType
List contents
static constraints = {
}
}
The service generates a report and populates contents as a list that is returned by createCriteria.
My problem is that my service claims to be saving the Report, no errors turn up, logging says that its all there, but when I go to call show from the controller on that report, it says contents is null.
Another relevant bit, my Service is called by an ActiveMQ message queue. The message originating from my report controller.
Controller:
class ReportController {
def scaffold = Report
def show = {
def rep = Report.get(params.id)
log.info("Report is " + (rep? "not null" : "null")) //says report is not null
log.info("Report content is " + (rep.contents? "not null" : "null")) //always says report.contents is null.
redirect(action: rep.reportType, model: [results: rep.contents, resultsTotal: rep.contents.size()])
}
}
My service that creates the report:
class ReportService {
static transactional = false
static expose = ['jms']
static destination = "Report"
void onMessage(msg)
{
this."$msg.reportType"(msg)
}
void totalQuery(msg)
{
def results = Result.createCriteria().list {
//This returns exactly what i need.
}
Report.withTransaction() {
def rep = new Report(createDate: new Date(), reportType: "totalQuery", contents: results)
log.info("Validation results: ${rep.validate()}")
if( !rep.save(flush: true) ) {
rep.errors.each {
log.error(it)
}
}
}
}
Is there something obvious that I'm missing here? My thought is that since all my unit tests work, that the hibernate context is not being passed through the message queue. But that would generate Exceptions wouldn't it? I've been beating my head on this problem for days, so a point in the right direction would be great.
Thanks,
You can't define an arbitrary List like that, so it's getting ignored and treated as transient. You'd get the same behavior if you had a def name field, since in both cases Hibernate doesn't know the data type, so it has no idea how to map it to the database.
If you want to refer to a collection of Results, then you need a hasMany:
class Report {
Date createDate
String reportType
static hasMany = [contents: Result]
}
If you need the ordered list, then also add in a List field with the same name, and instead of creating a Set (the default), it will be a List:
class Report {
Date createDate
String reportType
List contents
static hasMany = [contents: Result]
}
Your unit tests work because you're not accessing a database or using Hibernate. I think it's best to always integration test domain classes so you at least use the in-memory database, and mock the domain classes when testing controllers, services, etc.

Grails validation of a list objects

I'm trying to get grails to validate the contents of a List of objects, might be easier if I show the code first:
class Item {
Contact recipient = new Contact()
List extraRecipients = []
static hasMany = [
extraRecipients:Contact
]
static constraints = {}
static embedded = ['recipient']
}
class Contact {
String name
String email
static constraints = {
name(blank:false)
email(email:true, blank:false)
}
}
Basically what i have is a single required Contact ('recipient'), this works just fine:
def i = new Item()
// will be false
assert !i.validate()
// will contain a error for 'recipient.name' and 'recipient.email'
i.errors
What I'd like also do it validate any of the attached Contact objects in 'extraRecipients' such that:
def i = new Item()
i.recipient = new Contact(name:'a name',email:'email#example.com')
// should be true as all the contact's are valid
assert i.validate()
i.extraRecipients << new Contact() // empty invalid object
// should now fail validation
assert !i.validate()
Is this possible or do I just have to iterate over the collection in my controller and call validate() on each object in extraRecipients?
If I'm understanding the question correctly, you want the error to appear on the Item domain object (as an error for the extraRecipients property, instead of letting the cascading save throw a validation error on the individual Contact items in extraRecipients, right?
If so, you can use a custom validator in your Item constraints. Something like this (this hasn't been tested but should be close):
static constraints = {
extraRecipients( validator: { recipients ->
recipients.every { it.validate() }
} )
}
You can get fancier than that with the error message to potentially denote in the resulting error string which recipient failed, but that's the basic way to do it.

Resources