How to have multiple One-to-One relationships between three domain classes - grails

As a follow up question to this, I want to have a User domain class which has an optional one-to-one relationship with BasicProfile domain class, User being the owner which may or may not have a profile. I have this part figured out. What I also want is an optional one-to-one relationship between the AcademicProfile domain class and the User, AcademicProfile being the owner, such that an AcademicProfile may or may not have a User. When I try to replicate this the way I did the first one-to-one relationship, it does not work. Here are my classes.
class User {
String username
String password
String email
AcademicProfile academicProfile
Date dateCreated
Date lastUpdated
static hasOne = [basicProfile: BasicProfile]
static constraints = {
username size: 3..20, unique: true, nullable: false, validator: { _username ->
_username.toLowerCase() == _username
}
password size: 6..100, nullable: false, validator: { _password, user ->
_password != user.username
}
email email: true, blank: false
basicProfile nullable: true
}
}
class BasicProfile extends Profile {
User user
Date dateCreated
Date lastUpdated
}
class AcademicProfile extends Profile {
String dblpId
String scholarId
String website
Date dateCreated
Date lastUpdated
static hasOne = [user: User]
static hasMany = [publications: Publication]
static constraints = {
dblpId nullable: true
scholarId nullable: true
website nullable: true, url: true
publications nullable: true
user nullable: true
}
}
class Profile {
String firstName
String middleName
String lastName
byte[] photo
String bio
static constraints = {
firstName blank: false
middleName nullable: true
lastName blank: false
photo nullable: true, maxSize: 2 * 1024**2
bio nullable: true, maxSize: 500
}
static mapping = {
tablePerSubclass true
}
}
When I run it, I get the error: Field error in object 'org.academic.User' on field 'academicProfile': rejected value [null];. I don't understand what I am doing wrong.

You'll have to add nullable:true constraint for AcademicProfile academicProfile in User class as you mentioned that you need an 'optional' relationship between AcademicProfile & User.
The error itself is self explanatory though, that you can't create a User class's instance, without providing the academicProfile property a non-null value.

Related

Error loading plugin manager in Grails/Groovy

I'm working on a simple grails project when I encountered a problem. I have done lot of research but I haven't found the right answer.
The thing is I have 3 domain classes namely Inventory, User and Movement and the relationship between them is one-to-many for Inventory and Movement and the same for User and Movement so Movement is pretty much in the middle. I managed to connect the Inventory and Movement well but the other relationship shows an error below.
Error |
Error loading plugin manager: Property [movements] in class [classcom.inventory.User] is a
bidirectional one-to-many with two possible properties on the inverse side.
Either name one of the properties on other side of the relationship [user] or use the
'mappedBy' static to define the property that the relationship is mapped with.
Example: static mappedBy = [movements:'myprop'] (Use--stacktrace to see the full trace)
| Error Forked Grails VM exited with error
This are my domain classes:
Users:
class User {
String userID
String fullName
String position
Department department
String toString(){
fullName
}
static hasMany = [inventories: Inventory, movements: Movement]
static constraints = {
userID blank: false, unique: true
fullName blank: false
position()
department()
movements nullable: true
}
}
Movement:
class Movement {
User oldUser
User newUser
Inventory inventoryID
Date movementDate
User userResponsible
//static belongsTo = User
static constraints = {
inventoryID blank: false
oldUser blank: false
newUser blank: false
movementDate()
userResponsible blank: false
}
}
Inventory:
class Inventory {
String inventoryID
String code
String description
String serial_num
Date purchase_date
byte[] image
Date record_date
String remarks
Type type
Brand brand
User user
static hasMany = [movements: Movement]
String toString(){
"$inventoryID, $type"
}
static constraints = {
inventoryID blank: false, unique: true
code blank: false
description nullable: true, maxSize: 1000
serial_num blank: false
purchase_date()
image nullable: true, maxSize: 1000000
record_date()
remarks nullable: true, maxSize: 1000
type()
brand()
user()
}
}
Any idea how to solve the error..??
The problem here is that gorm is unable to distinguish between the newUser and the oldUser on your Movements class. Try adding a mappedBy section and adding another part to your hasMany property to your user class, below is an example that should work:
class User {
String userID
String fullName
String position
Department department
String toString(){
fullName
}
static hasMany = [inventories: Inventory, movementsByOldUser: Movement, movementsByNewUser: Movement]
static mappedBy = [movementsByOldUser: 'oldUser', movementsByNewUser: 'newUser']
static constraints = {
userID blank: false, unique: true
fullName blank: false
position()
department()
movements nullable: true
}
}
For some documentation reference see: http://www.grails.org/doc/2.2.x/ref/Domain%20Classes/mappedBy.html

Relationships between Grails domain classes with inheritance

