Grails set mapping table of 3 classes - grails

I am new to grails. I have 3 classes as follws
class Cycle
{
int year
int quarter
}
class User
{
String username, password
}
class Role
{
String roleName
}
The User and Role tables are independent and they stores details of all employees and Available roles respectively.
An employee can have many roles in each cycle. so i want a mapping table like
to find what all roles belongs to an employee in a particular cycle.
How do i set proper relation between these classes to solve this scenario so that if i have a cyle and a user , i can find out what all roles the user has in that cycle
also if i have a user i can find which all cycles he involved an his roles in each of these cycles

Following the comment.
You should create a table between User and Cycle
class UserCycle
{
User user
Cycle cycle
static hasMany = [roles: Role]
}
Role class should look like this:
class Role
{
String roleName
UserCycle userCycle
}
and in code You can refer to that like:
UserCycle uc = ...
def roles = uc.roles.list(params) //list of roles
def cycle = uc.cycle
def user = uc.user
to add
uc.addToRoles(/*Your role*/)
to remove
uc.removeFromRoles(/*Your role*/)

You'll need to add a join table (domain class) that stores the mapping between users and roles in a particular cycle. Here's an example of such a class with some helper methods that may be useful
class UserRole implements Serializable {
User user
Role role
Cycle cycle
static UserRole create(User user, Role role, Cycle cycle, boolean flush = false) {
new UserRole(user: user, role: role, cycle: cycle).save(flush: flush, insert: true)
}
static boolean remove(User user, Role role, Cycle cycle, boolean flush = false) {
UserRole instance = UserRole.findByUserAndRoleAndCycle(user, role, cycle)
if (!instance) {
return false
}
instance.delete(flush: flush)
true
}
static void removeAll(User user) {
executeUpdate 'DELETE FROM UserRole WHERE user=:user', [user: user]
}
static void removeAll(Role role) {
executeUpdate 'DELETE FROM UserRole WHERE role=:role', [role: role]
}
static void removeAll(Cycle cycle) {
executeUpdate 'DELETE FROM UserRole WHERE cycle=:cycle', [cycle: cycle]
}
static mapping = {
id composite: ['role', 'user', 'cycle']
version false
}
}

Related

How to implement role authorization with custom database?

