How to restrict properties for a domain being set from params - grails

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.

Related

Grails data binding field exclusion

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()
}
}

Is it wrong to add instance variables to a Controller?

If I want to share an object between multiple methods in a controller, is it wrong to create an instance var? E.g.
class MyController {
def index() {
def user = verifyUserLogin()
[messages:getMessages(user)]
}
private verifyUserLogin() {
...
return user
}
private getMessages(user) {
...do something with `user`...
return messages
}
}
would become
class MyController {
private user
def index() {
verifyUserLogin()
[messages:getMessages()]
}
private void verifyUserLogin() {
...
this.user = user
return
}
private getMessages() {
...do something with `user`...
return messages
}
}
Grails controllers are not singleton they are created for every request so it should not be any issue to have instance level variables.
However I personally prefer to not to declare instance level variables as much as possible, because when code starts becoming lengthy it becomes difficult to figure out the flow of the code that initializes and uses the variables.
I rather choose to pass them as parameters. However there's no such rule of thumb and it highly depends on the problem at hand.
The traditional way - for Servlets at least - of storing state between controllers, i.e. passing information between requests is to use the session object.
class MyController(){
def index(){
session.user = getUser()
}
def postex(){
def user = session.user
}
}
In grails 3.2.1 the default scope of controllers changed from "prototype", controller instance per request, to "singleton", one instance of each controller.
This made instance variables in controllers no longer safe unless you change the scope!
https://docs.grails.org/3.2.1/ref/Controllers/scope.html singleton
https://docs.grails.org/3.2.0/ref/Controllers/scope.html prototype
If you do want to use instance variables in a controller change the scope static scope = "prototype".
Rather than use instance variables you can set variables on the request (as request attributes). request.user = user Note that these will persist for the life of the request (visible to all interceptors and views traversed by the request).

Grails - how execute code before every save?

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

What is the best way to add logic to delete() on Grails domain class?

I need to make changes to other domain classes when an instance of a particular domain class is deleted. What is the best way to do this? I don't want to wait until commit or flush so I don't think the "beforeDelete" callback will help. I would like to "override" delete, do some stuff and call super.delete():
class Foo {
Bar bar
void delete() {
if (bar) bar.foo = null
super.delete() -- this doesn't work
}
}
Currently I have named "delete" cancel but would like to call it "delete" but then I cannot call the original delete().
To add to what #sbglasius said, here's the link to the docs on GORM events
Complete example:
class Foo {
Bar bar
def beforeDelete() {
if(bar) {
bar.foo = null
}
}
}
I haven't tried overriding GORM methods myself, but this might give some insight on what's involved:
"Overloading" standard GORM CRUD methods
I would put the "delete" logic in a service and call that instead:
class FooService {
def deleteInstance(foo) {
if (foo?.bar) {
foo.bar.foo = null
// might have to call foo.bar.save() here
// then foo.bar = null
}
foo.delete()
}
}

Adding a method to a domain class

I have a domain class containing a couple of fields. I can access them from my .gsps. I want to add a method to the domain class, which I can call from the .gsps (this method is a kind of virtual field; it's data is not coming directly from the database).
How do I add the method and how can I then call it from the .gsps?
To add a method, just write it out like you would any other regular method. It will be available on the object when you display it in your GSP.
def someMethod() {
return "Hello."
}
Then in your GSP.
${myObject.someMethod()}
If you want your method to appear to be more like a property, then make your method a getter method. A method called getFullName(), can be accessed like a property as ${person.fullName}. Note the lack of parentheses.
Consider class like below
class Job {
String jobTitle
String jobType
String jobLocation
String state
static constraints = {
jobTitle nullable : false,size: 0..200
jobType nullable : false,size: 0..200
jobLocation nullable : false,size: 0..200
state nullable : false
}
def jsonMap () {
[
'jobTitle':"some job title",
'jobType':"some jobType",
'jobLocation':"some location",
'state':"some state"
]
}
}
You can use that jsonMap wherever you want. In gsp too like ${jobObject.jsonMap()}

Resources