Grails, gorm. Find child by parent and parent by child - grails

For example, I've parent class Author:
class Author {
String name
static hasMany = [
fiction: Book,
nonFiction: Book
]
}
and a child class Book:
class Book {
String title
static belongsTo = [author: Author]
}
I've done some records to Author using:
def fictBook = new Book(title: "IT")
def nonFictBook = new Book(title: "On Writing: A Memoir of the Craft")
def a = new Author(name: "Stephen King")
.addToFiction(fictBook)
.addToNonFiction(nonFictBook)
.save()
How can I found child-class record by parent and parent-class record by child?

In my opinion, this is not the best way to model your data. I would do it like this.
class Author {
String name
static hasMany = [books: Book]
}
class Book {
String title
BookTypes bookType
static belongsTo = [author: Author]
}
enum BookTypes {
FICTION,
NON_FICTION
}
Then, you can do lookups like
def author = Author.get(1)
def nonFictionByAuthor = Book.findAllByAuthorAndBookType(author, BookTypes.NON_FICTION)
You could also just do something like this...
def author = Author.get(1)
def fictionBooks = author.books.findAll { it.bookType == BookTypes.FICTION }
And then inversely:
def fictionBook = Book.findByTitleAndBookType('Title001', BookTypes.FICTION)

Related

Grails: How to insert with many-to-many with extra column?

Take the example of author, book and with a join table author_books with an extra royalty column:
CREATE TABLE 'author_books' (
'author_id` bigint(20) NOT NULL,
'book_id' bigint(20) NOT NULL,
'royalty' int NOT NULL,
PRIMARY KEY ('author_id', 'book_id') ,
INDEX 'FK24C812F6183CFE1B' ('book_id'),
INDEX 'FK24C812F6DAE0A69B' ('author_id')
)
If I don't have the db-reverse-engineer plugin generate the AuthorBooks class, the generated code would be:
class Author {
String name
static hasMany = [books: Book]
}
class Book {
String title
static hasMany = [authors: Author]
static belongsTo = [Author]
}
The following code:
def author1 = new Author(name: 'author1').addToBooks(new Book(title:
'book1')).save(flush: true)
would insert one row into author table and one row in book table, but would fail to insert into author_books because royalty cannot be null.
However if have the db-reverse-engineer plugin generate the AuthorBooks class, the generated code would be:
class Author {
String name
static hasMany = [authorBookses: AuthorBooks]
}
class Book {
String title
static hasMany = [authorBookses: AuthorBooks]
}
class AuthorBooks implements Serializable {
Long authorId
Long bookId
Integer royalty
Author author
Book book
int hashCode() {
def builder = new HashCodeBuilder()
builder.append authorId
builder.append bookId
builder.toHashCode()
}
boolean equals(other) {
if (other == null) return false
def builder = new EqualsBuilder()
builder.append authorId, other.authorId
builder.append bookId, other.bookId
builder.isEquals()
}
static belongsTo = [author: Author, book: Book]
static mapping = {
author insertable: false // these insertable and updateable codes were manually added
author updateable: false // otherwise it would not run
book insertable: false
book updateable: false
id composite: ["authorId", "bookId"]
version false
}
}
In this case, I cannot call author.addToAuthorBooks(new AuthorBooks( )) because author_books cannot have author_id or book_id to be null. In the end I need to do the following to get it work:
def author1 = new Author(name: 'author1').save(flush: true)
def book1 = new Book(title: 'book1').save(flush: true)
def authorbook1 = new AuthorBooks(authorId: author1.id, bookId: book1.id,
royalty: 50, author: author1, book: book1).save(flush: true)
which is acceptable to me. But then what is the benefits of having hasMany association in the Author and Book class? Any better way to do this? Ideally, something like following would be cool
def author1 = new Author(name: 'author1').addToBooks(book: new Book(title: 'book1'),
royalty: 50).save(flush: true)
There are some changes that I would like to recommend,specifically in your AuthorBooks domain:
class Author {
String name
static hasMany = [authorBookses: AuthorBooks]
}
class Book {
String title
static hasMany = [authorBookses: AuthorBooks]
}
class AuthorBooks implements Serializable {
Integer royalty
Author author
Book book
int hashCode() {
def builder = new HashCodeBuilder()
builder.append author.id
builder.append book.id
builder.toHashCode()
}
boolean equals(other) {
if (other == null) return false
def builder = new EqualsBuilder()
builder.append author.id, other.author.id
builder.append book.id, other.book.id
builder.isEquals()
}
static belongsTo = [author: Author, book: Book]
static mapping = {
id composite: ["author", "book"]
version false
}
}
Having additional ids for book and author is redundant. I'm not 100% sure about what I'm going to suggest, but it would be worth of trying the following:
def author1 = new Author(name: 'author1', authorBookses: new HashSet())
def book1 = new Book(title: 'book1', authorBookses: new HashSet())
def authorbook1 = new AuthorBooks(royalty: 50, author: author1, book: book1)
author1.authorBookses(authorbook1)
book1.authorBookses(authorbook1)
authorbook1.save(flush: true)

