how to made one-to-one bidirectional relationships in grails? - grails

I have two domain classes and want to have one-to-one BIDIRECTIONAL relation between them. I write:
class Person {
Book book;
String name
Integer age
Date lastVisit
static constraints = {
book unique: true // "one-to-one". Without that = "Many-to-one".
}
}
class Book {
String title
Date releaseDate
String ISBN
static belongsTo = [person:Person] // it makes relationship bi-directional regarding the grails-docs
}
So, i want to have bi-directional, i could NOT find link from Book to Person in generated SQL:
CREATE TABLE `book` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`version` bigint(20) NOT NULL,
`isbn` varchar(255) NOT NULL,
`release_date` datetime NOT NULL,
`title` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=latin1
So then it means it is not bidirectional then? How to make bidirectional?

check out the hasOne property, in which class you define hasOne and belongsTo depends on where you want the FK to be stored, check this grails doc regarding hasOne:
http://www.grails.org/doc/latest/ref/Domain%20Classes/hasOne.html

To make this relationship one to one bidirectional,you should define them as fallows
//Person is the owning side of relationship
class Person {
//a foreign key will be stored in Book table called person_id
static hasOne = [book:Book]
static constraints = {
book unique: true
}
}
Class Book{
Person person
}
belongsTo is used for specifying the owner side(which manages relationship) of a one-to-many, many-to-one, or many-to-many relationship and for making relationship bidirectional
belongsTo should be used always in owned side

Related

Duplicates to be allowed in the mapping table for many to many relationship with extra column

I have two domain classes with many to many relationship with extra column.I created below domain classes by following the logic from the forums and still face an issue in saving the data in additional domain class.Roylaty is the additional column to save the value in the mapping table.
Below are the 3 domain classes:
class AuthorBook implements Serializable {
Author author
Book book
String royalty
boolean equals(other) {
if (!(other instanceof AuthorBook)) {
return false
}
other.author?.id == author?.id &&
other.book?.id == book?.id
}
int hashCode() {
def builder = new HashCodeBuilder()
if (author) builder.append(author.id)
if (book) builder.append(book.id)
builder.toHashCode()
}
static AuthorBook get(long authorId, long bookId) {
find 'from AuthorBook where author.id=:authorId and book.id=:bookId',
[authorId: authorId, bookId: bookId]
}
static AuthorBook create(Author author, Book book, boolean flush = false) {
new AuthorBook(author: author, book: book).save(flush: flush, insert: true)
}
}
class Author implements Serializable{
string name(nullable:false,unique:true)
Set<Book> getBooks() {
AuthorBook.findAllByAuthor(this).collect { it.book } as Set
}
}
class Book implements Serializable{
string title(nullable:false,unique:true)
Set<Author> getAuthors() {
AuthorBook.findAllByBook(this).collect { it.author } as Set
}
}
In one of my controllers i wrote the below logic:
def author1 = new Author("ABC")
author.save(flush:true)
def book1= new Book("GORM")
book.save(flush:true)
def authorBook = new AuthorBook(royalty:100,author:author1,book:book1)
authorBook.save(flush:true)
For both author and book, it works as expected i.e it won't allow duplicates and in the mapping table too. it won't allow duplicates but I want the output to be as below in the mapping table
Author AuthorBook Book
id Name id author_id book_id royalty id title
1 XYZ 1 1 1 500 1 Gorm
2 1 1 1000
It won't save this value as it is considering the combination of author_id and book_id to be unique even though I did not set any composite key on id's in the mapping table.
What should I change in the mapping table to allow duplicates?
Can you manually insert that row into the database? I suspect this is caused by your implementation of equals and hashcode on AuthorBook.
These two objects are the same:
author=1;book=1;royalty=100 and author=1;book=1;royalty=500 because your equality methods are only comparing author and book.

Unwanted GORM table creation

