GORM relational mapping - grails

I have 3 domain classes User, Server, Quota and I want to map them with the following relations
A User can access many Servers with different Quotas
But it should not allow a User to access the same Server with different Quotas (or +Each User may have exactly one entry for each Server)

You can create a NxN relation between User and Server. However, will be necessary create a class for that relation. This is necessary because you need the attribute quota. In the standard way of a NxN relation Grails (Hibernate) doesn't create a class for it.
You'll need three classes:
User,Server,UserServer
Class UserServer {
int quota
static belongsTo = [user:User, server:Server]
static constraints = {user unique: 'server'}
}
The unique constraint it's very important, because as you said, a User may have exactly one entry for each Server.
To link and unlink a User and Server you can use the following methods:
Class UserServer {
int quota
static belongsTo = [user:User, server:Server]
static constraints = {user unique: 'server'}
static PackScheme link(User user, Server server) {
UserServer userServer = UserServer.findByUserAndServer(user, server)
if (!userServer) {
userServer = new UserServer()
user.addToUserServers(userServer)
server.addToUserServers(userServer)
userServer.save()
}
return userServer
}
static void unlink(User user, Server server) {
UserServer userServer = UserServer.findByUserAndServer(user, server)
if (userServer) {
user.removeFromUserServers(userServer)
server.removeFromUserServers(userServer)
userServer.delete()
}
}
}
Note that you need to create a hasMany relation in User and Server called userServers:UserServer.
This solution is based on this link: http://grails.org/Many-to-Many+Mapping+without+Hibernate+XML

Related

Not able to define BelongsToMany relationship using same models with different type

I have Model Organization and User. There can be a buyer account manager and a seller account manager for an organization.
In organization the relationship is like :
public function managers() {
return $this->belongsToMany(User::class, 'organization_account_managers')->using('App\Model\Organization\OrganizationAccountManager')->withPivot(['account_manager_type']);
}
In User the relationship is defined as:
public function accounts()
{
return $this->belongsToMany(Organization::class, 'organization_account_managers')
->using('App\Model\Organization\OrganizationAccountManager')
->withPivot(['account_manager_type']);
}
When attaching in Nova, have defined on Organization as:
BelongsToMany::make('Account Managers','managers', 'App\Nova\User')
->fields(function () {
return [
Select::make('Type','account_manager_type')
->options(AppOrganization::$account_manager_types)
->rules('required')
->displayUsingLabels()->sortable(),
];
})
The table structure is:
Schema::create('organization_account_managers', function (Blueprint $table) {
$table->id();
$table->foreignId('organization_id');
$table->foreignId('user_id');
$table->tinyInteger('account_manager_type');
$table->timestamps();
});
Problem statement:
A user can be both buyer account manager and seller account manager. But when i try to attach so, NOva gives me an error: This users is already attached.
Appreciate any idea on how to resolve this.
From version v3.23.0 Laravel Nova allows duplicate relations.
BelongsToMany::make('Account Managers', 'managers', 'App\Nova\User')
->fields(function () {
...
})
->allowDuplicateRelations()
If you are using previous version you can try this workaround.

Session state being reset on every login

