Grails many to many relationship concern - grails

I have an application where Owner objects can have many Account objects and Account objects can have many Owner objects. I thought of modelling this with a many to many relationship but then I realized that I may also need to create Account objects where the Owner cannot be immediately determined or may be created in the future AFTER the account has already been created. Therefore I do not believe I can use the Grails many to many relationship(I think). So, I was just going to have a property in the Owner class that was a list of Account ids(this would be the Owner's Accounts references) and a property in the Account class which is a list of Owner ids(this would be the Account's Owners references). Any better ideas? I feel this is too old fashioned and is akin to the C++ pointers way of doing it.

You can create a third domain to resolve the relation like this
class Owner{
...
Set<Object> getObjects() {
OwnerObject.findAllByOwner(this).collect { it.object } as Set // or replace with optimized criteria
}
...
}
class Object{
...
Set<Owner> getOwners() {
OwnerObject.findAllByObject(this).collect { it.owner } as Set // or replace with optimized criteria
}
...
}
class OwnerObject implements Serializable {
Owner owner
Object object
boolean equals(other) {
if (!(other instanceof OwnerObject)) {
return false
}
other.owner?.id == owner?.id && other.object?.id == object?.id
}
static OwnerObject get(long ownerId, long objectId) {
find 'from OwnerObject where owner.id=:ownerId and object.id=:objectId',
[ownerId: ownerId, objectId: objectId]
}
static OwnerObject create(Owner owner, Object object, boolean flush = false) {
new OwnerObject(owner: owner, object: object).save(flush: flush, insert: true)
}
static boolean remove(Owner owner, Object object, boolean flush = false) {
OwnerObject instance = OwnerObject.findByOwnerAndObject(owner, object)
if (!instance) {
return false
}
instance.delete(flush: flush)
true
}
static void removeAll(Owner owner) {
executeUpdate 'DELETE FROM OwnerObject WHERE owner=:owner', [owner: owner]
}
static void removeAll(Object Object) {
executeUpdate 'DELETE FROM OwnerObject WHERE object=:object', [object: object]
}
static mapping = {
id composite: ['object', 'owner']
version false
}
}

Frankly, I have used m2m relation only once in my 6+ years "Grails-career". Instead a combination of o2m and dynamic queries is easier to implement and maintain and usually more performant.

Related

Issues with Grails with cascade deletion with multiple belongTo listed in classes