I have two domain classes, Person and Workshop. A Workshop has an owner of type Person, and many participants of type Person. A Person can be the participant of many workshops. When enrolling people in workshops I want to do so from the Workshop side like workshop.AddToParticipants() so here is how I set up my domain classes.
class Person {
String name
static hasMany = [enrolledWorkshops: Workshop]
static belongsTo = [Workshop]
}
class Workshop {
Date startDate
Date endDate
String name
Person owner
static hasMany = [participants: Person]
}
GORM correctly creates a WORKSHOP_PARTICIPANTS table with WORKSHOP_ID and PERSON_ID columns, and adds an OWNER_ID column to the WORKSHOP table. This is all good.
However, it ALSO creates a WORKSHOP_OWNER table with PERSON_ID and OWNER_ID columns! This makes no sense to me, and no matter how I try changing the GORM relationships I just can't get it to work how I want without this annoying extra table being created. How can I prevent the WORKSHOP_OWNER table from being created? Any help is greatly appreciated! (if it is of any help, I am using Grails 2.3.7)
In order to get rid of the WORKSHOP_OWNER table you'd have to replace Person owner with static belongsTo = [owner: Person] in the Workshop class. But that would conflict with static belongsTo = [Workshop] in the Person class. Both can't be owners.
Try this:
class Person {
String name
}
class Workshop {
Date startDate
Date endDate
String name
static belongsTo = [owner: Person]
static hasMany = [participants: Participant]
}
class Participant {
person person
static belongsTo = [workshop: Workshop]
}
In the above example, a Person owns a Workshop, and a Participant is owned by a Workshop. The workshop table will get a owner_id to refer to the person, which gets rid of the workshop_owner table.
To add a Person to a Workshop you simply wrap it in a Participant
workshop.addToParticipants(new Participant(person: somePerson))
You'd loose the enrolledWorkshops property in Person, but you can accomplish the same thing with a query.
def workshops = Workshop.withCriteria {
participants {
eq 'person', somePerson
}
}

GORM mappedBy and mapping difference

In the GORM what is the difference between mappedBy and mapping?
static mapping = {
...
}
static mappedBy = {
...
}
mapping
mapping simply tells GORM to explicitly map one or more Domain properties to a specific database column.
class Person {
String firstName
static mapping = {
table 'people'
id column: 'person_id'
firstName column: 'First_Name'
}
}
in this case for instance I am instructing GORM to map the id attribute to the column person_id of the people table and the firstName property to the First_Name column of the same table.
mappedBy
mappedBy instead let you control unidirectionality or bidirectionality of your classes associations. From Grails documentation:
class Airport {
static mappedBy = [outgoingFlights: 'departureAirport',
incomingFlights: 'destinationAirport']
static hasMany = [outgoingFlights: Route,
incomingFlights: Route]
}
class Route {
Airport departureAirport
Airport destinationAirport
}
Airport defines two bidirectional one-to-many associations. If you don't specify mappedBy you would get an error because GORM cannot infer which of the two properties on the other end of the association (either departureAirport or destinationAirport) each one-to-many should be associated with.
In other words it helps you remove the ambiguity that comes from bidirectional associations.

Columns in join table are NOT NULL

I have a domain class:
class Author {
String name
static hasMany = [superFantasticAndAwesomeBooks: Book, superBadAndUltraBoringBooks: Book]
}
This is all nice when using the in-memory database, however, when running on Oracle the Book collections are modeled in a join table which cannot be created because the column names are too long.
So, then I tried specifying the join table properties:
static mapping = {
superFantasticAndAwesomeBooks joinTable: [key: awesomeBooks]
superBadAndUltraBoringBooks joinTable: [key: boringBooks]
}
The problem (which doesn't happen if joinTable isn't specified) is that the join table is created where columns correspoinding to awesomeBooks and boringBooks are NOT NULL (they need to be nullable because a Book will be an awesomeBook or a boringBook)
Is there any way to configure joinTable to allow NULL columns?
Another option would be to map the join table yourself with a Domain class, for example:
class AuthorBook {
Author author
Book book
String status
static constraints = {
author(nullable:false)
book(nullable:false)
status(nullable:false,inList:['SuperFantasticAndAwesome','SuperBadAndUltraBoring'])
}
}
So your Author class becomes:
class Author {
...
static hasMany = [authorBooks:AuthorBook]
}
In this way the status is stored as a value of the join and statuses can be added, updated, or removed as needed in the future. It does have the side effect of having to query through the AuthorBook class to get to the associated Books.
See also: http://grails.org/Many-to-Many+Mapping+without+Hibernate+XML
I just ended up using 2 join tables:
static mapping = {
superFantasticAndAwesomeBooks joinTable: [name: 'awesomeBooks', key: 'book_id']
superBadAndUltraBoringBooks joinTable: [name: 'boringBooks', key: 'book_id']
}