I have the following class. In src/groovy,
class Profile {
String firstName
String middleName
String lastName
byte[] photo
String bio
}
The domain classes BasicProfile and AcademicProfile extend Profile.
class BasicProfile extends Profile {
User user
Date dateCreated
Date lastUpdated
static constraints = {
firstName blank: false
middleName nullable: true
lastName blank: false
photo nullable: true, maxSize: 2 * 1024**2
bio nullable: true, maxSize: 500
}
static mapping = {
tablePerSubclass true
}
}
class AcademicProfile extends Profile {
User user
String dblpId
String scholarId
String website
Date dateCreated
Date lastUpdated
static hasMany = [publications: Publication]
static constraints = {
importFrom BasicProfile
dblpId nullable: true
scholarId nullable: true
website nullable: true, url: true
publications nullable: true
}
static mapping = {
tablePerSubclass true
}
}
Then there is a Publication class.
class Publication {
String dblpId
String scholarId
String title
String description
Date publicationDate
int citations
Date dateCreated
Date lastUpdated
static belongsTo = [AcademicProfile]
static hasOne = [publisher: Publisher]
static hasMany = [academicProfiles: AcademicProfile]
static constraints = {
dblpId nullable: true
scholarId nullable: true
title blank: false, maxSize: 100
description nullable: true, maxSize: 500
publicationDate: nullable: true
academicProfiles nullable: false
}
}
Finally, I have a User class.
class User {
String username
String password
String email
Date dateCreated
Date lastUpdated
static hasOne = [basicProfile: BasicProfile, academicProfile: AcademicProfile]
static constraints = {
username size: 3..20, unique: true, nullable: false, validator: { _username ->
_username.toLowerCase() == _username
}
password size: 6..100, nullable: false, validator: { _password, user ->
_password != user.username
}
email email: true, blank: false
basicProfile nullable: true
academicProfile nullable: true
}
}
My questions are as follows.
I want a relationship where each User may optionally have a Profile (either BasicProfile or AcademicProfile). I tried static hasOne = [profile: Profile] but I got errors saying Profile does not agree to the hasOne relationship. So the current setup I have is a workaround. Is there no way a user can have one Profile be it BasicProfile or AcademicProfile?
Secondly, in the current setup, I get the error: Invocation of init method failed; nested exception is org.hibernate.MappingException: An association from the table academic_profile_publications refers to an unmapped class: org.academic.AcademicProfile when I try to run it. A Google search tells me that this is a problem with classes which are inheriting from other classes. So technically, if I don't have a hasMany relationship in Publication with AcademicProfile, it should work without any issues. But I don't want that. Because a publication has many authors (AcademicProfiles in my case) and an author may have many publications. So is there a way to fix this?
You're not using Hibernate inheritance - that requires that all of the classes be mapped. You're just using regular Java/Groovy inheritance where you inherit properties and methods from base classes. But Hibernate isn't aware of that, so it can't do queries on the unmapped base class.
I'm not sure why it's complaining about AcademicProfile, but it could be a secondary bug caused by the core issue.
I find Hibernate inheritance to be way too frustrating to use in most cases, so I use this approach when there is shared code.
It should work if you move Profile to grails-app/domain. Once you do that you should move the tablePerSubclass mapping config to the base class and only specify it once.

Grails BootStrap: No signature of method: *.addTo* applicable

I have two domain classes: User
class User {
String username
String password
String email
Date dateCreated
Date lastUpdated
// static belongsTo = [profile: Profile]
static constraints = {
username size: 3..20, unique: true, nullable: false, validator: { _username ->
_username.toLowerCase() == _username
}
password size: 6..100, nullable: false, validator: { _password, user ->
_password != user.username
}
email email: true, blank: false
// profile nullable: true
}
}
and Profile:
class Profile {
String firstName
String middleName
String lastName
byte[] photo
Date dateCreated
Date lastUpdated
static belongsTo = [User]
static constraints = {
firstName blank: false
middleName nullable: true
lastName blank: false
photo nullable: true, maxSize: 2 * 1024**2
}
}
A profile can belong to only one user and a user can have (or belong to?) only one profile. When I try to create the objects in BootStrap.groovy in the current setup I get an error saying that the addTo() method does not exist. I don't really know what I am doing wrong. This is how I am creating them in BootStrap.groovy:
User arun = new User(username: 'arun', password: 'password', email: 'arun#email.com').save(failOnError: true)
Profile arunProfile = new Profile(firstName: 'Arun', lastName: 'Allamsetty').addToUser(arun).save(failOnError: true)
Can someone please point out the mistake(s). I am sure it's silly.
A strict bi-directional one-one relationship is required as you have requested for:
A profile can belong to only one user and a user can have (or belong to?) only one profile
Three modifications are mainly required in domain classes:
//User.groovy
static hasOne = [profile: Profile]
static constraints = {
profile unique: true
}
//Profile.groovy
User user
Above is a bi-directianl one-one relationship. You do not need addTo* anymore while creating each of them.
Profile arunProfile = new Profile(firstName: 'Arun', lastName: 'Allamsetty')
User arun = new User(username: 'arun', password: 'password',
email: 'arun#email.com',
profile: arunProfile).save()

