Mixed relationship in grails application - grails

I have a domain class which is called Event. Event has many registrations, which are users with account on site or people without it. I want registered user to be able to track their events, so Event-User relationship should be bidirectional (user is created by Spring Security). Still there are registrations which are not connected with user accounts. It's important since event is kind of a live competition with ranking, which is updated by event owner when event is over. How to achieve this?

this should work:
class User {
static hasMany = [events:Event]
}
class Event {
User user
static constraints = {
user(nullable:true)
}
}
this way you can get the user from the event or all events for the user.
EDIT:
class User {
static hasMany = [events:Event]
}
class Event {
static hasMany = [users:User]
}
add Users to the Event with
event.addToUsers(user)
remove Users from Event with
event.removeFromUsers(user)
get all Users from Event
event.users
I'm not quite sure how you handle the not registered Users. Do you have no information about this users at all (no username etc ?)
I would suggest you add a flag in the User class "Boolean registered".
So you can create a User object for not registered Users also.
cheers manu

Related

How to create multiple one-to-many relationships between domain classes

I have an Account class that has many managers (User class) & reps (User class).
class Account {
static hasMany = { reps: User, managers: User }
}
Then, I have a User class which belongs to an account. User is differentiated into manager or rep using a Role Enum inside User class.
class User {
static belongsTo = { account: Account }
Role role
}
The problem is, when I create a user of any type and save it, Grails ends up adding that user to both managers and reps sets in the account object.
I realize I need to use mapped_by here, however I don't understand how it should be used. The manager and rep is differentiated by a Role Enum inside User class.
I have looked at several stackoverflow questions #1, #2 however most of the times, problems get solved with other relationships.
I specifically want to use 2 one-to-many relationships between the Account and User class.
Edit : Code to initialize a rep:
def addRep(manager) {
User rep = new User( account: manager.account,
role: Role.REP)
rep.save(flush: true, failOnError: true)
}
You need to specify which association is to be used :
def addRep(manager) {
User rep = new User(role: Role.REP)
manager.account.addToReps(rep) // This will do the bi-association
rep.save(flush: true, failOnError: true)
}

Domain modeling using GORM in grails, how to represent an association

I am learning grails and I came up with a use case. In my use case, a product has many users and each user can have many roles.
Here is my product class:
class Product {
String name
String description
String vision
Date startDate
Date endDate
static hasMany = [users : User, contributors : User, watchers : User, approvers : User]
static belongsTo = User
static constraints = {
}
}
Here is my User class:
class User {
static constraints = {
}
String fullName
String email
static hasMany = [roles : Roles, products : Product]
}
Here is the Roles Enum:
public enum Roles {
PRODUCTOWNER ('ProductOwner'),
APPROVER ('Approver'),
CONTRIBUTOR ('Contributor'),
WATCHER ('Watcher')
}
My question is specifically about the association between Product and User. I want to represent the fact that a product can have many users in different roles. Also, each user can be part of multiple products with a different role in each product. Is this the right way to represent this relationship? Also, I should be able to remove and add users to products and vice versa. What this also means is that, users can keep moving between roles and can move in and out of products. In this scenario, I probably don't want cascades to happen. How do I prevent automatic cascades from happening to CRUD operations for this relationship?
Thanks.
I think rather than having roles and products in User.groovy, it will be better if you create a separate domain like UserProductRole. As you said user will have different role in different products then creating a separate domain makes more sense in business usecase and also doing queries
class UserProductRole{
Role role
static belongsTo = [user:User,product:Product]
static constraints = {
user (unique:['product','role']
}
}
You can create composite key but I generally dont perfer it because it makes querying bit difficult.
And now you need to change hasMany in User and Product like following
[userProducts:UserProductRole] rather then having users or products

Unidirectional Many To One mapping with cascade

Is it possible to map the following with GORM?
I want to get rid off all associated events when I delete a person.
Person object should not have a link to events.( I want to avoid using hasMany on Person domain)
class Person {
String username
}
class Event {
String description
static belongsTo = [person:Person]
}
I'm getting now a 'Referential integrity constraint violation' when doing person.delete() because events are not removed before deleting person.
I don't think that is possible without using hasMany (speaking of which, why do you want to avoid that anyway?)
This SO Question states:
Hibernate only cascades along the defined associations. If A knows
nothing about Bs, nothing you do with A will affect Bs.
Use static hasMany and bam, problem fixed.
Edit:
The only way I think you could achieve this is using beforeDelete on the Person class to delete all the associated Events, i.e.
class Person {
def beforeDelete() {
def events = Event.findAllByPerson(this)
for (e in events) {
e.delete()
}
}
}
See the documentation on Events and Auto Timestamping for more info on that.
The above will not work
Why not define a no reference mapping:
class Person {
String username
static hasMany=[Events]
}
This way there is no actual bindings of events to person but a person can have many events

How to track changes to a GORM-managed collection?

Imagine the following domain classes:
class User {
static hasMany = [ roles: Role ]
List<Role> roles
User() {
roles = new ObservableList<>()
roles.addPropertyChangeListener({ event -> ... } as PropertyChangeListener)
}
}
class Role {
String name
}
My intention is to perform some actions each time an element is added to / removed from the roles collection; this includes 1) populating roles from database, and 2) further manipulation by a user. If a User instance is just created (and not persisted), I am able to catch notifications. After the call to save() method, the ObservableList is still intact. But later, for example, when I perform a User.find...() from a controller, the original ObservableList is replaced by Hibernate's org.hibernate.collection.PersistentList, and obviously there are no notifications anymore.
How can I implement a change-aware list that would be compatible with GORM?