Questions about implementing Domain class relationships & constraints in Grails

Using ArgoUML, I very quickly created this trivial representation of a few Domain classes (Person, Store, Product) and their relationships.
I'm struggling with the implementation of the relationships. Below was my initial approach for the Person domain, but it seems that I am missing something important.
class PersonToPerson {
Person from
Person to
String relation
static constraints = {
relation(inList:["Friend to", "Enemy of", "Likes", "Hates"])
}
static belongsTo = [ Person ]
}
class Person {
String firstName
String secondName
.
.
.
static hasMany= [ personToPerson:PersonToPerson,
personToStore:PersonToStore ]
}
Edit: updated question for clarity
After thinking on the problem I think I have a better way to ask the question(s). In the implementation of PersonToPerson above I have the relation as a simple string. I want the user to be able to select from a list of unique relations, which are defined in the constraints, for the string value for PersonToPerson. So this leads to the questions...
Should personToPerson and personToStore be consolidated into one list of type Relationship? Or should they stay independent lists as shown?
What is the mechanism to allow the user to add new values to the relation constraint?
1) Domain model
Keep your code simple. Don't create generic data model. It's way to hell. When you personToPerson and personToStore keep separate, it's much easier to follow your code.
Actually suggested solution makes it possible to access relations as consolidated and independent list simultaneously.
For this problem I would use inheritance feature in GORM.
Your classes would look like this:
class Person {
String name
static hasMany = [personToPerson:PersonToPerson,
personToProduct:PersonToProduct,
personToStore:PersonToStore]
static mappedBy = [personToPerson:"from"]
}
class Product{
String productName
}
class Relationship{
String note
}
class Store{
String storeName
}
class PersonToPerson extends Relationship{
Person from
Person to
String relation
static constraints = {
relation(inList:["Friend to", "Enemy of", "Likes", "Hates"])
}
static belongsTo = [ from:Person ]
}
class PersonToProduct extends Relationship{
Person person
Product product
String relation
static constraints = {
relation(inList:["likes", "dislikes"])
}
static belongsTo = [ person:Person ]
}
class PersonToStore extends Relationship{
Person person
Store store
String relation
static constraints = {
relation(inList:["Stock person", "Owner", "Manager", "Patron"])
}
static belongsTo = [ person:Person ]
}
DB schema for Person, Product and Store is usual. But for Relational domains look like this:
Relationship
Field Type Null Default
id bigint(20) No
version bigint(20) No
note varchar(255) No
class varchar(255) No
person_id bigint(20) Yes NULL
product_id bigint(20) Yes NULL
relation varchar(8) Yes NULL
from_id bigint(20) Yes NULL
to_id bigint(20) Yes NULL
store_id bigint(20) Yes NULL
Relationship domain makes possible to access all relational domain throw one domain.
2) Constraint
Just switch inList to validator. Than you can store constrain in file or DB.
See documentation or file example.
Example how to store constraint values in DB. First create a domain object.
class Constrain{
String name
String type
}
Than the domain class looks:
class PersonToPerson extends Relationship{
Person from
Person to
String relation
static constraints = {
relation(nullable:false, validator:{val, obj ->
def list = Constrain.findAllByType('person').collect{it.name}
return list.contains(val)
})
}
static belongsTo = [ from:Person ]
}
Look fine. You may want to consider a belongsTo in the PersonToPerson class.
Also, your has many in Person should be: [ personToPersons:PersonToPerson.... <- remove the s

Resources