I have an MVC application with a simple login page.
After a user has successfully been validated I populate session variables with their details ie -
System.Web.HttpContext.Current.Session["usergroup"] = Convert.ToInt32(userDetails[0]);
System.Web.HttpContext.Current.Session["userid"] = Convert.ToInt32(userDetails[1]);
I have a user class like so -
public class MyUser
{
public int usergroup { get; set; }
public int userid { get; set; }
public static readonly MyUser Default = new MyUser()
{
usergroup = 0,
userid = 0
};
}
After the session varibales have been populated I use these to populate my MyUser variables ie -
MyUser.Default.usergroup = (Int32)System.Web.HttpContext.Current.Session["usergroup"];
MyUser.Default.userid = (Int32)System.Web.HttpContext.Current.Session["userid"];
Logging in and out, switching users works fine my dev laptop.
My issue is I have setup an IIS application on my network on a seperate testing laptop, and logged in on that laptop as User1. (where the url is localhost/MyApp)
Then to test the multiuser functionality, on the dev laptop, I logged in as User2 (where the url is http://{MY.NETWORK.IP}/MyApp).
I was able to login fine as User2 however if I go back to the testing laptop and refresh the screen User2's info is accessible and visible even though I am still logged in as User1!
My session variables are being reset every time a user (regardless of the machine) is logging in, how can I stop this from happening?
I have done a lot of reading, and seen people stopping caching by creating a rule in IIS, however this did nothing for me. Also people talking about using 2 separate browsers however again this 1. did nothing and 2 was not relevant to me as I am on 2 completely separate machines.
A web application is a single application, serving multiple users.
Being a single application means that any static values are shared by all users. So when you change the properties of your public static readonly MyUser Default after a login, all users now see that same user.
If you don't want to do System.Web.HttpContext.Current.Session["usergroup"] all through your code (neither would I), then you might wrap that in some class:
public class MySession
{
public int Usergroup
{
get => (int)System.Web.HttpContext.Current.Session["usergroup"];
set => System.Web.HttpContext.Current.Session["usergroup"] = value;
}
// etc
}
As this doesn't store data in itself, you could even make this a static class.

Grails Elasticsearch plugin issues

I am new to Elasticsearch and am using the revived grails plugin "elasticsearch:0.0.4.6".
I wanted to implement a search for a User by their firstName, surname or username, which returns the full domain instance.
I have a 2 domain classes:
User:
class User {
String firstName
String surname
static hasOne = [profile:Profile]
static hasMany = [friends:User]
static mappedBy = [ friends: 'friends' ]
static searchable = {
profile component:true
only = ['firstName', 'surname', 'profile']
}
...
Profile:
class Profile {
String username
String status
byte[] photo
static belongsTo = [user:User]
static searchable = true
...
}
I made the classes "searchable = true" and then created the following search:
def res = User.search("${params.friendSearch}").searchResults
This found the correct instances, but now when a user adds a photo to their profile, it suceeds but I recieve a never ending loop of the following error:
ERROR index.IndexRequestQueue - Failed bulk item: MapperParsingException[failed to parse [photo]]; nested: NumberFor
matException[For input string: the photos base64inputstring
I dont really get what is happening, but i figure it must be something to do with elasticsearch being unable to index the photo data. Can somebody provide an explanation?
I then experimented with searchables custom mapping options -
profile index:'no'
all=false
excludeFromAll = true
etc
Nothing worked. Eventually I added
only = ['username']
and it stopped the error from occuring and allowed me to find users based on the criteria i mentioned above. However, because of the "only" limit, it means that the User instances returned by the seach have a photo property equal to null, but i need this value!
What am i doing wrong? Can anyone advise me on the best course of action to take or any misunderstandings i have about Elasticsearch? Thanks
I think you might have to exclude the byte property photo from the searchable fields like so:
class Profile {
String username
String status
byte[] photo
static belongsTo = [user:User]
static searchable = {
except = ['photo']
}
This will exclude the property photo from being indexed and search. Hence the output of converting the byte format to string format will not fail.
Also maybe you might need a custom convertor to change the byte(string) to something more usable in the results?

Grails find by property

Okay, I am trying to find all of my Employee that have a given role... However no matter what I try I end up getting the same exception thrown...
enum Role {
SFC("State Fitness Coordinator"),
TRAINING("Training"),
DFC("District Fitness Coordinator"),
final String longName
private Role(String longName) {
this.longName = longName
}
}
class Employee {
static hasMany = [roles: Role]
static constraints = {
}
}
The first Thing I tried was Employee.findAllByRoles(Role.DFC)
Then I tried:
Employee.findAll("FROM Employee e WHERE e.roles IN (:role)", [role: [Role.DFC]])
as well as
Employee.withCriteria {
'in'('roles', [Role.DFC])
}
all resulting in
Class
java.sql.SQLException
Message
Missing IN or OUT parameter at index:: 1
Any direction would be much appreciated.
with grails 2.3.8 and H2
Employee.findAll("FROM Employee e WHERE :role in elements(e.roles) ", [role: Role.DFC.toString()])
this works… even if I think that Role could be a real Domain simplifying all operations
I think the problem is that Role is an enum. Are you sure you want to make it an enum?
I recommend making it a class since new roles might be added. If so, you can easily add a record using bootstrap or SQL query without making a new build to production.
class Role {
String roleName
String description
}
In Bootstrap:
Role sfc = new Role("SFC","State Fitness Coordinator")
Role sfc = new Role("TRAINING," "Training")
Role sfc = new Role("DFC", "District Fitness Coordinator")
N.B. Please make sure you are not using spring security plugin which has its own 'Role' class. If so, please change the name of your 'Role' class to something like 'EmployeeRole'

Grails loading data once

I have these 2 domains
class Country {
String name
static hasMany = [cities:City]
}
class City {
String name;
static belongsTo = [country: Country]
}
The data contained in these 2 tables is relatively big, and they're used in all the screens,
every time i choose a country i have to reload all its cities.
How can I load the data only once in memory so i can access it faster in all the screens.
I tried putting cities for eager fetching, and tried using cache plugin.
Thank you
You can configure both domain classes to be cached automatically and also cache the cities relation in Country:
class Country {
String name
static hasMany = [cities:City]
static mapping = {
cache true
cities cache:true
}
}
class City {
String name
static belongsTo = [country: Country]
static mapping = {
cache true
}
}
Caching is often a good strategy, but remember that caches have expiry parameters so if left idle your app may reload from the DB again. Depending on your cache provider you'll have to tune this, eg For ehcache edit your ehcache.xml in the grails config folder and set (cache name is the same as your Domain class including package name):
<cache name="Country" maxElementsInMemory="1000" timeToIdleSeconds="0" timeToLiveSeconds="0"/>
You should also move the query into a service, the service is by default singleton scoped and the service method also cachable.
An alternative is to store them in application scope such as in your Bootstrap.groovy run the query and assign the results:
servletContext.countries = Country.list()
Retrieve them in the same way, eg
in a controller:
List<Country> countries = servletContext.countries
or gsp:
<g:each var="country" in="${application.countries}">
${country}
</g:each>
If you have eager fetching on then you should see no DB hits.

Resources