Spring Security in Grails providing authorization

A suggestion here would be deeply appreciated.
We use Spring Security domain object security to lock down access to specific objects in our Grails application. We have extended permissions as well. We use normal #PreAuthorize annotation to control access to the domain objects depending up the privileges assigned to a specific user.
In this architecture, I want for one user to be able to invite a second user to join a group. To do this, the second user receives an invitation from the first user to join a group with specific privileges. The second user can choose to accept or reject the invitation.
Given that the domain object belongs to the first user, not the second user, how could I grant access to the second user to the domain object with the privileges offered?
I have tried to hack a solution using #PreAuthorize("permitAll") on a service method just to see if that would work. While not ask secure as granting an object the privilege to set privileges to a domain object, it at least would get me going. But no joy, I continue to get "accessDenied for class" errors, presumably because the current user is the second user, not the domain object owner.
Do I need to work through the SpEL suggestion here? I barely understand how the bean resolver works unfortunately.
Edit: Even when I verify the invitation is valid at invocation Spring ACL throws an exception when the first ACE for the new user is attempted to be inserted. The exception occurs in void setPermissions(ObjectIdentity oid, recipient, Collection permissions) at the insertAce call:
void setPermissions(ObjectIdentity oid, recipient, Collection permissions) {
Sid sid = createSid(recipient)
MutableAcl acl
try {
acl = aclService.readAclById(oid)
}
catch (NotFoundException e) {
acl = aclService.createAcl(oid)
}
int deleted = 0
acl.entries.eachWithIndex { AccessControlEntry ace, int i ->
if (ace.sid == sid) {
acl.deleteAce(i-deleted)
deleted++
}
}
permissions.each {
acl.insertAce(acl.entries.size(), it, sid, true)
}
aclService.updateAcl acl
}
I assume that the access is denied because the current user (who did not exist when the invitation was issued) does not have the rights to set permissions on an object owned by another user.
You can use your Role class not only for generic roles (Admin,User, etc.), but for application specific ones as well. Simply allow the user to create a Role for a resource and then allow their invitees to be granted that role. Spring Security comes with a handy ifAnyGranted() method, which accepts a comma-delimited String of role names. At a resource entry-point simply ensure that a particular role is granted:
class Conversation{
Role role
}
class ConversationController{
def enterConversation(){
// obtain conversation instance
if(!SpringSecurityUtils.ifAnyGranted(conversationInstance.role.authority){response.sendError(401)}
}
}
The answer turned out to be using RunAs. For Grails, I did this as follows:
Create a Role specific for allowing invited users to act as an administrator in order to access a protected object. In Bootstrap, ensure this role is loaded into the SecRole domain:
def invitedRole = SecRole.findByAuthority('ROLE_RUN_AS_INVITED_USER')
if (!invitedRole) {
invitedRole = new SecRole()
invitedRole.authority = "ROLE_RUN_AS_INVITED_USER"
invitedRole.save(failOnError:true, flush:true)
}
Ensure in config.groovy that the role can change the target object:grails.plugins.springsecurity.acl.authority.changeAclDetails = 'ROLE_RUN_AS_INVITED_USER'
Enable RunAs in config.groovy
grails.plugins.springsecurity.useRunAs = true
grails.plugins.springsecurity.runAs.key = [key]
annotate the service method
#PreAuthorize([check that this is indeed an invited user])
#Secured(['ROLE_USER', 'RUN_AS_INVITED_USER'])
and it all works. The trick was to make the ROLE_RUN_AS_INVITED_USER able to change acl's.
Reference:
http://static.springsource.org/spring-security/site/docs/3.0.x/reference/runas.html
http://grails-plugins.github.io/grails-spring-security-acl/docs/manual/guide/2.%20Usage.html#2.5%20Run-As%20Authentication%20Replacement

Resources