It just takes 15 seconds. Send your
details and we’ll get back to you soon.

Implementing saveOrUpdate() for domain classes

Some times in our applications, while saving a domain object we may desire to have a save or update behavior on the basis of certain fieldSupp

Suppose we have a domain class Personwith following definition:

class Person{
String name
String source
String description

def static saveOrUpdateBy = ['name', 'source']

} 

Details of a person are coming from multiple sources at multiple intervals of time. We want to maintain the latest records from each source at any given time.

We want to saveOrUpdate a person if the name and source are same. So, If we happen to execute following three lines of code…

new Person(name: 'Alex', source: 'source-1', 'Hello').saveOrUpdate();
 new Person(name: 'Alex', source: 'source-1', 'Hi').saveOrUpdate();
 new Person(name: 'Alex', source: 'source-1', 'Wow').saveOrUpdate();

By the end of last line we would like to have only one row in the table corresponding to Person domain. Because we want to saveOrUpdate() on the basis of name , source combination.

We can have  a metaclass method injected in all our domain classes to have this method. The properties to be considered while doing saveOrUpdate can be specified through a static field in class.. Some thing like

def static saveOrUpdateBy = ['name', 'source']

The following is the piece of code for achieving the same:

Fetching the static property of the domain class.

//GrailsDomainClass
 application.domainClasses.each {
 domainClass->
 List saveOrUpdateBy = GrailsClassUtils.getStaticPropertyValue(domainClass.clazz, 'saveOrUpdateBy')
 if(saveOrUpdateBy)
 {
 SaveOrUpdatePlugin.addSOUToDomainClass(domainClass, saveOrUpdateBy)
 }
 }

A method to add a meta-method saveOrUpdate to given domain classes.


class SaveOrUpdatePlugin {

 static void addSOUToDomainClass(GrailsDomainClass domainClassObject, List saveOrUpdateBy) {
 println "Adding saveOrUpdate method to $domainClassObject.clazz"
 Class domainClass = domainClassObject.clazz
 domainClass.metaClass.saveOrUpdate = {
 def savedInst
 try {
 log.debug "[SAVE OR UPDATE] Trying to do saveOrUpdate for ${SaveOrUpdatePlugin.getToStringForGivenFields(delegate, saveOrUpdateBy)}"

 def inst = domainClass.findWhere(delegate.properties.subMap(saveOrUpdateBy))

 if (!inst) {
 synchronized (domainClass) {
 inst = domainClass.findWhere(delegate.properties.subMap(saveOrUpdateBy))

 if (!inst) {
 log.info("[SAVE OR UPDATE] Saving a new object ${SaveOrUpdatePlugin.getToStringForGivenFields(delegate, saveOrUpdateBy)}")
 if (!delegate.validate()) {
 delegate.errors.each {
 log.error "Error while saving scrapedMovie  $it"
 }
 }

 if (!delegate.save(flush: true)) {
 delegate.errors.each {
 log.error it
 }
 }
 log.debug("[SAVE OR UPDATE] Saved a new object with id ${delegate.id} - ${SaveOrUpdatePlugin.getToStringForGivenFields(delegate, saveOrUpdateBy)}")
 }
 }
 }
 else {
 log.info "[SAVE OR UPDATE] matching object found: ${SaveOrUpdatePlugin.getToStringForGivenFields(inst, saveOrUpdateBy)}"
 long id = inst.id
 //inst.properties = delegate.properties
 SaveOrUpdatePlugin.copyProperties(domainClassObject, delegate, inst)
 log.info "[SAVE OR UPDATE] Updating an existing object ${SaveOrUpdatePlugin.getToStringForGivenFields(inst, saveOrUpdateBy)}"
 if (!inst.save(flush: true)) {
 inst.errors.each {
 log.error it
 }
 }
 log.debug("[SAVE OR UPDATE] Updated existing object with id ${inst.id} - ${SaveOrUpdatePlugin.getToStringForGivenFields(inst, saveOrUpdateBy)}")
 }

 savedInst = domainClass.findWhere(delegate.properties.subMap(saveOrUpdateBy))

 //returning the saved instance...

 } catch (Exception ex) {
 log.error("Failed to saveOrUpdate the object ${delegate} ", ex)
 }
 return savedInst
 }
 }

 static String getToStringForGivenFields(def object, List fields) {
 StringBuilder sb = new StringBuilder()
 sb.append(object.toString() + " SaveOrUpdateBy: ")
 fields.each {
 sb.append(", ${it}=")
 sb.append(object."$it")
 }
 sb.toString().replaceFirst(', ', '')
 }

 static void copyProperties(GrailsDomainClass domainClassObject, def source, def target) {
 domainClassObject.persistantProperties.each {prop ->
 if (!(prop.name in ['dateCreated', 'lastUpdated', 'id'])) {
 target."${prop.name}" = source."${prop.name}"
 //println "adding property ${prop.name}"
 }
 }
 }
}
Hope this would help someone in need.

Any comments / suggestion for improvement of the same are most welcome.

Thanks & Regards
Mohd Farid
farid@intelligrape.com


This entry was posted in GORM, Grails, Plugin. Bookmark the permalink.

One Response to Implementing saveOrUpdate() for domain classes

  1. Craig Petty says:

    Very Nice. Thank you.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>