Inheritance and data binding on Grails

I have a problem to use data binding with inherit models. That's my code:
class Member {
int age
static belongsTo = [Family]
static constraints = {}
}
class Parent extends Member {
String name
static constraints = {}
}
class Family {
String name
List members
static hasMany = [members: Member]
static constraints = {}
}
def test(){
def bindingMap = [name: 'toto', members:[[age: 18, name: '1'],[age: 18]]]
def family = new Family()
family.properties = bindingMap
family.save(flush: true)
def get = Family.get(family.id)
// here family only contains Member and no Parent, as expected...
}
With this code, as you can see, I'm not able to create Parent with data binding.
Anyone have an idea?
Thanks

Adding multiple childs belonging to a parent at once

If I have two domains as below
class Author {
static hasMany = [books: Book]
String name
}
class Book {
static belongsTo = [author: Author]
String color
}
How can I add multiple books for an author at the same time?
like Below:
def book1 = new Book(color: "white")
def book2 = new Book(color: "black")
def books = []
books << book1
books << book2
def author = new Author(name: "John Doe").addToBooks(books).save()
addToBooks takes either a Book instance or a map that can be used to create a Book instance. This is relatively compact:
def book1 = new Book(color: "white")
def book2 = new Book(color: "black")
def books = []
books << book1
books << book2
def author = new Author(name: "John Doe")
books.each { author.addToBooks(it) }
author.save()

grails domain dirtyPropertyNames for child object

def names = domain.dirtyPropertyNames
for (name in names) {
def originalValue = domain.getPersistentValue(name)
def newValue = domain."$name"
}
But if i have a relation of 1-1 with other domain
how can i access dirtyPropertyNames for that other domain
def dirtyProperties = domain?.otherDomain?.dirtyPropertyNames
for (name in dirtyProperties ) {
def originalValue = domain?.otherDomain?.getPersistentValue(name)
def newValue = domain?.otherDomain?."$name"
}
But i am getting
No such property: dirtyPropertyNames for class: otherDomain
This seems not to be an issue when tested against Grails 2.2.4 and 2.3.0.
How have you tailored the 1:1 relationship?
Here is a sample, hope that helps:
class Book {
String name
String isbn
static hasOne = [author: Author]
}
class Author {
String name
String email
Book book
}
//Save new data
def book = new Book(name: 'Programming Grails', isbn: '123')
book.author = new Author(name: "Burt", email: 'test', book: book)
book.save(flush: true)
//Sanity check
println Book.all
println Author.all
//Check dirty properties of association
def book = Book.get(1)
book.author.name = 'Graeme'
def dirtyProperties = book?.author?.dirtyPropertyNames
for (name in dirtyProperties ) {
println book?.author?.getPersistentValue(name) //Burt
println book?.author?."$name" //Graeme
}
Although, wrt Grails 2.3.0 you can persist a 1-1 relation as done below unlike above:
def author = new Author(name: "Burt", email: 'test')
def book = new Book(author: author, name: 'PG', isbn: '123').save(flush: true)

Grails criteria select when hasMany hasn't any elements

I have the classes:
class Course{
String name
static hasMany = [
studentGrades: StudentGrade
]
}
class StudentGrade{
String name
int grade
}
How can I make a criteria to get the courses without any student grade?
You could use the isEmpty criterion method:
def c = Course.createCriteria()
def results = c.list {
isEmpty("studentGrades")
}
See the docs for further informations.

Resources