I want to make a copy of a domain object. What is the simplest way to accomplish this?
I realize I could create a new record, and then iterate over each of the fields copying the data field-by-field - but I figured there must be an easier way to do this...
In Rails there is a simple way to do this:
#rails < 3.1
new_record = old_record.clone
#rails >= 3.1
new_record = old_record.dup
Is there any equivalent in Grails?
I've adapted a piece of code that make the deep clone of domain classes. I've been using in my system and it works very well (in most of cases). The code bellow is an adaptation found in http://grails.1312388.n4.nabble.com/Fwd-How-to-copy-properties-of-a-domain-class-td3436759.html
In my application the user has the option to saveAs some type of objects and I use the deepClone to do that.
You can specify "not cloneable" properties. For that you need to specify a static map (in your class) with the properties that you don't want to clone, for example:
static notCloneable = ['quoteFlows','services']
static hasMany = [quotePacks: QuotePack, services: Service, clients: Client, quoteFlows: QuoteFlow]
static Object deepClone(domainInstanceToClone) {
//TODO: PRECISA ENTENDER ISSO! MB-249 no youtrack
//Algumas classes chegam aqui com nome da classe + _$$_javassist_XX
if (domainInstanceToClone.getClass().name.contains("_javassist"))
return null
//Our target instance for the instance we want to clone
// recursion
def newDomainInstance = domainInstanceToClone.getClass().newInstance()
//Returns a DefaultGrailsDomainClass (as interface GrailsDomainClass) for inspecting properties
GrailsClass domainClass = domainInstanceToClone.domainClass.grailsApplication.getDomainClass(newDomainInstance.getClass().name)
def notCloneable = domainClass.getPropertyValue("notCloneable")
for(DefaultGrailsDomainClassProperty prop in domainClass?.getPersistentProperties()) {
if (notCloneable && prop.name in notCloneable)
continue
if (prop.association) {
if (prop.owningSide) {
//we have to deep clone owned associations
if (prop.oneToOne) {
def newAssociationInstance = deepClone(domainInstanceToClone?."${prop.name}")
newDomainInstance."${prop.name}" = newAssociationInstance
} else {
domainInstanceToClone."${prop.name}".each { associationInstance ->
def newAssociationInstance = deepClone(associationInstance)
if (newAssociationInstance)
newDomainInstance."addTo${prop.name.capitalize()}"(newAssociationInstance)
}
}
} else {
if (!prop.bidirectional) {
//If the association isn't owned or the owner, then we can just do a shallow copy of the reference.
newDomainInstance."${prop.name}" = domainInstanceToClone."${prop.name}"
}
// ##JR
// Yes bidirectional and not owning. E.g. clone Report, belongsTo Organisation which hasMany
// manyToOne. Just add to the owning objects collection.
else {
//println "${prop.owningSide} - ${prop.name} - ${prop.oneToMany}"
//return
if (prop.manyToOne) {
newDomainInstance."${prop.name}" = domainInstanceToClone."${prop.name}"
def owningInstance = domainInstanceToClone."${prop.name}"
// Need to find the collection.
String otherSide = prop.otherSide.name.capitalize()
//println otherSide
//owningInstance."addTo${otherSide}"(newDomainInstance)
}
else if (prop.manyToMany) {
//newDomainInstance."${prop.name}" = [] as Set
domainInstanceToClone."${prop.name}".each {
//newDomainInstance."${prop.name}".add(it)
}
}
else if (prop.oneToMany) {
domainInstanceToClone."${prop.name}".each { associationInstance ->
def newAssociationInstance = deepClone(associationInstance)
newDomainInstance."addTo${prop.name.capitalize()}"(newAssociationInstance)
}
}
}
}
} else {
//If the property isn't an association then simply copy the value
newDomainInstance."${prop.name}" = domainInstanceToClone."${prop.name}"
if (prop.name == "dateCreated" || prop.name == "lastUpdated") {
newDomainInstance."${prop.name}" = null
}
}
}
return newDomainInstance
}
There is not. It has been requested http://jira.grails.org/browse/GRAILS-3532. Someone has added some code to that issue that might be helpful to you though.
Related
I'm wondering how I would go about adding the functionality of cloning to my grails application. I've attached an image below that explains how my domain classes are associated. One template has many steps and those steps each have many inputs and or outputs.
Currently I can view my templates on the index.gsp page but I want to be able to clone entire templates along with their steps/inputs/outputs that they contain aswell.
Is this possible and if so how?
Here is a version of deep cloning. Though It's a bit customized to meet specific needs, it's very generic. And I'm pretty sure above said scenario is well covered by this.
Object deepClone(def domainInstanceToClone, def notCloneable) {
return deepClone(domainInstanceToClone, notCloneable, null)
}
Object deepClone(def domainInstanceToClone) {
return deepClone(domainInstanceToClone, null, null)
}
Object deepClone(def domainInstanceToClone, def notCloneable, def bindOriginal) {
if (domainInstanceToClone.getClass().name.contains("_javassist"))
return null
//Our target instance for the instance we want to clone
def newDomainInstance = domainInstanceToClone?.getClass()?.newInstance()
//Returns a DefaultGrailsDomainClass (as interface GrailsDomainClass) for inspecting properties
GrailsClass domainClass = domainInstanceToClone.domainClass.grailsApplication.getDomainClass(newDomainInstance.getClass().name)
for (DefaultGrailsDomainClassProperty prop in domainClass?.getPersistentProperties()) {
if (notCloneable && prop.name in notCloneable) {
continue
}
if (bindOriginal && prop.name in bindOriginal) {
newDomainInstance."${prop.name}" = domainInstanceToClone."${prop.name}"
continue
}
if (prop.association) {
if (prop.owningSide) {
//we have to deep clone owned associations
if (prop.oneToOne) {
def newAssociationInstance = deepClone(domainInstanceToClone?."${prop.name}", notCloneable, bindOriginal)
newDomainInstance."${prop.name}" = newAssociationInstance
} else {
domainInstanceToClone."${prop.name}".each { associationInstance ->
def newAssociationInstance = deepClone(associationInstance, notCloneable, bindOriginal)
if (prop.oneToMany) {
if (newAssociationInstance) {
newDomainInstance."addTo${prop.name.capitalize()}"(newAssociationInstance)
}
} else {
newDomainInstance."${prop.name}" = newAssociationInstance
}
}
}
} else {
if (!prop.bidirectional) {
//If the association isn't owned or the owner, then we can just do a shallow copy of the reference.
newDomainInstance."${prop.name}" = domainInstanceToClone."${prop.name}"
}
// ##JR
// Yes bidirectional and not owning. E.g. clone Report, belongsTo Organisation which hasMany
// manyToOne. Just add to the owning objects collection.
else {
//println "${prop.owningSide} - ${prop.name} - ${prop.oneToMany}"
//return
if (prop.manyToOne) {
newDomainInstance."${prop.name}" = domainInstanceToClone."${prop.name}"
def owningInstance = domainInstanceToClone."${prop.name}"
// Need to find the collection.
String otherSide = prop.otherSide.name.capitalize()
//println otherSide
//owningInstance."addTo${otherSide}"(newDomainInstance)
} else if (prop.manyToMany) {
//newDomainInstance."${prop.name}" = [] as Set
domainInstanceToClone."${prop.name}".each {
//newDomainInstance."${prop.name}".add(it)
}
} else if (prop.oneToMany) {
domainInstanceToClone."${prop.name}".each { associationInstance ->
def newAssociationInstance = deepClone(associationInstance, notCloneable, bindOriginal)
newDomainInstance."addTo${prop.name.capitalize()}"(newAssociationInstance)
}
}
}
}
} else {
//If the property isn't an association then simply copy the value
newDomainInstance."${prop.name}" = domainInstanceToClone."${prop.name}"
if (prop.name == "activationDate") {
newDomainInstance."${prop.name}" = new Date()
}
}
}
return newDomainInstance
}
Example usage is :-
Template cloneTemplate = cloneService.deepClone(originalTemplate,["id","name"],["parent"])
1st parameter is original object that is to be cloned
2nd parameter is the list of columns that must not be cloned
3rd parameter is list of properties that must be referenced as it is.e.g. Template might belong to some parent which must remain same during clone.
To save cloned object create another method that meets your custom requirements.Above code will work in other scenarios too.
I have a method which returns Iqueryable result, but the result is based on an if else condition, where if condition satisfies then I will use "AssetDetails" class object ,otherwise "UserandClientdetails" object.
Here is the code:
private IQueryable<?> GetAssetDetails(ShareViewModel item)
{
...
if (type == "Video")
{
if (type == "Video")
{
return from meta in my.Assets().OfType<Model.Video>()
join content in my.Contents() on meta.ContentId equals content.ID
join channel in my.Channels() on content.ChannelId equals channel.ID
where meta.ID == item.ID
select new AssetDetails
{
ContentTitle = content.Title,
ChannelName = channel.ChannelName,
...
};
}
else
{ return from meta in my.Assets().OfType<Model.Client>()
join country in db.Countries on meta.ResellerCountry equals country.ID
where meta.ID == item.ID
select new UserAndClientDetails
{
Name = meta.ResellerName,
UserName = meta.ResellerEmail,
..
};}
So how to decide type of Iqueyable here at runtime??
So, I was able to verify that this works, so I'll go ahead and post it as an answer.
You can return IQueryable instead of the generic IQueryable<>. That will accept any IQueryable<T>. However, IQueryable, since it has no direct inner type, is very limited. So, you'll still likely need to cast to IQueryable<> at some other point in your code to get anything done:
// Piece of code where you know you are working with `IQueryable<AssetDetails>`
IQueryable<AssetDetails> assetDetails = GetAssetDetails(someItem);
That's a little dangerous, though, as you're assuming that your code is working perfectly and the right type of thing is being returned. Better would be:
try
{
var assetDetails = (IQueryable<AssetDetails>)GetAssetDetails(someItem);
// do something with `assetDetails`
}
catch (InvalidCastException)
{
// recover gracefully
}
What about using a base class ?
public abstract class BaseDetails
{
// ...
}
public class AssetDetails : BaseDetails
{
// ...
}
public class UserAndClientDetails: BaseDetails
{
// ...
}
Then you method would be like :
private IQueryable<BaseDetails> GetAssetDetails(ShareViewModel item)
{
// return either IQueryable<AssetDetails> or IQueryable<UserAndClientDetails>
}
I have several domain classes for which the user interface includes
a "duplicate" command. As part of the implementation of those commands,
I have implemented clone() methods in the corresponding domain classes.
I have been trying to correct my bad habit of improperly implementing
clone() (in general) based on use of "new" rather than "super.clone(),"
so as soon as I thought about doing the same for my Grails domain
classes, I wondered how using super.clone() to obtain a clone might
interact with GORM / Hibernate persistence. In particular, I was
wondering about the proper way to handle the implicit "id" property. If
I simply super.clone(), do nothing further and later try to save() the
cloned instance, will it work properly (creating a new persistence
entry?) or will some kind of error or silent failure result?
What is the proper or preferred way to duplicate a Grails domain
instance?
Add the following method to metaClass of the interface GormInstanceApi which all domain implements it.
:
def cloneForDomains={def cloned=delegate.class.newInstance();
cloned.properties=delegate.properties;
return cloned;}
then :
org.grails.datastore.gorm.GormInstanceApi.clone=cloneForDomains ;
Congrats! now you can use clone method such as save , delete .....
USE CASE :
Person p=Person.get(1);
Person cloned=p.clone();
cloned.id=null;
cloned.save();
UPDATE : you can loop all domain classes also :
grailsApplication.getDomainClasses().each{cls->
cls.metaClass.clone=cloneForDomains
}
UPDATE : for deep clone :
grailsApplication.getDomainClasses().each{cls->
cls.metaClass.clone={
return deepClone(delegate);
}
}
known that deepClone is a method as following:
Object deepClone(domainInstanceToClone) {
//TODO: PRECISA ENTENDER ISSO! MB-249 no youtrack
//Algumas classes chegam aqui com nome da classe + _$$_javassist_XX
if (domainInstanceToClone.getClass().name.contains("_javassist"))
return null
//Our target instance for the instance we want to clone
// recursion
def newDomainInstance = domainInstanceToClone.getClass().newInstance()
//Returns a DefaultGrailsDomainClass (as interface GrailsDomainClass) for inspecting properties
GrailsClass domainClass = domainInstanceToClone.domainClass.grailsApplication.getDomainClass(newDomainInstance.getClass().name)
def notCloneable = domainClass.getPropertyValue("notCloneable")
for(DefaultGrailsDomainClassProperty prop in domainClass?.getPersistentProperties()) {
if (notCloneable && prop.name in notCloneable)
continue
if (prop.association) {
if (prop.owningSide) {
//we have to deep clone owned associations
if (prop.oneToOne) {
def newAssociationInstance = deepClone(domainInstanceToClone?."${prop.name}")
newDomainInstance."${prop.name}" = newAssociationInstance
} else {
domainInstanceToClone."${prop.name}".each { associationInstance ->
def newAssociationInstance = deepClone(associationInstance)
if (newAssociationInstance)
newDomainInstance."addTo${prop.name.capitalize()}"(newAssociationInstance)
}
}
} else {
if (!prop.bidirectional) {
//If the association isn't owned or the owner, then we can just do a shallow copy of the reference.
newDomainInstance."${prop.name}" = domainInstanceToClone."${prop.name}"
}
// ##JR
// Yes bidirectional and not owning. E.g. clone Report, belongsTo Organisation which hasMany
// manyToOne. Just add to the owning objects collection.
else {
//println "${prop.owningSide} - ${prop.name} - ${prop.oneToMany}"
//return
if (prop.manyToOne) {
newDomainInstance."${prop.name}" = domainInstanceToClone."${prop.name}"
def owningInstance = domainInstanceToClone."${prop.name}"
// Need to find the collection.
String otherSide = prop.otherSide.name.capitalize()
//println otherSide
//owningInstance."addTo${otherSide}"(newDomainInstance)
}
else if (prop.manyToMany) {
//newDomainInstance."${prop.name}" = [] as Set
domainInstanceToClone."${prop.name}".each {
//newDomainInstance."${prop.name}".add(it)
}
}
else if (prop.oneToMany) {
domainInstanceToClone."${prop.name}".each { associationInstance ->
def newAssociationInstance = deepClone(associationInstance)
newDomainInstance."addTo${prop.name.capitalize()}"(newAssociationInstance)
}
}
}
}
} else {
//If the property isn't an association then simply copy the value
newDomainInstance."${prop.name}" = domainInstanceToClone."${prop.name}"
if (prop.name == "dateCreated" || prop.name == "lastUpdated") {
newDomainInstance."${prop.name}" = null
}
}
}
return newDomainInstance
}
I've had much more luck with this method:
YourDomainClass clonedObject = new YourDomainClass(objectToClone.properties)
Background:
I have grails 1.3.7 application which uses g:createLink and g:link on many pages.
Recently I decided to make big change in url mappings - introduce preceding path element.
Currently I have: /$controller/$action?/$id?
But want to have: /$regionId/$controller/$action?/$id?
It was easy to change urlMappings, but I can't figure out how to easily change the behavior how links are built throught the application.
Basically, I don't want to go through each page and change links. But want to do this in one place.
Question
How to override ApplicationTagLib#createLink functionality so grails will use this implementation without the need of changes pages which use this tag (or function)?
Any help greatly appriciated!
I had a smilar problem. Actually you can decorate g:link tag like this.
1) TagLib
import org.codehaus.groovy.grails.plugins.web.taglib.*
class OverrideDefaultTagLib {
static namespace = "g"
def link = { attrs, body ->
def c = "1" // Get it from session or somewhere else
if (attrs.params) {
attrs.params.put("region", c)
} else {
attrs.params = [region: c]
}
def applicationTagLib = grailsApplication.mainContext.getBean('org.codehaus.groovy.grails.plugins.web.taglib.ApplicationTagLib')
applicationTagLib.link.call(attrs, body)
}
}
}
2) add to UrlMappings.groovy
"/$region/$controller/$action?/$id?"{}
I was unable to solve this problem in terms of OOP. I mean I can't find way how to override closure. I tried several approaches, but with no success. And documentation says that you can't override closure, you can only replace it with new implementation (please correct me if I wrong).
But (!) I was able to solve task by copy-pasting source code of ApplicationTagLib#createLink method.
I think this is brutal solution, but after 8 hours of fighting with this simple task - it's acceptable.
So finally all I need to do - is define this class, grails will immediately use it for link generation (for all views, no need to change their code):
import java.text.SimpleDateFormat;
import groovy.time.*;
import java.text.*;
import org.codehaus.groovy.grails.commons.GrailsControllerClass
import org.codehaus.groovy.grails.plugins.web.taglib.ApplicationTagLib;
import org.codehaus.groovy.grails.web.mapping.UrlCreator
import org.codehaus.groovy.grails.commons.ControllerArtefactHandler
import org.springframework.web.context.request.RequestContextHolder
class OverrideTagLib extends ApplicationTagLib {
def createLink = { attrs ->
// get value for regionId parameter
def regionId = regionIdFinderService.currentRegionId
// add cutsom regionId parameter
if (attrs) {
if (attrs.params)
attrs.params.put("regionId", regionId);
else {
attrs.params = ["regionId":regionId];
}
}
// process
def writer = getOut()
// prefer URI attribute
if (attrs.uri) {
writer << handleAbsolute(attrs)
writer << attrs.uri.toString()
}
else {
// prefer a URL attribute
def urlAttrs = attrs
if (attrs.url instanceof Map) {
urlAttrs = attrs.remove('url').clone()
}
else if (attrs.url) {
urlAttrs = attrs.remove('url').toString()
}
if (urlAttrs instanceof String) {
if (useJsessionId) {
writer << response.encodeURL(urlAttrs)
}
else {
writer << urlAttrs
}
}
else {
def controller = urlAttrs.containsKey("controller") ? urlAttrs.remove("controller")?.toString() : controllerName
def action = urlAttrs.remove("action")?.toString()
if (controller && !action) {
GrailsControllerClass controllerClass = grailsApplication.getArtefactByLogicalPropertyName(ControllerArtefactHandler.TYPE, controller)
String defaultAction = controllerClass?.getDefaultAction()
if (controllerClass?.hasProperty(defaultAction)) {
action = defaultAction
}
}
def id = urlAttrs.remove("id")
def frag = urlAttrs.remove('fragment')?.toString()
def params = urlAttrs.params && urlAttrs.params instanceof Map ? urlAttrs.remove('params') : [:]
def mappingName = urlAttrs.remove('mapping')
if (mappingName != null) {
params.mappingName = mappingName
}
if (request['flowExecutionKey']) {
params."execution" = request['flowExecutionKey']
}
if (urlAttrs.event) {
params."_eventId" = urlAttrs.remove('event')
}
def url
if (id != null) params.id = id
def urlMappings = applicationContext.getBean("grailsUrlMappingsHolder")
UrlCreator mapping = urlMappings.getReverseMapping(controller,action,params)
// cannot use jsessionid with absolute links
if (useJsessionId && !attrs.absolute) {
url = mapping.createURL(controller, action, params, request.characterEncoding, frag)
def base = attrs.remove('base')
if (base) writer << base
writer << response.encodeURL(url)
}
else {
url = mapping.createRelativeURL(controller, action, params, request.characterEncoding, frag)
writer << handleAbsolute(attrs)
writer << url
}
}
}
}
}
add regionId to params in createLink and g:link and grails is smart enough to match your urlmappings. i.e
${createLink(controller:'c',action:'a',id:1,params:[regionId:2])}
Does anyone know of a way to avoid repeating a closure when emitting the same object more than once using the latest version of the Grails JSONBuilder?
I have a Group domain object that contains sets of Members and Leaders. I would like to find a way to emit a person without having to cut and paste the closure.
def builder = new JSONBuilder()
def result = builder.build {
array {
Group.list().each { Group group ->
g = {
id = group.id
name = group.name
members = array {
group.members?.person?.sort().each { Person person ->
m = { // Person closure copy #1
id = person.id
firstName = person.firstName
lastName = person.lastName
}
}
}
leaders = array {
group.leaders?.person?.sort().each { Person person ->
l = { // Person closure copy #2
id = person.id
firstName = person.firstName
lastName = person.lastName
}
}
}
}
}
}
}
I have tried defining the closure separately but that leads to errors such as: exception: No such property: id for class:.
Some notes:
1) The domain objects in the example are greatly simplified. I'm using JSONBuilder instead of render Group.list() as JSON or render Group.list().encodeAsJSON because I need to control what parts of my objects are encoded.
2) I'll accept authoritative answers that explain why this can't be done.
After repeated failures using closures, I have a solution. It doesn't use a closure directly but instead uses a closure that returns a map.
class Person {
...
def toMap = {
def map = [:]
map["id"] = this.id
map["firstName"] = this.firstName
map["lastName"] = this.lastName
return map
}
}
def builder = new JSONBuilder()
def result = builder.build {
array {
Group.list().each { Group group ->
g = {
id = group.id
name = group.name
members = array {
group.members?.person?.sort().each { Person person ->
m(person.toMap())
}
}
leaders = array {
group.leaders?.person?.sort().each { Person person ->
l(person.toMap())
}
}
}
}
}
}
The m(person.toMap()) syntax isn't intuitive but it works and allows me to avoid repeating myself. This blog entry provides the details and explains the origin of the current Grails JSONBuilder.
You can execute another closure in the same "context" by setting the "delegate" for the closure.
Closures aren't multi-thread safe (only one delegate at a time) so you will have to clone the closure each time if the closures are shared in a singleton class or static variable.
This is just an idea of refactoring the code, it might not work (I didn't test it).
You can replace assignment (=) with "setProperty" and DSL method calls with "invokeMethod" if you have to dynamicly decide the property name or method name in the DSL.
(reference http://groovy.codehaus.org/api/groovy/lang/Closure.html)
def personClosure = { Person person, varname ->
setProperty(varname, {
id = person.id
firstName = person.firstName
lastName = person.lastName
})
}
def groupMembersClosure = { memberList, memberListVarName, memberVarName ->
personClosure.delegate = delegate
setProperty(memberListVarName, array {
memberList?.person?.sort().each personClosure, memberVarName
})
}
def builder = new JSONBuilder()
def result = builder.build {
array {
Group.list().each { Group group ->
g = {
id = group.id
name = group.name
groupMembersClosure.delegate = delegate
groupMembersClosure(group.members, 'members', 'm')
groupMembersClosure(group.leaders, 'leaders', 'l')
}
}
}
}