grails composite key update - grails

I have a legacy database and I'm using Grails. Mistake #1.
It uses compound keys. Mistake #2.
Given these domain classes:
Movie {
static hasMany = [ roles: Role ]
}
Person {
static hasMany = [ roles: Role ]
}
Role {
Movie movie
Person person
String foo
}
I want to move roles from one person to another, like so:
Role x = person1.roles[0]
x.person = person2
save(flush:true)
But nothing happens. At all. I've turned on trace and debug level logging for hibernate in log4j and it shows no update statement. But, if I do this:
Role x = person1.roles[0]
x.person = person2
x.foo = "i can haz update?"
save(flush:true)
An update does happen for foo, but the foreign key pointing to person is not modified, as shown by:
DEBUG hibernate.SQL - update ct_roles set foo=? where movie_id=? and person_id=?
TRACE sql.BasicBinder - binding parameter [1] as 'i can haz update?'
TRACE sql.BasicBinder - binding parameter [2] as [BIGINT] - 999
TRACE sql.BasicBinder - binding parameter [3] as [BIGINT] - 2
Note that person_id 2 belongs to person2, which as yet, has no roles, so the update fails.
So, short of simply deleting the old role and creating a new one attached to the desired person, is there any way to solve this?

Try:
person1.removeFromRoles(role) //role is the role object from which person is to be removed
role.person = person2
Should work.

The answer is:
class Role {
Movie movie
Person person
String fuGorm
def move(Person newbie) {
def m = this.movie?.id
def p = this.person?.id
def n = newbie?.id
// bypass GORM and issue a raw SQL update command...
def q = "update ct_roles set person_id=$n where person_id=$p and movie_id=$m"
def success = runCommand(q)
if (success) {
// the above bypasses GORM, so we should still
// perform the operation using GORM speak
// otherwise our objects will be out of sync with the DB.
// The following EPIC FAILS to update the DB,
// but it does update the local Grails domain objects
this.person.removeFromRoles(this)
this.person = newbie
newbie.addToRoles(this)
}
return success
}
def runCommand = { query ->
def db = new Sql(dataSource)
db.execute (query)
}
}

Related

Criteria in relation between entities - Grails

I have an app with the following entities:
Topic:
class Topic {
UUID id
String description
String name
boolean visibility = true
// Relation
static hasMany = [tests:Test]
...
}
Test:
class Test {
UUID id
boolean active = true
String description
...
static hasMany = [evaluationsTest: Evaluation]
static belongsTo = [topic: Topic, catalog: Catalog]
}
When I show all visible topics to the user I request the query:
def visibleTopics = Topic.findAllByVisibility(true, [sort:"name", order:"asc"])
This query returns me for example: [['English'], ['Spanish']]. Then, I can show the full information about each topic to the user.
But I also want to indicate to the user the number of active test in each visible topic.
For example:
English topic has 2 active test.
Spanish topic has a total of 2 test. One is active and the other is not.
German topic has not any active test.
Then I need a query whose result is: def activeTotalEachTopic = [[2],[1],[0]] and I can pass the activeTotalEachTopic variable to the view (.gsp).
Solution:
From the first query where I can obtain all visible topics, I get the number of active test.
def visibleTopics = Topic.findAllByVisibility(true, [sort:"name", order:"asc"])
def numberActiveTest = []
activeTopics.each { topic ->
def result = Test.findAllByTopicAndActive(topic, true).size()
numberActiveTest.push(result)
}
And I pass to the view both variables.
render view: 'home', model: [activeTopics: activeTopics, numberActiveTest: numberActiveTest]
What you are missing is grouping so that you get the count per group, rather than a total count.
You also need to change the join type from the default inner join to an outer join in order for topics without an active test to return 0. However, a side-effect of this is that it changes how association properties are referenced due to the alias that's created by the join. Something like this:
import static org.hibernate.sql.JoinType.*
def activeTotalEachTopic = Topic.createCriteria().list() {
createAlias('tests', 't', LEFT_OUTER_JOIN)
eq 'visibility', true
or {
eq 't.active', true
isNull 't.id'
}
projections {
groupProperty 'name'
count()
}
order ("name", "asc")
}
Now, another issue to address is that the output would be something like this due to the grouping: [['English', 2],['Spanish', 1],['German', 0]]. So what you can do is collect the second item in each sub-list:
activeTotalEachTopic*.getAt(1)
// Which is the equivalent of...
activeTotalEachTopic.collect { it[1] }