I am trying to develop a restful server with Grails but I am having trouble getting the domain classes correct. I am using Grails 2.4.2.
I will have a Story class and a User class. The Story class will have a single "author" which is of type User. It will also have many "editors" and many "viewers" which will be a collection of Users. I have tried many different ways of describing this in Groovy for GORM to translate to mySQL but different problems arose for each one. This following scenario seems to be the closest I've gotten to a good solution, but I've hit a snag. If I delete a User instance that is also in one of the Story instance's editors collection, I get
Cannot delete or update a parent row: a foreign key constraint fails (`test`.`editor`, CONSTRAINT `FK_jhrniu8kj891sx2i73mgtgc0v` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)); nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`test`.`editor`, CONSTRAINT `FK_jhrniu8kj891sx2i73mgtgc0v` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`))
How can I get this to work? I thought the belongsTo in Editor.groovy pointing to User would cascade deletion. I am open to a better way of setting up the domain classes as I'm relatively new to Grails and GORM.
Here's the code:
Story.groovy
class Story {
String title
User author
static belongsTo = [User]
static hasMany = [viewers: Viewer, editors: Editor]
static constraints = {
viewers nullable: true
editors nullable: true
}
}
User.groovy
class User {
String username
static hasMany = [stories : Story]
static constraints = {
username nullable:false
}
}
Editor.groovy
class Editor {
User user
static belongsTo = [story: Story, user: User]
static constraints = {
}
}
Test code:
void "Testing removal of a user and editors"() {
given: "New Users, New Story"
def joe = new User(username:"joe").save(failOnError: true)
def jane = new User(username:"jane").save(failOnError: true)
def bill = new User(username:"bill").save(failOnError: true)
def story1 = new Story(author: joe, title:"Story1 Title").save(failOnError: true)
when: "let's add some editors and then delete a user"
story1.addToEditors(new Editor(user: jane))
story1.addToEditors(new Editor(user: bill))
story1.save(failOnError: true, flush: true)
assert story1.editors.size() == 2
println("before bill deleted " + story1.dump())
bill.delete(flush: true) //<-ERROR occurs here
then: "Should only be one editor now"
println("after bill deleted:" + story1.dump())
story1.errors.errorCount == 0
story1.editors.size() == 1
}
I've tried adding a beforeDelete closure in User, but I get the same error.
def beforeDelete = {
Editor.findAllByUser(it)*.delete()
}
The belongsTo in your Story class is BelongsTo instead of belongsTo. It needs to be belongsTo.
I don't know if this is the best way or not. A friend mentioned to organize the domain classes a bit differently and to implement some getters and setters. It reminded me of Roles used in Spring Security Core so I looked at the UserRole class from that module and noticed they do not use belongsTo or hasMany in the relationships between User, Role, and UserRole. In fact, I did a test where I created a UserRole and then tried to delete the User without removing UserRole and I got the same a foreign key constraint fails error. In this direction I created the following code that passes the integration tests and I don't get the contraint fails error. It is a little bit more work. Maybe when I understand GORM better, I can simplify this. (I borrowed the removeAll methods from Spring Security Core)
class Story {
def roleService
String title
static constraints = {
}
def getEditors() {
roleService.findAllByStory(this, Role.EDITOR)
}
def addToEditors(User u) {
roleService.addToRole(u, this, Role.EDITOR)
}
def removeEditor(User u){
roleService.removeRole(u, this, Role.EDITOR)
}
def beforeDelete() {
def story = this
Story.withNewSession {
Editor.removeAll(story, true)
Viewer.removeAll(story, true)
}
}
//TODO add getters and setters for Viewers and Authors
}
User.groovy
class User {
String username
static constraints = {
username nullable:false
}
}
Role.groovy
class Role {
User user
Story story
static constraints = {
}
static void removeAll(Story s, boolean flush = false) {
if (s == null) return
Role.where {
story == Story.load(s.id)
}.deleteAll()
if (flush) { Role.withSession { it.flush() } }
}
//TODO implement removeAll(User u, boolean flush=false)
static final int EDITOR = 1
static final int VIEWER = 2
static final int AUTHOR = 3
}
Editor.groovy
class Editor extends Role{
static constraints = {
}
}
Viewer.groovy
class Viewer extends Role{
static constraints = {
}
}
RoleService.groovy
#Transactional
class RoleService {
public findAllByStory(Story s, int role){
switch (role ){
case Role.EDITOR:
return Editor.findAllByStory(s).collect {e -> e.user}
break;
case Role.VIEWER:
return Viewer.findAllByStory(s).collect {v-> v.user}
break;
}
}
public addToRole(User u, Story s, int role){
switch (role ){
case Role.EDITOR:
new Editor(user: u, story: s).save()
break;
case Role.VIEWER:
new Viewer(user: u, story: s).save()
break;
}
}
public removeRole(User u, Story s, int role){
if(u == null || s == null) {return false}
switch (role ){
case Role.EDITOR:
Editor.where {
user.id == u.id &&
story.id == s.id
}.deleteAll()
break;
case Role.VIEWER:
Viewer.where {
user.id == u.id &&
story.id == s.id
}.deleteAll()
break;
}
}

Grails many to many using a third 'join' class

I read that a m:m relationship often means there is a third class that isn't yet required. So I have m:m on User and Project, and I created a third domain class, ProjectMembership
The three domains are as follows (minimized for illustration purposes):
User
class User {
String name
static hasMany = [projectMemberships : ProjectMembership]
}
Project Membership
class ProjectMembership {
static constraints = {
}
static belongsTo = [user:User, project:Project]
}
Project:
class Project {
String name
static hasMany = [projectMemberships : ProjectMembership]
static constraints = {
}
}
If I have the ID of the user, how can I get a list of Project objects that they are assigned to?
There are a handful of ways - here are a couple:
def user = User.get(userId)
ProjectMembership.findAllByUser(user).collect { it.project }
or to avoid the query for the User:
ProjectMembership.withCriteria {
user {
eq('id', userId)
}
}.collect { it.project }
Be wary of queries that'll return large result sets - you'll end up with a huge in-memory list of project objects.

How to add a property to a map?

I have a domain class:
class Person {
String name
Boolean likesGrails
Boolean isSmart
}
and want to pre process the data (create a new property friend) before passing it to a view (which will use friend to decide stuff):
def people = Person.list()
people.each {
it.friend = likesGrails && isSmart
}
How do i add this friend property? The code above doesn't work (it complains that it.friend doesn't exist).
You should just be able to add:
static transients = [ 'friend' ]
public boolean isFriend() {
likesGrails && isSmart
}
To your domain class, then access person.friend in your view
You can add a transient if you don't want it to be stored in your database.
class Person {
String name
Boolean likesGrails
Boolean isSmart
Boolean friend
static transients = [ 'friend' ]
}
but you can't add random properties on the fly to domain classes.

grails / gorm supporting different parent entity in 1 - many associations

I have the following "top-level" (parent) domain entities:
Customer
Company
Contact
And the following child entity:
Address
There is one to many relationship between each of the top level domain entities:
Customer -> Address
Company -> Address
Contact -> Address
i.e. a customer, company or contact can have one or more addresses.
Unfortunately I do not know how to model this in grails / gorm. It seems I can only define one parent or belongsTo declaration in address i.e. I was not able to declare Address using:
Address {
Customer parent //??
Company parent //??
Contact parent //??
}
Can someone tell me if I am missing something or if it's possible to define this type of relationship in a supported way?
Thanks,
cowper
You should be able to use the array version of belongsTo as Tim pointed out:
Address {
static belongsTo = [Customer, Company, Contact]
}
If entities can share a common address may change the way you configure deletes.
Another option is to have those three classes inherit the property from a superclass, but whether or not that makes sense in your case, I don't know (it kind of doesn't look like it).
In our application we have several entities that need addresses. But we've chosen to model them in a many-to-many relationship.
Address looks like this
class Address {
// typical address properties
Set<Company> getCompanies() {
CompanyAddress.findAllByAddress(this).collect { it.company } as Set
}
static constraints = {
// typical constraints
}
}
And for each "parent" we provide a getter. You can see getCompanies() in the code above. If you're only every going to have 1 company per address, then simply have that getter return the 1 company instead of a Set. The inverse is true inside Company, we have a getAddresses().
Company Address, for example, looks like this...
class CompanyAddress implements Serializable{
Address address
Company company
boolean equals(other) {
if (this.is(other)){
return true
}
if (!(other instanceof CompanyAddress)) {
return false
}
other.address?.id == address?.id &&
other.company?.id == company?.id
}
int hashCode() {
def builder = new HashCodeBuilder()
if (address) builder.append(address.id)
if (company) builder.append(company.id)
builder.toHashCode()
}
static CompanyAddress get(long companyId, long addressId) {
find 'from CompanyAddress where address.id=:addressId and company.id=:companyId',
[addressId: addressId, companyId: companyId]
}
static CompanyAddress create(Company company, Address address, boolean flush = false) {
new CompanyAddress(address: address, company: company).save(flush: flush, insert: true)
}
static boolean remove(Company company, Address address, boolean flush = false) {
CompanyAddress instance = CompanyAddress.findByAddressAndCompany(address, company)
instance ? instance.delete(flush: flush) : false
}
static void removeAll(Address address) {
executeUpdate 'DELETE FROM CompanyAddress WHERE address=:address', [address: address]
}
static void removeAll(Company company) {
executeUpdate 'DELETE FROM CompanyAddress WHERE company=:company', [company: company]
}
static mapping = {
id composite: ['address', 'company']
version false
}
}

How To Use A Unique Constraint On Multiple Columns Instead Of Creating A Composite Key

The new system I'm starting has several domains that are just join tables for many-to-many relations. They are all like this example for Spring Security Core. In the case, of Spring Security, a "legacy" database is being mapped, so using a composit key is necessary.
My question is if it's possible to put a unique constraint on the combination of the fields instead of having to use a composite key and thus having to implement the get and other functions. I'd be quite happy to use the id as the key, but I do need to ensure the records are unique.
In other words, how could I add a unique constraint on secUser + secRole instead of creating a composite key in the following domain?
class SecUserSecRole implements Serializable {
SecUser secUser
SecRole secRole
boolean equals(other) {
if (!(other instanceof SecUserSecRole)) {
return false
}
other.secUser?.id == secUser?.id &&
other.secRole?.id == secRole?.id
}
int hashCode() {
def builder = new HashCodeBuilder()
if (secUser) builder.append(secUser.id)
if (secRole) builder.append(secRole.id)
builder.toHashCode()
}
static SecUserSecRole get(long secUserId, long secRoleId) {
find 'from SecUserSecRole where secUser.id=:secUserId and secRole.id=:secRoleId',
[secUserId: secUserId, secRoleId: secRoleId]
}
static SecUserSecRole create(SecUser secUser, SecRole secRole, boolean flush = false) {
new SecUserSecRole(secUser: secUser, secRole: secRole).save(flush: flush, insert: true)
}
static boolean remove(SecUser secUser, SecRole secRole, boolean flush = false) {
SecUserSecRole instance = SecUserSecRole.findBySecUserAndSecRole(secUser, secRole)
if (!instance) {
return false
}
instance.delete(flush: flush)
true
}
static void removeAll(SecUser secUser) {
executeUpdate 'DELETE FROM SecUserSecRole WHERE secUser=:secUser', [secUser: secUser]
}
static void removeAll(SecRole secRole) {
executeUpdate 'DELETE FROM SecUserSecRole WHERE secRole=:secRole', [secRole: secRole]
}
static mapping = {
id composite: ['secRole', 'secUser']
version false
}
}
You could do that if you're allowed to add a new column that'll be the real primary key, e.g. an autoincrement or a sequence-based column. Then adding the unique constraint would be simple:
class SecUserSecRole {
SecUser secUser
SecRole secRole
static constraints = {
secUser(unique:'secRole')
}
}
But I doubt that's an option :)
Note that Grails 2.0 has support for Hibernate Bags, which addresses the collection performance problems that this SecUserSecRole approach works around.

Resources