How to ensure true one-to-many relationship - grails

I have one-to-many relationship between entities - RentalUnit and Review . All tests run fine, except when i am adding the same review to different RentalUnit instances as following:
def review3 = Review.build().save(flush: true)
def rentalUnit2 = RentalUnit.build().
addToReviews(review2).addToReviews(review3).save(flush: true)
assert rentalUnit.reviews.contains(review2)
It seems that GORM and the addTo* method doesn't seem to care if i am adding the same Review instance, so i am guessing there is something missing in my domain classes. What would that be?
Thank you for helping
p.s.
class Review {
String submittedBy
String content
String dateReceived
boolean isApproved
static belongsTo = RentalUnit
static mapping = {
content type: 'text'
}
static constraints = {
submittedBy blank: false, size: 3..50
content blank: false, size: 5..2500
}
}
class RentalUnit {
String name
String nickname
Address address
static hasMany = [reviews:Review]
static mapping = {
reviews cascade: "all-delete-orphan"
}
static constraints = {
name blank: false, unique: true, size: 4..10
nickname blank: false, size: 5..60
}
}

Yes it doesn't care - it doesn't change any other objects' properties. Even about the other end of relationship - review2.rentalUnit (if such field existed) will be null here.
The Review will disappear from rentalUnit.reviews (or review2.rentalUnit would be assigned) the next time the object is loaded from database.
You can assign review2.rentalUnit by hand and validate if it's been added to another RentalUnit, though - the assignment will do no harm.
edit: let's go through the code step-by-step.
rentalUnit1.addToReviews(review2)
Here review2 is added to rentalUnit1.reviews. review2.rentalUnit is NOT assigned, but after save in the database it would point to rentalUnit1. The only persistent representation of RentalUnit.reviews field is a child-to-parent reference field, Review.rentalUnit.
def rentalUnit2 = ...
rentalUnit2.addToReviews(review2).addToReviews(review3).save(flush: true)
Here review2 is added to rentalUnit2.reviews. review2.rentalUnit is NOT assigned again. review2 is NOT removed from rentalUnit1.reviews BUT, after save, in the database, it will point to rentalUnit2.
assert rentalUnit1.reviews.contains(review2)
review2 was NOT removed from rentalUnit1.reviews, so the assertion will pass. BUT in the next session rentalUnit1 and rentalUnit2 will have the correct sets of reviews - only rentalUnit2 will have review2.
Now, if you want to always maintain Java representation consistent, implment a method like this:
class RentalUnit {
RentalUnit addToReviewsAndCheck(Review r) {
if (r.rentalUnit == this) return;
if (r.rentalUnit != null) {
r.rentalUnit.removeFromReviews(r)
}
r.rentalUnit = this
addToReviews(r)
}
}
but it's an overkill, as for me.

Related

Grails 3.3.10 value set in domain method is not being saved

