Adding multiple childs belonging to a parent at once - grails

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()

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)

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

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)

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)

Deserialize a JSON object with support for embedded associations

Is there an easy way to deserialize a JSON string to a domain class with support of embedded association; belongsTo and hasMany
{
name: "Customer",
contact: {
name: "Contact"
}
}
class Customer {
name
Contact contact
}
class Contact {
String name
static belongsTo = [customer:Customer]
}
in my controller I would like to do the following
def save() {
def customer = new Customer(request.JSON)
customer.save();
}
Now i'm forced to do
def save() {
def contact = new Contact(request.JSON.contact);
def customer = new Customer(request.JSON);
customer.contact = contact;
customer.save();
}
Have you tried using JsonSlurper?
Example usage:
def slurper = new JsonSlurper()
def result = slurper.parseText('{"person":{"name":"Guillaume","age":33,"pets":["dog","cat"]}}')
assert result.person.name == "Guillaume"
assert result.person.age == 33
assert result.person.pets.size() == 2
assert result.person.pets[0] == "dog"
assert result.person.pets[1] == "cat"
Ref: http://groovy.codehaus.org/gapi/groovy/json/JsonSlurper.html
you can try this
Test test
def result = new JsonSlurper().parseTest('yourString')
test = result
Try this will work.

How do I save GORM objects with multiple many-to-one relationships?

Let's say I have the following hierarchy of domain classes.
class School {
String name
static hasMany = [teachers: Teacher, students: Student]
}
class Teacher {
String name
static belongsTo = [school: School]
static hasMany = [students: Student]
}
class Student {
String name
static belongsTo = [school: School, teacher: Teacher]
}
I tried two different ways to save a school, teacher, and student.
Attempt 1:
def school = new School(name: "School").save()
def teacher = new Teacher(name: "Teacher", school: school).save()
def student = new Student(name: "Student", school: school, teacher: teacher).save(flush: true)
It appears to save properly but when I run:
println(school.students*.name)
It prints null.
So I decided to try a different approach.
Attempt 2:
def school = new School(name: "School")
def teacher = new Teacher(name: "Teacher")
def student = new Student(name: "Student")
teacher.addToStudents(student)
school.addToStudents(student)
school.addToTeachers(teacher)
school.save(failOnError: true, flush: true)
Here I tried several combinations of saves and I always got an error about a required field being null. In this case the error was
JdbcSQLException: NULL not allowed for column "TEACHER_ID"
I would greatly appreciate if someone could explain why my attempts failed and what the proper way to go about creating the data is.
def school = new School(name: "School").save(flush: true)
def teacher = new Teacher(name: "Teacher")
school.addToTeachers(teacher)
teacher.save(flush: true)
def student = new Student(name: "Student", teacher: teacher)
school.addToStudents(student)

Resources