I have an application which requires role authorization using custom database. The database is set up with a tblUsers table that has a reference to a tblRoles table. The users are also already assigned to their roles.
I also want to use the [Authorize(Role = "RoleName")] attribute on each action to check if an authenticated user is assigned to "RoleName" in the database. I'm having a lot of trouble figuring out where I need to make a modification to the [Authorize] attribute so it behaves that way. I just want to see if a username has a role, I won't have a page to manage roles in the database.
I have tried implementing custom storage providers for ASP.NET Core Identity, but it's starting to look like this is not what I need because I'm not gonna be managing roles within the application, and I can't tell how it affects the behavior of [Authorize] attribute.
Also, it's likely that I have a false assumption in my understanding on how the [Authorize] attribute even works. If you notice it, I would appreciate if you could point it out.
I had a similar problem when my client asked for granular permissions for each role. I couldn't find a way to modify the Authorize attribute but was able to implement the solution with a custom attribute. But it depends on one thing i.e can you get the userId of the calling user? I used cookie authentication so I just include the userId in my claims when someone logs in so when a request comes I can always get it from there. I think the built-in Session logic in asp.net might get the job done too, I can't say for sure though. Anyways the logic for custom authorization goes like this:
Load users and roles from database to cache on startup. If you haven't set up a cache in your program (and don't want to) you can simply make your own for this purpose by making a UserRoleCache class with 2 static lists in it. Also there are several ways of loading data from db on startup but I found it easy to do that directly in Program.cs as you'll see below.
Define your custom attribute to check if the calling user has the required role by iterating over lists in cache and return 403 if not.
Modify your Program class like:
public class Program
{
public static async Task Main(string[] args)
{
IWebHost webHost = CreateWebHostBuilder(args).Build();
using (var scope = webHost.Services.CreateScope())
{
//Get the DbContext instance. Replace MyDbContext with the
//actual name of the context in your program
var context = scope.ServiceProvider.GetRequiredService<MyDbContext>();
List<User> users = await context.User.ToListAsync();
List<Role> roles = await context.Role.ToListAsync();
//You may make getters and setters, this is just to give you an idea
UserRoleCache.users = users;
UserRoleCache.roles = roles;
}
webHost.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
Then comes the logic for checking if user has a role. Notice I've used an array of roles because sometimes you'll want to allow access to multiple roles.
public class RoleRequirementFilter : IAuthorizationFilter
{
private readonly string[] _roles;
public PermissionRequirementFilter(string[] roles)
{
_roles = roles;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
bool hasRole = false;
//Assuming there's a way you can get the userId
var userId = GetUserId();
User user = UserRoleCache.users.FirstOrDefault(x => x.Id == userId);
//Where roleType is the name of the role like Admin, Manager etc
List<Role> roles = UserRoleCache.roles.FindAll(x => _roles.Contains(x.RoleType))
foreach(var role in roles)
{
if(user.RoleId == role.Id)
{
hasRole = true;
break;
}
}
if (!hasRole)
context.Result = new StatusCodeResult(403);
}
}
Finally make the Role attribute
public class RoleAttribute : TypeFilterAttribute
{
public RoleAttribute(params string[] roles) : base(typeof(RoleRequirementFilter))
{
Arguments = new object[] { roles };
}
}
Now you can use the Role attribute in your controllers:
public class SampleController : ControllerBase
{
[HttpGet]
[Role("Admin", "Manager")]
public async Task<ActionResult> Get()
{
}
[HttpPost]
[Role("Admin")]
public async Task<ActionResult> Post()
{
}
}

Grails auto update related table

I have following domain classes:
class UserGroup {
String name
String description
static hasMany = [users:User]
}
class User {
String name
static hasMany = [userGroups:UserGroup]
static belongsTo = UserGroup
}
Here User and UserGroup have many to many relationaship.but when I save user to usergroup,the user is not updated with that usergroup.Is there a mapping I can use to do that?I am using following code to add users to group:
static def addUserToGroups(def user){
def userGroups = UserGroup.findAll()
userGroups.each {
if(/** condition **/){
it.addToUsers(user)
it.save(flush:true,failOnError:true)
}
}
}

How does one setup cascading save, update, and delete operations for Multiple Domain Classes that are each composed of a Single Shared Domain Class?

Given the following Domain Classes:
The shared entity (C) can't use belongsTo to set up cascading for both A and B since an instance will only ever belong to one or the other.
What is the best way to model this with Gorm?
class EntityA {
static hasOne = [enitityC: SharedEntityC]
}
class EntityB {
static hasOne = [enitityC: SharedEntityC]
}
class SharedEntityC {
String foo
// Can't use belongsTo to set up cascading
}
I've looked to the following:
http://grails.org/doc/2.0.x/guide/single.html#cascades
http://grails.org/doc/2.0.x/guide/single.html#customCascadeBehaviour
I've tried the Strategy Pattern:
interface Shareable {
Shared sharedEntity
}
class EntityA implements Shareable {
static hasOne = [sharedEntity: Shared]
}
abstract class Shared {
static belongsTo = [shareable: Shareable]
}
class SharedEntityC extends Shared {
String foo
}
But this is un-groovy according to some, and Gorm seems to only care about concrete classes.
I've tried interceptors:
class EntityA {
SharedEntityC enitityC
def afterDelete {
this.entityC.delete() // Results in readonly session error
}
}
class EntityA {
SharedEntityC enitityC
def beforeDelete {
this.entityC.delete() // Results in fk constraint violation
}
}
Another option would be:
class EntityASharedEntityC {
EntityA entityA
SharedEntityC entityC
...
// a bunch of static methods for managing the relationship
...
}
class EntityBSharedEntityC {
EntityB entityB
SharedEntityC entityC
...
// a bunch of static methods for managing the relationship
...
}
...
// Plus a new class for each entity containing SharedEntityC.
...
But this seems like a long way around defining a straightforward composite relationship.
Before the answer:
class EntityA {
SharedEntityC enitityC
def afterDelete() {
this.deleteSharedEntityC()
}
void deleteSharedEntityC() {
if(this.sharedEntityC) {
this.sharedEntityC.beforeDelete() // It has some cleanup to do itself
SharedEntityC.executeUpdate('delete SharedEntityC where id=:id',
[id: this.sharedEntityC.id]) // Go around Gorm
}
}
}
Even though I arrived at a solution I can live with, I wonder if these classes can be modeled in a way that wouldn't require me to bend Gorm in this way.
Any suggestions are welcome and appreciated... :-)
After the answer:
class EntityA {
SharedEntityC entityC
static mapping = {
entityC cascade: 'all'
}
}
class EntityB {
SharedEntityC entityC
static mapping = {
entityC cascade: 'all'
}
}
class SharedEntityC {
String foo
// Leave out belongsTo
}
Much, much better...
On the entity classes did you try specifying the cascade:
static mapping = {
enitityC cascade: 'all'
}

Grails many to many relationship migration

Hello I have two domain classes as following
class Users {
String password
String firstName
String lastName
String emailAddress
String username
Company company
.....
static hasMany = [projects:Projects];
}
Another class
class Projects {
String projectName
String description
Users projectLead
Date dateCreated
Date lastUpdated
static belongsTo = Users
}
These classes obviously has one to many relationship but now I want to change it to many to many relationship by adding "ProjectMembership" class but the problem I have is that my application has already gone into production and there are people who are already using the app. In such a case they already have one user->many projects in the the db. In such a case how can I migrate this existing data and change my prod app to have m2m relationship which will looks like following.
class Users {
String password
String firstName
String lastName
String emailAddress
String username
Company company
.....
static hasMany = [projectMemberships:ProjectMemberships];
}
Another class
class Projects {
String projectName
String description
Users projectLead
Date dateCreated
Date lastUpdated
static hasMany = [projectMemberships:ProjectMemberships];
}
and
class ProjectMemberships{
Users u
Projects p
}
This is best done with a migration tool like Liquibase, and the http://grails.org/plugin/database-migration plugin is probably your best be in Grails since it uses Liquibase and is tightly integrated with GORM. But this one's easy enough to do by hand.
I wouldn't use hasMany since you can easily manage everything from the ProjectMemberships class, so your Users and Projects classes would be
class Users {
String password
String firstName
String lastName
String emailAddress
String username
Company company
.....
}
and
class Projects {
String projectName
String description
Date dateCreated
Date lastUpdated
}
I'd go with a ProjectMemberships class that uses a composite key, which requires that it implement Serializable and have a good hashCode and equals:
import org.apache.commons.lang.builder.HashCodeBuilder
class ProjectMemberships implements Serializable {
Users u
Projects p
boolean equals(other) {
if (!(other instanceof ProjectMemberships)) {
return false
}
other.u?.id == u?.id && other.p?.id == p?.id
}
int hashCode() {
def builder = new HashCodeBuilder()
if (u) builder.append(u.id)
if (p) builder.append(p.id)
builder.toHashCode()
}
static ProjectMemberships get(long userId, long projectId) {
find 'from ProjectMemberships where u.id=:userId and p.id=:projectId',
[userId: userId, projectId: projectId]
}
static ProjectMemberships create(Users u, Projects p, boolean flush = false) {
new ProjectMemberships(u: u, p: p).save(flush: flush, insert: true)
}
static boolean remove(Users u, Projects p, boolean flush = false) {
ProjectMemberships instance = ProjectMemberships.findByUsersAndProjects(u, p)
if (!instance) {
return false
}
instance.delete(flush: flush)
true
}
static void removeAll(Users u) {
executeUpdate 'DELETE FROM ProjectMemberships WHERE u=:u', [u: u]
}
static void removeAll(Projects p) {
executeUpdate 'DELETE FROM ProjectMemberships WHERE p=:p', [p: p]
}
static mapping = {
id composite: ['p', 'u']
version false
}
}
Use ProjectMemberships.create() to add a relationship between a user and a project, and ProjectMemberships.remove() to remove it.
Run grails schema-export to see the updated DDL (it'll be in target/ddl.sql). Run the create table statement for the project_memberships table, e.g.
create table project_memberships (
p_id bigint not null,
u_id bigint not null,
primary key (p_id, u_id)
)
Then populate it with this SQL (depending on your database you might need a slightly different syntax):
insert into project_memberships(p_id, u_id) select id, project_lead_id from projects
and finally drop the project_lead_id column from the projects table.
Of course do a database backup before making any changes.
You can get a user's projects with
def projects = ProjectMemberships.findAllByUsers(user)*.p
and similarly a project's users with
def users = ProjectMemberships.findAllByProjects(project)*.u

Dynamic finders with Grails many-to-many relationship

I have 2 domain classes which are mapped by many-to-many relationship. I followed the instruction of Grails documentation, but I still have some problem when processing data on those domains. Here are my 2 domain classes:
class User {
String name
int age
String job
static hasMany = [groups : Group]
static belongsTo = [org : Organization]
}
class Group {
String groupName
String code
static hasMany = [members : User]
}
My problems are:
1. The above relationship require one class hold belongsTo to be the "owner" of the relationship. In this context, the User belongs to the Group, but I do not know how to put the belongsTo to User class, because the standard syntax that Grails suggest is static belongsTo = [Group] (just specify the owner class name), so I cannot:
- put it into the exist belongsTo like this: static belongsTo = [org : Organization, Group]
- or define another belongsTo like this: static belongsTo = [Group]
Is below example right:
class Book {
String title
static belongsTo = Author
static hasMany = [authors:Author]
static mapping = {
authors joinTable:[name:"mm_author_books", key:'mm_book_id' ]
}
}
class Author {
String name
static hasMany = [books:Book]
static mapping = {
books joinTable:[name:"mm_author_books", key:'mm_author_id']
}
}
(Ref link: Many-to-Many link tables in grails (GORM) / hibernate)
I mean that do we need to specify the name of foreign key of the join table for each class?
If I want to find all User that are members of a specified Group whose name is "ABC", how can I use the DynamicFinder of Grails?
Thank you so much
It is very rare that m2m relationships have an owning side, so I've always found it odd to have to specify one for GORM to work correctly. Because of this, I don't do it this way. I create the join table as a domain. Then things get really simple.
class UserGroup implements Serializable {
User user
Group group
boolean equals(other) {
if (!(other instanceof UserGroup)) {
return false
}
other.user?.id == user?.id &&
other.group?.id == group?.id
}
int hashCode() {
def builder = new HashCodeBuilder()
if (user) builder.append(user.id)
if (group) builder.append(group.id)
builder.toHashCode()
}
static UserGroup get(long userId, long groupId) {
find 'from UserGroup where user.id=:userId and group.id=:groupId',
[userId: userId, groupId: groupId]
}
static UserGroup create(User user, Group group, boolean flush = false) {
new UserGroup(user: user, group: group).save(flush: flush, insert: true)
}
static boolean remove(User user, Group group, boolean flush = false) {
UserGroup instance = UserGroup.findByUserAndGroup(user, group)
instance ? instance.delete(flush: flush) : false
}
static void removeAll(User user) {
executeUpdate 'DELETE FROM UserGroup WHERE user=:user', [user: user]
}
static void removeAll(Group group) {
executeUpdate 'DELETE FROM UserGroup WHERE group=:group', [group: group]
}
static mapping = {
id composite: ['group', 'user']
version false
}
}
Then you just need to create the getters in your User and Group class. You won't have User user or Group group in either class. There is no need to map them with hasMany/belongsTo because all that will do is create the join table, which you've done by creating the UserGroup domain.
class User {
Set<Group> getGroups() {
UserGroup.findAllByUser(this).collect { it.group } as Set
}
}
class Group {
Set<User> getUsers() {
UserGroup.findAllByGroup(this).collect { it.user } as Set
}
}
Once you have these in place you can use the methods you created in the UserGroup domain and/or you can use finders on it...
def userGroupInstance = UserGroup.findByUserAndGroup(userInstance, groupInstance)
def userGroups = UserGroup.findAllByUser(userInstance)
def userGroupInstance = UserGroup.get(userId, groupId)
You get the idea. This presentation by Burt Beckwith sheds more light on why this is a good approach along with some other great tips for performance increases.
belongsTo will create another M:1 relationship (field) - you tell if there is a "main group" for a User in your case. If I wished only to enforce at least one group, I'd go with a custom validator for User.groups checking it's not empty.
(I'm not sure here) - I believe yes, if the key name differs from default Hibernat/GORM "userId"/"groupId".
findBy*() methods won't work here, you need a CriteriaBuilder, like here.

Resources