I have a User class with a resetPasswordToken attribute, that is a UUID set when a user tries to reset his password.
On Grails 2.5.6 I had something like this that worked OK:
class UserController {
def forgotPassword(String email)
{
...
def user = User.findByEmail(email)
user.setPasswordToken()
user.save(flush: true()
...
}
}
class User {
...
String resetPasswordToken
static transients = ['passwordToken']
def setPasswordToken()
{
...
this.resetPasswordToken = (java.util.UUID.randomUUID() as String)
}
}
Now I migrated that to GRails 3.3.10 and the resetPasswordToken is NULL on the database after the forgotPassword action is invoked. If I do a println after the user.setPasswordToken() is invoked, I can see the resetPasswordToken is set to an UUID, but is not in the DB. Also checked for errors on the save, and there are no errors.
Strange thing, if I do user.resetPasswordToken = "xxxx" in the controller, the value is saved into the database correctly.
Not sure what is going on with the value set in the setPasswordToken() not being saved into the DB. Any pointers?
See the comment at https://github.com/grails/grails-data-mapping/issues/961#issuecomment-309379214. The issue you are experiencing is one of dirty checking, which changed in GORM 6.1.
Consider this code...
class Person {
String name
String email
void updateName(String newName) {
this.name = newName
}
static constraints = {
email email: true
}
}
That updateName method will not result in the name property being marked as dirty. The following code would result in the name property being marked as dirty:
class Person {
String name
String email
void updateName(String newName) {
setName newName
}
static constraints = {
email email: true
}
}
If you really want to turn on the old way of dirty checking you can do that per the instructions in the comment I linked above but be aware of the performance penalty of doing so. The recommended approach would be to use the setter or to explicitly mark the property as dirty using the markDirty method.
I hope that helps.

List vs Set when adding a GORM domain class

Say I have the following (heavily simplified) GORM domain classes:
class PhoneCall extends Interaction {
Survey survey
}
class Survey {
String campaignCode
Integer clientId
Boolean isDynamic
List interactions
static constraints = {
campaignCode unique: true, nullable: false
clientId nullable: true
isDynamic nullable: true
}
static hasMany = [interactions: Interaction]
}
class Interaction {
String clazz
Instant dateCreated
static constraints = {
}
static mapping = {
tablePerHierarchy false
autoTimestamp false
}
def beforeInsert() {
dateCreated = Instant.now()
}
}
I have the following simple code to set up these classes for a test:
def survey = new Survey(campaignCode: "TEST", isDynamic: true).save(failOnError: true, flush: true)
def phoneCall = new PhoneCall(survey: survey, clazz: PhoneCall.name).save(failOnError: true)
This fails with the following stack trace:
org.springframework.dao.DataIntegrityViolationException: could not insert: [uk.co.nttfundraising.onitfhi.domain.PhoneCall]; SQL [insert into phone_call (id) values (?)]; constraint [survey_id]; nested exception is org.hibernate.exception.ConstraintViolationException: could not insert: [uk.co.nttfundraising.onitfhi.domain.PhoneCall]
at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:643)
at org.springframework.orm.hibernate3.HibernateAccessor.convertHibernateAccessException(HibernateAccessor.java:412)
at org.springframework.orm.hibernate3.HibernateTemplate.doExecute(HibernateTemplate.java:412)
at org.springframework.orm.hibernate3.HibernateTemplate.execute(HibernateTemplate.java:339)
at org.codehaus.groovy.grails.orm.hibernate.metaclass.SavePersistentMethod.performSave(SavePersistentMethod.java:56)
at org.codehaus.groovy.grails.orm.hibernate.metaclass.AbstractSavePersistentMethod.doInvokeInternal(AbstractSavePersistentMethod.java:215)
at org.codehaus.groovy.grails.orm.hibernate.metaclass.AbstractDynamicPersistentMethod.invoke(AbstractDynamicPersistentMethod.java:63)
at org.codehaus.groovy.grails.orm.hibernate.HibernateGormInstanceApi.save(HibernateGormInstanceApi.groovy:196)
However, if I remove the line List interactions from Survey (making interactions into a Set), everything works fine. There are also no problems if I use SortedSet interactions, though the generated database schema doesn't seem to have any notion of order so I'm unsure about that solution. Google mostly suggests not saving the Survey (e.g. this blog post) but I've tried this to no avail.
It's only the List that fails, and it causes the insert into PhoneCall to completely ignore my Survey! What's going on?
A caveat to using a List is that the item you add to it cannot be save()d prior to adding to the List. But more importantly, the proper way to add items to a collection when using a one-to-many association is to use survey.addToInteractions(), See addTo*(). But first, you need a proper association...
class PhoneCall extends Interaction {
static belongsTo = [survey: Survey]
}
By replacing the Survey property with belongsTo, you get a proper bi-directional one-to-many association. Then, you can use/test it like this:
def survey = new Survey(campaignCode: "TEST", isDynamic: true)
survey.addToInteractions(new PhoneCall(survey: survey, clazz: PhoneCall.name))
survey.save(failOnError: true, flush: true)
Notice that the PhoneCall is never explicitly saved, and PhoneCall.survey is not explicitly assigned. All of this gets taken care of when survey.save() is called.
Once saved, someSurvey.interactions[index].survey will reference the someSurvey.

Grails one to one on two domain object

