I am a beginner in GRAILS so i am hoping some help on the issue i am facing.
I have read the documentation but i am still vague on the idea of relationships in grails. In grails, you could have 4 types of relationship between domain classes.
1 to 1
1 to many
many to 1
many to many
Grails has three constructs to define relationships
static hasMany =
static belongsTo =
static hasOne =
My question and dilemma is why do we need these three constructs to define a relation when we could just specify what type of objects each class has that would automatically define relationship between domain classes.
for example
To define many to many i could have two classes designed this way
class Author{
Set<Book> books
}
class Book{
Set<Author> authors
}
For 1 to many and many to 1
class Author{
Set<Book> books
}
class Book{
String title
}
for one to one
class Author{
Book book
}
class Book{
Author author
}
I appreciate it if anyone can give me a clear, easy to understand explanation. Thank you!
Everything you defined there should work fine. You don't have to use any of the other stuff that you mentioned that GORM offers, but there are reasons that you might want to. For example, you can write a class like this:
class Author{
Set<Book> books
}
That is not the same thing as this:
class Author {
static hasMany = [books: Book]
}
When you use hasMany, Grails generates this for you...
class Author {
Set<Book> books
def addToBooks(Book b) {
books.add(b)
this
}
def addToBooks(Map m) {
books.add(new Book(m))
this
}
def removeFromBooks(Book b) {
books.remove(b)
this
}
}
That isn't exactly what is generated, but that is some of the stuff that you might care about.
There is more to it than is represented there. For example, if the Book has a reference back to the Author, the addToBooks methods will hook that back reference up for you.
There are other behaviors associated with the other properties you mentioned. For example, the hasOne property switches the direction in which the foreign key points on the persistence model. The belongsTo property enforces cascading of certain events. etc.
Take a look at the GORM docs at http://grails.org/doc/latest/guide/GORM.html for more information.
Related
Would this be correct way to extend Grails parent and child classes?
Originally I thought that overriding hasMany and belongsTo properties, would be a good idea but that did not work so well as it introduced conflicting functionality so I dropped it from subclasses.
What I am trying to do here is to package shared code between multiple applications. I am starting with these two classes in my plugin.
class Purchase {
String firstName
String lastName
static mapping = {
tablePerHierarchy false
}
static hasMany = [items: PurchaseItem]
}
class PurchaseItem {
BigDecimal price
Integer qty
statiuc belongsTo = [purchase: Purchase]
static mapping = {
tablePerHierarchy false
}
}
The application specific classes have to extend both Purchase and PurchaseItem so I am implementing it like so, inheriting one-to-many relationship:
class Flight {
static hasMany = [purchases: TicketPurchase]
}
class TicketPurchase extends Purchase {
// some class specific properties
static belongsTo = [flight: Flight]
}
class TicketPurchaseItem extends PurchaseItem
Integer bagQty
static namedQueries = {
ticketPurchaseItemsWithBagsByFlight {flightInstance->
purchase {
flight {
eq 'id', flightInstance.id
}
}
gt 'bagQty', 0
}
}
}
The namedQuery in TicketPurchaseItem joins Purchase and Flight even though super class Purchase does not belongTo Flight, only subclass TicketPurchase does.
TicketPurchase ticketPurchase = new TicketPurchase()
ticketPurchase.addToItems(new TicketPurchaseItem(bagQty: 5)).save()
Flight flight = Flight.first()
flight.addToPurchases(ticketPurchase).save()
// this works
def ticketPurchaseItemList = TicketPurchaseItem.ticketPurchaseItemsWithBagsByFlight(flight)
This works with Grails but is it good design or is there a better way to deal with domain classes extending one-to-many relationships?
The short answer is you've got it right. Probably. The question to ask is whether you're ok with allowing the properties you've added to your subclasses to be set to NULL. I don't see a problem with what you have. You can learn more about Grails domain class inheritance and polymorphic queries from the Grails documentation and from my blog article on the subject.
If you're curious about the impact of your domain class model on the database, you can take a look at the queries GORM/Hibernate is running by logging then. I believe this is the article I've used to set up logging.
I have a domain class that can belong to one of several classes. I'm seeing validation errors when I try to save.
class Teacher {
Book book
}
class Student {
Book book
}
// book can belong to either a student or a teacher
class Book {
static belongsTo = [student : Student, teacher : Teacher]
}
The validation error suggests that a book must belong to BOTH a student and a teacher (neither can be null), but I want to model it so that it can belong to either. How do I do this please?
Please disregard the fact that for my example you could change it so that a Person owns a book and a Teacher and a Student are both types of Person - I want to know how how to create the correct belongsTo.
Edit to explain reasoning behind requirement:
There will be 3 tables created: Book, Student and Teacher. I need to be able to create an index on the Book class that refers to Student and Teacher. This is so the query that is "find all books that belong to Teacher A" can be made as fast as possible.
If there was just a single belongsTo (example shown if for owner teacher)then this is done like this:
static mapping = {
teacher index: 'teacher_idx'
}
Well this is very much doable, its just that your approach is wrong here.
belongsTo is used in a way when an entity must and must be mapped with some other entity. There is nothing like either of them.
What you can do is
1. create an Abstract Domain `Book`
2. create an Domain `StudentBook` it belongs to `Student`
3. create an Domain `TeacherBook` it belongs to `Teacher`
So here only one table will be created for the three Domains, named as Book. This table will contain a field class which will determine if the book belongs to Student or Teacher.
If I understand you then you can use other version of belongsTo which does not store back reference of Owner Class e.g
class Book {
static belongsTo = [Student, Teacher]
}
Ref
Is it possible to map the following with GORM?
I want to get rid off all associated events when I delete a person.
Person object should not have a link to events.( I want to avoid using hasMany on Person domain)
class Person {
String username
}
class Event {
String description
static belongsTo = [person:Person]
}
I'm getting now a 'Referential integrity constraint violation' when doing person.delete() because events are not removed before deleting person.
I don't think that is possible without using hasMany (speaking of which, why do you want to avoid that anyway?)
This SO Question states:
Hibernate only cascades along the defined associations. If A knows
nothing about Bs, nothing you do with A will affect Bs.
Use static hasMany and bam, problem fixed.
Edit:
The only way I think you could achieve this is using beforeDelete on the Person class to delete all the associated Events, i.e.
class Person {
def beforeDelete() {
def events = Event.findAllByPerson(this)
for (e in events) {
e.delete()
}
}
}
See the documentation on Events and Auto Timestamping for more info on that.
The above will not work
Why not define a no reference mapping:
class Person {
String username
static hasMany=[Events]
}
This way there is no actual bindings of events to person but a person can have many events
UPDATED
I have a domain classes as below
class Training{
// has one createdBy object references User domain and
// has one course object references Course domain
// has One Trainer1 and Trainer2 objects refernces Trainer Object.
}
class Trainer{
String name
//can have many trainings.
//If Trainer gets deleted, the trainings of him must be deleted
}
Class User{
String name
// can have many trainings.
}
class Course{
String name
//If Course gets deleted, Trainings of this course must be deleted
// can have many trainings.
}
I have got a training create page, Where I have to populate already saved Course, User, Trainer1 and Trainer2. I am not saving them while Creating the training.
So, How to specify the relationship in grails
You did not put any effort to searching answer for yourslef. There are plenty basic examples and blog posts how to map relations in Grails. You should start with Grails documentation of GORM - Grails' object relational mapping. You can find it here.
I see some minor flaws in yout initial design: ie why should training be deleted if user is deleted when trainings will obviously tie with many users. Can training exists without trainers or vice versa ?
I would start with something like this:
Class Training {
static hasMany = [users: User, trainers: Trainer]
static belongsTo = Course
}
Class Trainer {
String name
}
Class User {
String name
}
Class Course {
String name
static hasMany = [trainings: Training]
}
EDIT: I have to agree with Tomasz, you have jumped here too early without searching for answers yourself. Grails.org has good documentation about GORM with examples too.
I've been struggling to produce the right configurations to produce cascade-delete behaviour in a relatively simple Grails project.
Say I have the following simple domain classes:
class Author {
String name
static constraints = {
}
}
and
class Book {
String title
Author author
static constraints = {
}
}
If I create an author, and then create a book written by that author, I am not able to delete the Author without first manually deleting the book. I get an "Integrity constraint violation". This isn't suprising as MySQL (my underlying database) is created by Grails with a "foreign key constraint" on the "author" column of the "book" table as "Restrict" (and this behaviour is consistent with expectations from the Grails documentation as I understand it).
Now, if I were to manually change the underlying database constraint on the "author" column of the book table from "Restrict" to "Cascade", I get the behaviour I want. Namely, that if you delete the Author, all their books are also deleted.
So, what I'd like to do is change my Grails "Book" class in a way that creates the "book" table with "on delete cascade" on the author column. I've been reading plenty of information about doing this sort of thing and GORM defaults using "belongsTo" and explicit "mappings".
One way to do this, based on the documentation for "belongsTo", seemed to be to change the line in the Book class from:
Author author
to
static belongsTo = [author: Author]
Thereby making it explicit that the Author is the "owning side" of the relationship. The documentation seems to suggest that this should generate the cascade-delete behaviour I'm after. However, it doesn't work unless I add an explicit "hasMany = [books:Book]" into the Author class. I don't wish to do this. (This wish makes more sense in my actual business domain, but even just as an exercise in understanding, I don't yet get why I have to have the Author domain class know about books explicitly).
I'd just like a grails setting to change the Book class to produce the "cascade delete" setting in the database, without having to change the Author class. I tried using an explicit mapping like:
static mapping = {
author cascade: 'all'
}
and combinations of this with other explicit mapping or "belongsTo" options. This didn't work.
Noting that a simple change to the "constraints" in the underlying SQL database provides the behaviour I want, is there a way to get this through Grails? If not, have I misunderstood something fundamental here or am I trying to do something stupid?
I think you are missing the required field of type Book in Author.
Here's the sample code, which is as per the documentation (tested and works)
class Author {
String name
Book book //you are probably missing this field
static constraints = {
}
}
class Book {
String name
static belongsTo = [author: Author]
static constraints = {
}
}
Test case:
#TestFor(Author)
#Mock([Book])
class AuthorTests {
#Test
void testAuthorBookCascades() {
Author a = new Author(name: "Douglas Adams")
Book b = new Book(name: "So Long, and Thanks for all the Fish")
a.book = b
a.save()
assert Author.count() == 1
assert Book.count() == 1
a.delete()
assert Author.count() == 0
assert Book.count() == 0
}
}
As you can see, you need the Book argument in Author. No need for the hasMany or hasOne clause.