Grails - Trying to remove objects, but they come back - grails

I have two domain classes, Job and Description, in a simple one to many relationship:
Job.groovy
class Job {
static hasMany = [descriptions: Description]
static mapping = {
descriptions lazy: false
}
}
Description.groovy
class Description {
static belongsTo = [job: Job]
}
I have two controller actions:
def removeDescriptionFromJob(Long id) {
def jobInstance = Job.get(id)
System.out.println("before remove: " + jobInstance.descriptions.toString())
def description = jobInstance.descriptions.find { true }
jobInstance.removeFromDescriptions(description)
jobInstance.save(flush: true)
System.out.println("after remove: " + jobInstance.descriptions.toString())
redirect(action: "show", id: id)
}
def show(Long id) {
def jobInstance = Job.get(id)
System.out.println("in show: " + jobInstance.descriptions.toString())
}
This is what gets printed when I submit a request to removeDescriptionFromJob:
before remove: [myapp.Description : 1]
after remove: []
in show: [myapp.Description : 1]
Why does the Description get removed in the removeDescriptionFromJob action, only to come back immediately afterwards in the show action?

removeFromDescriptions did remove the relationship of jobInstance from description, but the entity description still exists.
System.out.println("after remove: " + jobInstance.descriptions.toString())
would result no o/p since the relation is non-existing. In place of the above line try to get the description object directly like:
System.out.println("after remove: " + Description.get(//id you just disassociated))
You would get the description successfully.
To avoid that, you need to add the below mapping property to Job to remove all the orphans once an entity is detached from parent.
descriptions cascade: "all-delete-orphan"
Like:
class Job {
static hasMany = [descriptions: Description]
static mapping = {
descriptions lazy: false, cascade: "all-delete-orphan"
}
}
GORM Bible - A must-read.

Related

Grails Reusable Service for saving Domain Objects

I have a Grails project with multiple Domain Classes, and I want to make a persistence service as reusable as possible by only having one save() inside of it. To try and achieve this I have done the following in my project.
//PersistenceService.groovy
#Transactional
class PersistenceService {
def create(Object object) {
object.save flush: true
object
}
//BaseRestfulController
class BaseRestfulController extends RestfulController {
def persistenceService
def save(Object object) {
persistenceService.create(object)
}
//BookController
class BookController extends BaseRestfulController {
private static final log = LogFactory.getLog(this)
static responseFormats = ['json', 'xml']
BookController() {
super(Book)
}
#Transactional
def save(Book book) {
log.debug("creating book")
super.save(book)
}
So basically I have a bunch of domains for example Author etc, each with their own controller similar to the bookController. So is there a way to reuse the service for persistence like I am trying above?
Thank you
I'm doing something similar, but mainly because all my entities are not actually removed from the database but rather "marked" as removed. For several apps you need such an approach since it's critical to prevent any kind of data loss.
Since most databases do not provide support for this scenario, you can't rely on foreign keys to remove dependent domain instances when removing a parent one.
So I have a base service class called GenericDomainService which has methods to save, delete (mark), undelete (unmark).
This service provides a basic implementation which can be applied to any domain.
class GenericDomainService {
def save( instance ) {
if( !instance || instance.hasErrors() || !instance.save( flush: true ) ) {
instance.errors.allErrors.each {
if( it instanceof org.springframework.validation.FieldError ) {
log.error "${it.objectName}.${it.field}: ${it.code} (${it.rejectedValue})"
}
else {
log.error it
}
}
return null
}
else {
return instance
}
}
def delete( instance, date = new Date() ) {
instance.dateDisabled = date
instance.save( validate: false, flush: true )
return null
}
def undelete( instance ) {
instance.dateDisabled = null
instance.save( validate: false, flush: true )
return null
}
}
Then, in my controller template I always declare two services: the generic plus the concrete (which may not exist):
def ${domainClass.propertyName}Service
def genericDomainService
Which would translate for a domain called Book into:
def bookService
def genericDomainService
Within the controller methods I use the service like:
def service = bookService ?: genericDomainService
service.save( instance )
Finally, the service for a given domain will inherit from this one providing (if needed) the custom logic for these actions:
class BookService extends GenericDomainService {
def delete( instance, date = new Date() ) {
BookReview.executeUpdate( "update BookReview b set b.dateDisabled = :date where b.book.id = :bookId and b.dateDisabled is null", [ date: date, bookId: instance.id ] )
super.delete( instance, date )
}
def undelete( instance ) {
BookReview.executeUpdate( "update BookReview b set b.dateDisabled = null where b.dateDisabled = :date and b.book.id = :bookId", [ date: instance.dateDisabled, bookId: instance.id ] )
super.undelete( instance )
}
}
Hope that helps.

grails: sort by nested attributes

Is it possible to sort by nested attributes using where queries?
I have 2 domain classes:
class Parent {
String name
Child child
}
and
class Child {
String name
static belongsTo = [parent: Parent]
}
This works:
Parent.where {}.list(sort: 'name')
and this doesn't:
Parent.where {}.list(sort: 'child.name')
I have an error:
could not resolve property: child.name of: Parent
I am using grails 2.3.x
See this: Grails - sort by the domain relation attribute (using createCriteria())
Solution 1:
def criteria = Child.createCriteria();
println criteria.list{
createAlias("parent","_parent")
order( "_parent.name")
}
Solution 2:
def criteria = Child.createCriteria();
println criteria.list{
parent {
order("name")
}
}
Solution 3:
class Child {
String name
static belongsTo = [parent: Parent]
public String getParentName(){
return parent.getName()
}
}
println Child.listOrderByParentName()
Hope it helps.

Grails clear hasMany entries and add new ones error?

I am currently working on a grails applications and I have a list of addresses that are attached to an account. Basically what I want to do is when the Account is edited is displays a current list of all the attached Addresses and then I can delete/add as many as I want from the view. When this data is captured it is picked up by the controller and what I want to do is to be able to clear all of the current Addresses from this account and then create the list again with what exists on the view, my code is below:
Account Domain:
class Account {
String name
Date dateCreated
Date lastUpdated
static hasMany = [addresses:Addresses]
static mapping = {
addresses cascade:"all-delete-orphan"
}
def getAddressesList() {
return LazyList.decorate(
addresses,
FactoryUtils.instantiateFactory(Addresses.class))
}
static constraints = {
name(blank:false, unique: true)
}
}
Address Domain:
class Addresses {
int indexVal
String firstLine
String postcode
String area
static belongsTo = [account:Account]
static mapping = {
}
static transients = [ 'deleted' ]
static constraints = {
indexVal(blank:false, min:0)
}
}
Account Controller:
def update() {
def accountInstance = Account.get(params.id)
if (!accountInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'account.label', default: 'Account'), params.id])
redirect(action: "list")
return
}
if (params.version) {
def version = params.version.toLong()
if (accountInstance.version > version) {
accountInstance.errors.rejectValue("version", "default.optimistic.locking.failure",
[message(code: 'subscriptions.label', default: 'Subscriptions')] as Object[],
"Another user has updated this Account while you were editing")
render(view: "edit", model: [accountInstance: accountInstance])
return
}
}
accountInstance.properties = params
accountInstance.addresses.clear()
accountInstance.save(flush: true)
....
}
Error:
A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: com.tool.Account.addresses. Stacktrace follows:
Message: A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: com.tool.Account.addresses
This error seems to be occurring in the controller on line:
accountInstance.save(flush: true)
I have tried several different way to get this to work and would really appreciate some help.
So it seems like you have done some work that Grails can do for you.
class Account {
String name
Date dateCreated
Date lastUpdated
List addresses
static hasMany = [addresses:Address]
static mapping = {
addresses cascade:"all-delete-orphan"
}
static constraints = {
name(blank:false, unique: true)
}
}
class Address {
String firstLine
String postcode
String area
static belongsTo = [account:Account]
}
This will produce the effect you want of having addresses being a list.
I've found either
instance.addresses = null
or
instance.addresses.clear()
to work for me
When you define addresses cascade:"all-delete-orphan" in Account class you don't need static belongsTo = [account:Account] in Addresses. So just try to remove that statement and test your code. See related link.

Grails set default value of scaffolded dropdown box

Is it possible to set the default value for a generated (scaffolded) State dropdown box in Grails? I am hoping that this is something I can do within either the domain class or the controller class:
class State {
String name
String code
static mapping = { sort name: "asc" }
String toString() {
return this.name;
}
}
class Person implements Comparable {
String firstName
String lastName
State state
String toString() {
return this.lastName + ", " + this.firstName;
}
}
I have tried explicitly setting String name="Pennsylvania" and String code="PA" in the State domain class with no luck. I am bootstrapping the State data in Bootstrap.groovy:
def init = { servletContext ->
if (!State.count()) {
new State(name: "Alabama", code:"AL").save(failOnError: true)
new State(name: "Alaska", code:"AK").save(failOnError: true)
new State(name: "Arizona", code:"AZ").save(failOnError: true)
new State(name: "Arkansas", code:"AR").save(failOnError: true)
....
}
}
Updated:
PersonController.groovy:
def create() {
// [personInstance = new Person(params)]
def personInstance = new Person(params)
personInstance.state = new State("Pennsylvania", "PA")
[personInstance: personInstance]
}
State.groovy:
public State(String name, String code) {
this.name = name
this.code = code
}
If you're trying to set the default value of the state dropdown for the "new Person" page, then you would probably want to make the default value of Person's 'state' member be the 'Pennsylvania' state that you create in Bootstrap.
Actually, I think that a better way to do this would be to actually generate the PersonController, and then modify the 'create' closure to set the initial value of Person.state

Why One-to-one relationship dosen't work?

I'm trying to create a very simple relationship between two objects. Can anybody explain me why I can't find the Company object via findBy method?
class Company {
String name
String desc
City city
static constraints = {
city(unique: true)
}
}
class City {
String name
static constraints = {
}
}
class BootStrap {
def init = { servletContext ->
new City(name: 'Tokyo').save()
new City(name: 'New York').save()
new Company(name: 'company', city: City.findByName('New York')).save()
def c = Company.findByName('company') // Why c=null????!
}
def destroy = {
}
}
A field called desc conflicts with the database keyword for descending sort. Per default a field is nullable:false in Grails. So first rename that field to for example description and then provide one or mark that field as nullable:true in your constraints.
class BootStrap {
def init = { servletContext ->
new City(name: 'Tokyo').save()
new City(name: 'New York').save()
new Company(name: 'company', city: City.findByName("New York")).save()
assert Company.findByName('company') != null
}
}
Remember that you can always check for the errors that prevent Grails from saving your objects to the database easily:
def invalidCompany = new Company() // missing required name property
if (!invalidCompany.validate())
invalidCompany.errors.each { println it }

Resources