I want to add a child to two different parent class, like that:
First:
class Member {
Profile profile
Member() {
this.profile = new Profile()
}
static mapping = {
profile cascade: 'all-delete-orphan'
}
}
Second:
class Team {
Profile profile
Team() {
this.profile = new Profile()
}
static mapping = {
profile cascade: 'all-delete-orphan'
}
}
Thw child is simply define like that
class Profile() {
}
The probleme is when I save the parent, it dosent save the child:
Member member = new Member().save(flush: true, failOnError: true)
assert !member.hasErrors()
assert member.profile
assert !member.profile.hasErrors()
assert member.profile.id //FAIL
What do I do wrong? Is there a better way to do it?
UPDATE:
I found this
Saving associated domain classes in Grails
It seem that 'belong to' is needed for that kind of behavior. But Why the 'cascade: 'all-delete-orphan' doesn't force this ? Because I can't use 'belong to' in that specific case
I copied your example (with slight modification to change group table name to a non-reserved word) and the cascades are working properly using grails 2.2.1. Both Member and Group cascaded their saves to the newly created Profiles.
Assuming your classes are more complicated than this, you might have an error elsewhere in your class (eg cascade behavior described in constraints instead of mapping, etc).
I found a nice solution. The best was to define both parent in the child but nullable. Like that:
class Profile() {
static belongsTo = [member: Member, team: Team]
static constraints = {
member nullable: true
team nullable: true
}
}
This way, the cascade behavior work just fine !

GORM how to ensure uniqueness of related objects property

I'm trying to get my head around GORM and relational mapping. The relationships are working fine but there is one problem. I can't seem too ensure that every MailAddress added to MailingList has a unique address. What would be the must efficient way to do this?
Note: There is no unique constraint on MailAddress.address. Identical addresses can exist in the same table.
class MailAddress {
String name
String email
static belongsTo = MailingList
static constraints = {
name blank:true
email email:true, blank:false
}
}
class MailingList {
String name
static hasMany = [addresses:MailAddress]
static mapping = {
addresses cascade: 'all-delete-orphan'
}
static constraints = {
name blank:false
}
}
As mentioned in the comments by #ibaralf the answer is a custom validator.
The MailingList class needed to validate if all addresses (MailAddress) have a unique e-mailaddress.
I added this constraint to the MailingList class and it worked.
static constraints = {
name blank:false
addresses(validator: {
if (!it) {
// validates to TRUE if the collection is empty
// prevents NULL exception
return true
}
// Grab a collection with all e-mailaddresses in the list
def addressCollection = it*.email
// Compare to a collection with only unique addresses
return addressCollection == addressCollection.unique()
})
}
More info can be found here http://grails.org/doc/2.2.0/ref/Constraints/validator.html
There is a unique constraint you can add:
static constraints = {
name blank:true
email email:true, blank:false, unique: true
}
=> put the unique constraint on the email variable (unique: true). This would prevent identical email addresses to be saved in the table.

Why is GORM performing cascading delete, even if I omit the belongsTo keyword?

I have the following domain classes:
Holiday:
class Holiday {
String justification
User user
//static belongsTo = User
static constraints = {
}
}
User:
class User {
String login
String password
static hasMany = [ holidays : Holiday ]
static constraints = {
}
}
I have created a one-many relationships between Holiday and User. Note that I haven't included belongsTo on the Holiday class. Now I have written the following integration test:
void testWithoutBelongsTo() {
def user1 = new User(login:"anto", password:"secret")
user1.save()
def holiday1 = new Holiday(justification:"went to trip")
holiday1.save()
user1.addToHolidays(holiday1)
assertEquals 1, User.get(user1.id).holidays.size()
user1.delete()
assertFalse User.exists(user1.id)
assertFalse Holiday.exists(holiday1.id)
}
Clearly in the above test case, I'm deleting only the user1 instance, but when I run with assert statements I can see that GORM have implicitly deleted holiday1, too. And my Test cases have PASSED! How this can happen, even though I haven't given belongsTo keyword in Holiday class?
I'm using Grails version 1.3.7.
holiday1 was never saved as it does not validate: property user is neither set nor nullable.
Here is how your code in Holiday.groovy should look like:
class Holiday {
String justification
User user
//static belongsTo = User
static constraints = {
user(nullable: true)
}
}
And your test, with property user set properly in holiday1:
void testWithoutBelongsTo()
{
def user1 = new User(login:"anto", password:"secret")
user1.save(failOnError: true)
def holiday1 = new Holiday(justification:"went to trip",
user: user1) // Set user properly
holiday1.save(failOnError: true)
user1.addToHolidays(holiday1)
assert 1, User.get(user1.id).holidays.size()
holiday1.user = null // Unset user as otherwise your DB
// won't be happy (foreign key missing)
user1.delete()
assert ! User.exists(user1.id)
assert Holiday.exists(holiday1.id)
}
In order to eliminate validation errors quickly in your tests, always use save(failOnError: true). It will throw an exception if your object do not validate.
You should save holiday after adding it to the user.

Resources