Grails searchable plugin with hasMany

I am using grails searchable plugin to search my domain classes. However, I cannot yet search by my hasMany (skills and interests) fields even though they are of the simple type String. This is my domain class:
class EmpactUser {
static searchable = [except: ['dateCreated','password','enabled','accountExpired','accountLocked','passwordExpired']]
String username
String password
boolean enabled = true
boolean accountExpired
boolean accountLocked
boolean passwordExpired
String email
String firstName
String lastName
String address
String phoneNumber
String description
byte[] avatar
byte[] resume
Date dateCreated
static hasMany = [
skills : String,
interests : String, // each user has the ability to list many skills and interests so that they can be matched with a project.
]
static constraints = {
username blank: false, unique: true
password blank: false
email email: true, blank: false
firstName blank: false
lastName blank: false
description nullable: true
address nullable: true
avatar nullable: true, maxSize: 1024 * 1024 * 10
resume nullable: true, maxSize: 1024 * 1024 * 10
phoneNumber nullable: true, matches: "/[(][+]d{3}[)]d+/", maxSize: 30
}
}
This is the code I am using to search:
def empactUserList = EmpactUser.search(
searchQuery,
[reload: false, result: "every", defaultOperator: "or"])
Am I missing something?
Thanks,
Alan.
Searchable has trouble recognising hasMany relations with Strings. A workaround is to create a new domain object which "belongs to" the parent object, and make a transient variable in the parent class. The following code works as an alternate to the example given in documentation.
class Article {
static searchable = {
root true
keywords (component:true)
}
static transients = ['keywords']
Set<ArticleKeyword> getKeywords() {
ArticleKeyword.findAllByArticle(this) as Set
}
}
class ArticleKeyword {
static searchable = { root false}
static constraints = {
}
String text
static belongsTo = [article:Article]
static mapping = {
text type: 'text'
}
}

Grails: How to create formula on an embedded class

When I create a general class to embed it to other class, I want to add a transient property which is defined by a formula, but I do not how to implement this. Here is my source code:
//Embedded
class LocationInfo {
Long country, state, city
String address, fullAddress
static constraints = {
country nullable: true
state nullable: true
city nullable: true
address nullable: true
}
static mapping = {
country column: 'l_country'
state column: 'l_state'
city column: 'l_city'
address column: 'l_address'
fullAddress formula: "SELECT (l_address || ', ' || city.name) FROM system_location city WHERE city.id = l_city"
}
static transients = ['fullAddress']
}
class SystemLocation {
String name
Long parentLocationId
String level
static constraints = {
name blank: false, maxSize: 100
parentLocationId nullable: true
level inList: ['country', 'state', 'city']
}
static mapping = { version false }
}
//Host
class User {
String email
String password
String firstName
String lastName
Team team
UserLevel userLevel
boolean enabled = true
boolean accountExpired = false
boolean accountLocked = false
boolean passwordExpired = false
boolean teamLeader = false
LocationInfo locationInfo
AuditingInfo auditingInfo
static embedded = ['locationInfo', 'auditingInfo']
transient springSecurityService
static constraints = {
email blank: false, unique: true, maxSize: 200
password blank: false, maxSize: 200
firstName blank: false
lastName blank: false
team nullable: true
teamLeader nullable: true
locationInfo nullable: true
auditingInfo nullable: true
}
static mapping = {
team column: "team_id"
userLevel column: "user_level_id"
}
}
The LocationInfo is embedded to User class, nhÆ°ng when I get a specific user by ID and check the value in user.locationInfo.fullAddress, it is always NULL; and the generated SQL does not contains the "SELECT (l_address || ', ' || city.name)..." statement.
I do not know how to use a formula in an embedded class.
Could you please help me solve this?
According to the Grails manual there is no such thing like formula in the mapping settings.
I'd solve this by simply declaring a getter method on your domain class:
class LocationInfo {
Long country, state,
SystemLocation city
String address // n.b. no longAddress here
static constraints = {
country nullable: true
state nullable: true
city nullable: true
address nullable: true
}
static mapping = {
country column: 'l_country'
state column: 'l_state'
address column: 'l_address'
}
static transients = ['fullAddress']
String getFullAddress() {
"$address $city.name"
}
}
N.B. that city is now a reference to another domain class. In your draft this is just an id which makes your domain model hard to navigate.
I also had this problem and I think you can't do this that way.
When you define formula for some derived property, you have to put SQL with names of columns you know. But when this class is used in embedded property column names of that embedded object are changed.
In your case table User has columns location_info_l_city, location_info_l_address. But in formula you used names like l_city, l_address... There is no such column in table User.
I resolved the problem by adding derived property for embedded object's owner.
In your case I would add to class User mapping:
class User {
//...
String fullAddress
//...
static mapping = {
//...
fullAddress formula: "SELECT (location_info_l_address || ', ' || city.name) FROM system_location city WHERE city.id = location_info_l_city"
}
}
Now, you can use column User.fullAddress also in HQL queries.

Resources