How to save addTo in order by given date/id?

I need some help on my API, when I'm on web, the order is saving correct, but when its on API, it goes all wrong:
def test = parseJSON.sort { a, b -> a.ID <=> b.ID } //or dateTime, will print the same
//order when I print each of them
[IDWeb:0, conductivity:0, ReportId:2, dissolvedOxygen:0, levelWater:1, ID:1, ph:0, redoxPotential:0, temperature:0]
[IDWeb:0, conductivity:0, ReportId:2, dissolvedOxygen:0, levelWater:0, ID:2, ph:0, redoxPotential:0, temperature:0]
[IDWeb:0, conductivity:0, ReportId:2, dissolvedOxygen:0, levelWater:0, ID:3, ph:0, redoxPotential:0, temperature:0]
[IDWeb:0, conductivity:0, ReportId:2, dissolvedOxygen:0, levelWater:4, ID:4, ph:0, redoxPotential:0, temperature:0]
test.each{
def sample = new SampleWater()
sample.levelWater = it.levelWater
sample.conductivity = it.conductivity
sample.dissolvedOxygen = it.dissolvedOxygen
sample.redoxPotential = it.redoxPotential
sample.ph = it.ph
sample.temperature = it.temperature
water.addToSamples(sample)
}
return water
My problem is that addTo is not saving in order. How can I solve this?
Make sure you have defined the type of samples as a List in your Water domain class so that we can maintain the insertion order:
class Water {
static hasMany = [samples: Sample]
List<Sample> samples = []
}
class Sample {
def levelWater
}
By default implementation of hasMany is of type Set which does not maintain the insertion order but is responsible for uniqueness.
Since, now you samples will be saved in the same order as they are inserted.
You have to specify with order you want to apply to the list of SampleWater in the "water" domain class.
i.e:
class BlogCategory {
static hasMany = [
entries : BlogEntry
]
static mapping = {
entries: sort:'dateCreated', order:'desc'
}
}
In this example BlogEntry will be ordered respect dateCreated.

Index out of bound when use data binding to update many-to-many relation

I have problem for updating domain that has many-to-many relation. For instance, consider these 2 simple domains.
class Student {
String name
static hasMany = [courses: Course]
}
class Course {
String name
static hasMany = [students: Student]
static belongsTo = [Student]
}
To update student's name along with his/her courses' name, I use data binding like this:
def params = [
'courses[0].id': c2.id,
'courses[0].name': 'c11',
'courses[1].id': c1.id,
'courses[1].name': 'c22'
]
s1.properties = params
s1.save(flush: true)
However, this will cause error:
org.springframework.beans.InvalidPropertyException: Invalid property 'courses[1]' of bean class [tb.Student]:
Invalid list index in property path 'courses[1]'; nested exception is java.lang.IndexOutOfBoundsException:
Index: 1, Size: 1
After some searching, I found that all answers suggest to use List for relation instead of Set. However, I still prefer to use Set.
Environment
Grails: 2.2.3
Java: 1.6.0_45
OS: Ubuntu 13.04
My solution is to clear the children list before data binding. This is the full code to test above domains. The line s1.courses.clear() will prevent above error.
def s1 = new Student(name: 's1').save(flush: true)
def s2 = new Student(name: 's2').save(flush: true)
def c1 = new Course(name: 'c1').save(flush: true)
def c2 = new Course(name: 'c2').save(flush: true)
s1.addToCourses(c1)
s1.addToCourses(c2)
s1.save(flush: true)
def params = [
'courses[0].id': c2.id,
'courses[0].name': 'c11',
'courses[1].id': c1.id,
'courses[1].name': 'c22'
]
s1.courses.clear()
s1.properties = params
s1.save(flush: true)
However, I still think this problem is a bug. And my solution is a work around.
A Set isn't ordered hence it will fail if you specify an index.
If you don't want to use List, try to use SortedSet instead.
You can find more info # (http://grails.org/doc/latest/guide/single.html#ormdsl) 6.5.3 Default Sort Order

