Is there a good/standard way to execute some common code before every save() invocation on domain classes?
For example, my domain
class Page {
String url
Boolean processed
Date date
Integer urlCrc
}
My form has only 3 first fields and I would like to calculate urlCrc every time the save() method is called. I cannot just override save method because it is injected.
You can use GORM events - see the docs. Since by default validate() is called before every save() I would use that.
class Page {
//your defs here
def beforeValidate() {
this.urlCrc = yourComputationHere
}
}
class Page {
def beforeInsert() {
this.beforeUpdate()
}
def beforeUpdate() {
this.urlCrc = 'calculate something'
}
}
This topic is covered in the GORM docs:
6.5 Advanced GORM Features
6.5.1 Events and Auto Timestamping
Related
I am using Grails 2.5 and use Grails databinding in request methods.
For a basic example of the situation consider the following:
Domain class
class Product {
String field1
String privateField
}
Controller
class ProductController {
def update(Product productInstance) {
productInstance.save()
}
}
If I pass an existing Product to the controller like
{"id":3, "privateField":"newValue","field1":"whatever"}
the old value of privateField is overwritten. I want to enforce, that privateField is never bound from a request and avoid checking if the field is dirty.
Is there a mechanism in Grails to achieve this?
If I have to do the dirty check, how can I discard the new value and use the old one?
Pretty sure there's a "bindable" constraint.
http://grails.github.io/grails-doc/2.5.x/ref/Constraints/bindable.html
class Product {
String field1
String privateField
static constraints = {
privateField bindable: false
}
}
Should keep that field from binding automatically.
You can enforce which values are bound, but you'll need to change your method signature to get more control of the data binding process.
class ProductController {
def update() {
def productInstance = Product.get(params.id)
bindData(productInstance, params, [exclude: ['privateField']]
productInstance.save()
}
}
I've got several properties in my domain class. However, I only want few of them to be set via the params object. What is a good way to do this?
Example:
Domain
class Color {
String name
String shade //don't want this set by params
}
controller
class ColorController {
def save() {
json {
def c = new Color(params?.color)
c.save(flush: true)
//..more code
}
}
}
If someone sends a request like:
{"color":
{name: "red",
shade: "light"
}
}
then user can change the shade property. How can I stop this?
You could probably do one of a couple of things:
If it is many properties, create a transient beforeInsert() {} and/or transient beforeUpdate() {} method in your domain class and handle setting (or not) the properties.
If only a few, override the setters in the domain class.
Since Groovy makes me not want to mess with getters and setters unless I absolutely have to, I usually use the beforeInsert and beforeUpdate methods.
Grails provides a bindData method on the controller to give you fine grained control of data-binding. For your example you could write this as:
class ColorController {
def save() {
json {
def c = new Color()
bindData(c, params, [include: 'name'])
c.save(flush: true)
//..more code
}
}
}
In this case, only the 'name' field would be set on the c instance before attempting to save.
If you want to to additional validation on the incoming params, I would also suggest looking into using a Command Object for the data binding.
I want to call a service inside my grails domain objects beforeDelete() event. Unfortunately it always crashes reproducibly when the event is fired. I built an example that reproduces the problem. The domain object:
class Gallery {
def myService
def beforeDelete() {
// System.out.println(myService); // not even this works, same error!
System.out.println(myService.say());
}
}
The service:
class MyService {
String say() {
"hello"
}
}
The test controller:
class DeleteController {
def index() {
Gallery.list().each {
it.delete()
}
}
def create() {
def gallery = new Gallery()
gallery.save()
}
}
If I start the application and call create followed by index I get:
Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [testbeforedelete.Gallery#1]
What I want to accomplish is to call my service, which is a bit more complicated than this example. I cannot explain this behavior and I don't know how cope with this. I know that the Hibernate events need special care yet I'm stuck.
The beforeDelete actually makes a change to your domainclass. I agree that you would not expect this behaviour. Hibernate thinks that you are modifying the instance. You could use the follow code to get around your problem
def beforeDelete() {
Gallery.withNewSession {
System.out.println(myService.say());
}
}
Wanna do the following:
BootStrap {
def init = {servletContext ->
........
MyDomainClass.metaClass.save = {->
delegate.extraSave()
//////// how to call original save() here?
}
}
.........
}
P.S. MyDomainClass#extraSave is defined as public void extraSave(){.....}
First of all, Bootstrap.groovy may not be the best place to do this kind of metaprogramming. The problem with this approach is that the changes to the classes will be applied when the application starts, but you may lose these changes when the application is reloaded. Obviously this is only an issue during development, and not an issue at all if you don't mind restarting the server every time you make a change, but I'll bet this would quickly become a major annoyance. In order to have the changes applied when app is reloaded as well, you should move the metaprogramming into a plugin, where you can hook into the onChange application lifecycle event.
So the steps are:
Create a plugin
Do the metaprogramming in the doWithDynamicMethods and onChange closures of the plugin descriptor
Here's a complete example where I "override" the chain() method on all the controller classes. The code to do likewise for the save() method of domain classes should only require some obvious replacements, e.g. use application.domainClasses instead of application.controllerClasses
def doWithDynamicMethods = {ctx ->
application.controllerClasses.each {controller ->
replaceChain(controller)
}
}
def onChange = {event ->
if (application.isArtefactOfType(ControllerArtefactHandler.TYPE, event.source)) {
def clz = application.getControllerClass(event.source?.name)
replaceChain(clz)
}
}
private replaceChain(controllerClass) {
// Save a reference to the grails chain() method
def grailsChain = controllerClass.metaClass.pickMethod("chain", [Map] as Class[])
controllerClass.metaClass.chain = {Map params ->
println "My code to execute before chain goes here"
// Invoke the grails chain() method
grailsChain.invoke(delegate, [params] as Object[])
println "My code to execute after chain goes here"
}
}
why not leveraging the GORM events for this purpose? In the Domain class:
def extraSave() {
// ...
}
def beforeInsert = {
extraSave()
}
def beforeUpdate = {
extraSave()
}
IMHO this a cleaner approach. Documentation can be found here
Not sure if the following works, but this might be a solution:
MyDomainClass.metaClass.origSave = MyDomainClass.metaClass.save
MyDomainClass.metaClass.save = {->
delegate.extraSave()
delegate.origSave()
}
Please give me feedbeck if the above worked...
I'm writing a grails plugin and I need to hook into the domain save() method to do some logic after the save. I need to do this across multiple domain classes. I'm trying to avoid hibernate events in the cases where a plugin user is not using hibernate with GORM.
I've tried many thing but below is what I think should have had the best chance at working. In all cases grailsSave is null. How can I do this?
def doWithDynamicMethods = { ctx ->
application.domainClasses.each { dc ->
def grailsSave = dc.metaClass.pickMethod('save', [Map] as Class[])
domainClass.metaClass.save = { Map params ->
grailsSave.invoke(delegate, [params] as Object[])
println "Saved object, now do my thing"
//...
}
}
}
I have the following set in my *Plugin.groovy class:
def dependsOn = [domainClass: '1.1 > *', hibernate: '1.1 > *']
def loadAfter = ['hibernate']
I was unable to successfully get a reference to the save() methods during plugin/app initialization; I don't know why. Instead, I decided to create a listener for the hibernate events after insert, update, and deletes. This post by Sean Hartsock regarding the Audit Logging plugin was a perfect primer for doing that.
Here's the gist of the Listener:
class MyListener implements PostInsertEventListener, PostUpdateEventListener, PostDeleteEventListener, Initializable {
public void onPostInsert(final PostInsertEvent event) {
// logic after insert
return
}
public void onPostUpdate(final PostUpdateEvent event) {
// logic after update
return
}
public void onPostDelete(final PostDeleteEvent event) {
// logic after delete
return
}
public void initialize(final Configuration config) {
return
}
}
Then in the *GrailsPlugin.groovy:
def doWithApplicationContext = { applicationContext ->
// add the event listeners for reindexing on change
def listeners = applicationContext.sessionFactory.eventListeners
def listener = new MyListener()
['postInsert', 'postUpdate', 'postDelete'].each({
addEventTypeListener(listeners, listener, it)
})
}
// copied from http://hartsock.blogspot.com/2008/04/inside-hibernate-events-and-audit.html
private addEventTypeListener(listeners, listener, type) {
def typeProperty = "${type}EventListeners"
def typeListeners = listeners."${typeProperty}"
def expandedTypeListeners = new Object[typeListeners.length + 1]
System.arraycopy(typeListeners, 0, expandedTypeListeners, 0, typeListeners.length)
expandedTypeListeners[-1] = listener
listeners."${typeProperty}" = expandedTypeListeners
}
Fairly simple at the end of the day...
There are three different version of save added to the metaClass,
save(Map)
save(Boolean)
save()
Which one are you calling in your testing? You'll need to add you code to each one.
Another thing to check is whether your plugin is running after the hibernate plugin which adds the three methods to the metaClass
cheers
Lee
Have a look at the Falcone Util plugin. This plugin allows you to hook into Hibernate events (see documentation at the bottom of the page). I don't know if this is exactly what you want, but you might get some hints.
Ps! I don't think the plugin works with Grails 1.2 yet.
This is an issue of premature optimization: older versions of Groovy seriously penalized MetaClass mangling, and so GORM does not add all of its magic until it detects the need to.
Easiest solution is to have your plugin dependOn GORM Labs (I work around it there). The alternative solution is to trigger methodMissing manually (which would be duplicating the work I did). See the GORM Labs documentation for details on how I accomplished that.
Wouldn't this best be added to the service class that owns the unit of work? That's where the usual Spring/Grails idiom would have such logic. You needn't modify the save at all.
Additional GORM methods are lazily initialized on first call to any of them.
To initialize them in doWithDynamicMethods simply call one of the static methods on your domain class(es):
def doWithDynamicMethods = { ctx ->
application.domainClasses.each { dc ->
// call any static method to initialize dynamic gorm methods
dc.clazz.count()
def grailsSave = dc.metaClass.pickMethod('save', [Map] as Class[])
//...
}
}
Your save() method will be available now. As this is called at start up a single count shouldn't be to much of a problem.