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
Very Nice. Thank you.