Benefits of using Insert flag in Grails save()

What are the benefits of using the grails insert flag when saving a domain class?
Here is an example:
Lets say I have a Domain Object FooBar:
FooBar foo = FooBar.find("foo")?: new FooBar(id:"foo")
foo.bar = "bar"
foo.save()
Would it be better do do something more like this:
boolean insertFlag
FooBar foo = FooBar.find("foo")
if(foo == null){
insertFlag = false
}else {
foo = new FooBar(id:"foo")
insertFlag = true
}
foo.bar = "bar"
foo.save(insert: insertFlag)
I was thinking that the save would run smoother somehow with the insert flag verses not having it.
insert inside save is highly useful if you have the id generator for a domain class as assigned. In that case the id has to be assigned by the user.
This is a way to inform hibernate that whether you want to insert a record or just want to update.
class FoofBar{
String bar
static mapping = {
id generator: 'assigned'
}
}
def fooBar = new FooBar(bar: 'foo')
fooBar.id = 100
fooBar.save() //inserts a record with id = 100
def secondFooBar = FooBar.get(100)
secondFooBar.id = 200
//want to insert as a new row instead of updating the old one.
//This forces hibernate to use the new assigned id
fooBar.save(insert: true)
This will make it clear.

where is bidirectional ? grails one-to-one, testing bidirectional (continue)

class Book {
String title
Date releaseDate
String ISBN
static belongsTo = [person:Person] // it makes relationship bi-directional regarding the grails-docs
}
class Person {
Book book; // it will create person.book_id
String name
Integer age
Date lastVisit
static constraints = {
book unique: true // "one-to-one". Without that = "Many-to-one".
}
}
There is a test which test if it is real bidirectional or not. As i understand it.
public void testBidirectional() {
def person = new Person(name:"person_c1", age: 99, lastVisit: new Date())
def book = new Book(
title:"somebook_c1",
ISBN: "somebook_c1",
releaseDate: new Date()
)
person.setBook (book)
assertNotNull(person.save())
def bookId = person.getBook().id
Book thatBook = Book.get(bookId)
assertNotNull(thatBook.person) // NULL !!!
}
So, i save a person with a book, and then i got that book from db by id. Then from that book i try to get back the person which book should refer to (because it should be bidirectional, right?). Eventually i got null instead of an instance of the person.
The questing is: how to make that test working?
i have found the solution how to get it working, but still can not understand why it does not work without 'refresh', see below:
public void testBidirectional() {
def person = new Person(name:"person_c1", age: 99, lastVisit: new Date())
def book = new Book(
title:"somebook_c1",
ISBN: "somebook_c1",
releaseDate: new Date()
)
person.setBook (book)
def p = person.save()
assertNotNull p
person.refresh() //load the object again from the database so all the changes made to object will be reverted
//person = Person.get(p.id) // BUT this also gets the object from db ...?
def bookId = person.getBook().id
assertNotNull bookId
def thatBook = Book.get(bookId)
assertNotNull(thatBook.person)
}
So, here as you can see i use 'refresh' to get it working, but why it does not work without 'refresh' but with the following line after 'refresh' - this one:
person = Person.get(p.id) // BUT this also gets the object from db ...?
If i just want to get object from database by id, then it would be without bidirectional?
Your problem is probably caused by the way that Hibernate works. Grails used Hibernate under the hood.
Even when you call "save", the object person may (and usually) not saved in database. That's because Hibernate is programmed to optimize the query, so it often waits to perform all query at then end of the Hibernate session.
That means if you don't call "refresh", the book-person relation (person.setBook) is still in memory, but not saved in database. Hence you can't get the book.person from book.
To enforce the save, you can use "refresh" like the previous answer, or use flush:true.
I still not try, but it's very likely that you will produce desired results with:
person.save(flush:true)

Resources