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
}
}
Related
I have created two domain class named City and Address in Grails 3:
class City {
Long id
static hasMany = [addresses: Address]
static mapping = {
id generator: "increment"
}
}
class Address {
Long id
}
I have created two controllers for these classes. The controllers just extend the RestfulController class. I have created the following URL mapping:
group("/api") {
"/city"(resources: 'city') {
"/addresses"(controller: 'address')
}
"/address"(resources: 'address')
}
The idea being that I can access the addresses belonging to a city (with id = 1) via localhost:8080/api/city/1/addresses. Unfortunately, I always get all addresses and not only the ones belonging to the given City. If I break on the index method of the AddressController I see that the params given to the method indclude a cityId which contains the id of the City passed in the query. However, it seems that the id is not picked up during the database query. Am I doing something wrong here? Do I need to add a belongsTo / mappedBy or a City to the Address?
Is it possible to have a domain class that belongs to multiple domain classes with back reference? For instance:
class Person {
List<Book> books
static hasMany = [books: Book]
}
class Organization {
List<Books> books
static hasMany = [books: Book]
}
class Book {
def owner // what's the type?
static belongsTo = [Person, Books]
}
A Book can belong to a Person or an Organization, but not both.
Person and Organization have separate sequence IDs.
The solution I came up with is:
class Book {
Long ownerID
String ownerClass
static belongsTo = [Person, Books]
static transients = ['owner']
static constraints = {
ownerId(nullable:false, blank:false)
ownerClass(nullable:false, blank:false)
}
public BookOwner getOwner() {
grailsApplication.getArtefact("Domain", ownerClass)?.getClazz()?.get(ownerId)
}
}
where BookOwner is an Interface implemented by Person and Organization. So calling a bookInstance.owner will return a Person or Organization instance, both BookOwner.
My solution works well, but it doesn't feel right - a sure sign that I am not fully understanding what I'm doing. What's the best way to implement this? Should I completely give up on having the extremely convenient back reference?
Thank you
I guess, you should have made Owner superclass. Grails will create Owner table with field class meaning child class names (in your case: Person, Organization).
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.
Lets say I have this two classes in my application:
class User
{
static belongsTo = [company: Company]
Address address
Phone phone
String name
Integer salary
Date birthDate
}
and
class Company {
Boolean active = false
static hasMany = [users: User]
}
Something really basic.
I would like to make a constraint in Company class on the users field. That I won't have in the same company two users with the same name address and phone.
I should be able to add another user with this three matching fields for a different company. And, name is a nullable field so I should be able to have a few records with the same address and phone for the same company id the name is null.
Could someone please help me to define such a constraint?
See if this is what you are looking for, but make sure your company is saved first.
Company.withTransaction {
def compnay= new Company (active:true)
company.save(flush:true)
def user = new User (...)
compnay.addToUsers(user)
}
/
class User
{
static belongsTo = [company: Company]
Address address
Phone phone
String name
Integer salary
Date birthDate
static constraints = {
name unique: ['company','address','phone']
}
}
/
class Company {
Boolean active = false
static hasMany = [users: User]
}
I have a relationship between two domain class: User and Bank, user has many of bank and bank has many of user, the result table I call it as mm_user_banks that keep both table IDs for MM relationship.
class User {
String name
String password
Date createdAt = new Date()
Date loginAt
static hasMany = [banks:Bank]
static fetchMode = [banks: 'eager']
static constraints = {
}
static mapping = {
banks joinTable: [name: 'mm_user_banks', key: 'mm_user_id']
}
}
and Bank...
class Bank {
Long id
String name
static belongsTo = User
static hasMany = [users:User]
static constraints = {
}
static mapping = {
id generator: 'assigned'
users joinTable: [name: 'mm_user_banks', key: 'mm_bank_id'], lazy: false
}
}
I can save users with banks as well, but the problem is when I try to retrieve bank list from an user instance and it only fetch one row even if there is more than one row for this user in mm_user_banks table.
E.g:
User u = new User(name: 'user', password: 'pwd')
u.addToBanks(Bank.findById(1))
u.addToBanks(Bank.findById(2))
u.addToBanks(Bank.findById(3))
u.save(flush: true)
In database it is OK, fetching 3 rows, but when I get the user and see its bank list:
def user = User.findById(1) //OK
println "Size ${user.banks?.size()}" //only 1??? How?
Thanks in advance!
Just tested it out - "user.banks?.size()" returns "3". Are you sure that data in DB is OK?
Anyway, if you want to use eager fetching for collections then define it in "mapping" of User class (instead of "fetchMode" static field):
static mapping = {
banks joinTable: [name: 'mm_user_banks', key: 'mm_user_id'], fetch: 'join'
}
in Bank class use fetch: 'join' in mapping instead of lazy: false
Thanks so much, Sergei, but didn't worked out.
I fixed it in an ugly way, but this is was my remaining way out.
I created another domain class to mapping the MM table, so I did a mm_user_banks class. I use it to fetch records from bank or user and worked OK. To save banks for a user I actually do it in the old way as well, not changed from that. I guess this mighty not be the best way, but like I said, worked out.
I've tried "fetch: 'join'" and "lazy: false" and "static fetchMode = [banks: 'eager']" without success.
The new domain class is:
class UserBanks {
User user
Bank bank
static constraints = {
}
static mapping = {
table name: 'mm_user_banks'
user column: 'mm_user_id'
bank column: 'mm_bank_id'
}
}
I had the similar problem but in my case I had another relation between my equivalents to User and the Bank. This made the hasMany statement to use the wrong relation to map the many to many relation. In my case it was solved by adding a mappedBy statement to the relation:
class Bank{
...
static mappedBy=[users:'banks']
...
}
and
class User{
...
static mappedBy=[banks:'users']
...
}
or as it was in my case, since it was a uni-directional relation:
class ArticleGroup {
String name
static hasMany = [articles:Article]
static mappedBy = [articles: 'none']
static constraints = {
name(nullable:false, blank:false, unique:true, maxSize:100)
}
static mapping = {
table 'article_group'
sort 'name'
articles joinTable: [name:'article_group_article', key: 'articleGroup_id', column:'article_id'], fetch: 'join'
}