I'm upgrading by Grails 2.5.1 web-app to grails 3, but I'm stuck with this problem: in my controllers I was using beforeInterceptors to pre-calculate a set of variables to be used in their action methods.
class MyController {
def myVar
def beforeInterceptor = {
myVar = calculateMyVarFromParams(params)
}
def index() {
/* myVar is already initialized */
}
}
Now that with Grails 3 interceptors are more powerful and on separate files, how can I achieve the same result? To avoid using request-scope variables, I tried with the following code
class MyInterceptor {
boolean before() {
MyController.myVar = calculateMyVarFromParams(params)
MyController.myVar != null // also block execution if myVar is still null
}
boolean after() { true }
void afterView() { /* nothing */ }
}
class MyController {
def myVar
def index() {
println('myVar: '+myVar)
}
}
but I get
ERROR org.grails.web.errors.GrailsExceptionResolver - MissingPropertyException occurred when processing request: [GET] /my/index
No such property: myVar for class: com.usablenet.utest.MyController
Possible solutions: myVar. Stacktrace follows:
groovy.lang.MissingPropertyException: No such property: myVar for class: com.usablenet.utest.MyController
Possible solutions: myVar
at com.usablenet.utest.MyInterceptor.before(MyInterceptor.groovy:15) ~[main/:na]
I assumed (wrongly, apparently) that this would be feasible. Is there a solution? Thanks in advance!
Note: in my case MyController is an abstract class extended by all other controllers
What I was missing is to declare myVar as static, as simple as that!
Update
If for any reason you cannot define the variable as static, you can set it as attribute on request object in the interceptor, and read it from there in the controller
// Interceptor
request.setAttribute('myVar', calculateMyVarFromParams(params))
// Controller
request.getAttribute('myVar')
Related
Using Grails 3.2.8 and the Spock framework for testing, given the following controller class:
class SomeController {
def doSomething() {
// do a few things, then:
someOtherMethod()
}
protected void someOtherMethod() {
// do something here, but I don't care
}
}
How can I test the doSomething() method to make sure someOtherMethod() is called exactly once?
This is my attempt that failed:
#TestFor(SomeController)
class SomeControllerSpec extends Specification {
void "Test that someOtherMethod() is called once inside doSomething()"() {
when:
controller.doSomething()
then:
1 * controller.someOtherMethod(_)
}
}
Error message:
Too few invocations for:
1 * controller.someOtherMethod(_) (0 invocations)
Note: Imports have been omitted to focus on the problem at hand
You can't do that as controller is not a mocked object. Instead, you need to use metaclass like this:
#TestFor(SomeController)
class SomeControllerSpec extends Specification {
void "Test that someOtherMethod() is called once inside doSomething()"() {
given:
Integer callsToSomeOtherMethod = 0
controller.metaClass.someOtherMethod = {
callsToSomeOtherMethod++
}
when:
controller.doSomething()
then:
callsToSomeOtherMethod == 1
}
}
Just out of curiosity I tried to place a local class within one of my controller's actions, e.g.:
def index() {
class TestClass {
TestClass() {
// do something
}
doSomething() { ... }
}
TestClass test = new TestClass()
test.doSomething()
respond anything
}
However, compilation always fails giving me an error like this:
Error Compilation error: startup failed: class TestClass ...
^
Have you got any ideas?
You can't define classes inside methods
Move it outside the method
I don't appear to be able to override a getter within my grails controller. The sample code I've created to illustrate this is provided below:
class MyController extends RestfulController<MyDomainObj> {
def field
def getField(){
field += 1
}
def index(MyCommand command) {
field = 1
// in a controller this prints 1, but in my class it prints 2
println('field' + field)
}
}
If I create a Groovy class and override the getter then it works.
class X {
public static void main(String[] args){
def x = new X()
x.field = 1
println x.field
}
def field
def getField(){
field += 1
}
}
Am I doing something wrong in the Controller or is this feature not supported in controllers? If it isn't supported, then does anyone know why? What magic is going on that would cause this feature not to work?
For attributes within a class, Groovy uses the generated private variable directly:
See http://groovy.codehaus.org/Groovy+Beans:
If you access a property from within the class the property is defined
in at compile time with implicit or explicit this (for example
this.foo, or simply foo), Groovy will access the field directly
instead of going though the getter and setter.
Example:
class C {
def prop
def getProp() {
println "getter"
prop
}
def dostuff() {
prop = "Y"
println prop
println getProp()
}
}
new C().dostuff()
results in
Y
getter
Y
It appears the convention for converting objects in Groovy is to use the as operator and override asType(). For example:
class Id {
def value
#Override
public Object asType(Class type) {
if (type == FormattedId) {
return new FormattedId(value: value.toUpperCase())
}
}
}
def formattedId = new Id(value: "test") as FormattedId
However, Grails over-writes the implementation of asType() for all objects at runtime so that it can support idioms like render as JSON.
An alternative is to re-write the asType() in the Grails Bootstrap class as follows:
def init = { servletContext ->
Id.metaClass.asType = { Class type ->
if (type == FormattedId) {
return new FormattedId(value: value.toUpperCase())
}
}
}
However, this leads to code duplication (DRY) as you now need to repeat the above in both the Bootstrap and the Id class otherwise the as FormattedId will not work outside the Grails container.
What alternatives exist to writing conversion code in Groovy/Grails that do not break good code/OO design principals like the Single Responsibility Principal or DRY? Are Mixins are good use here?
You can use the Grails support for Codecs to automatically add encodeAs* functions to your Grails archetypes:
class FormattedIdCodec {
static encode = { target ->
new FormattedId((target as String).toUpperCase()
}
}
Then you can use the following in your code:
def formattedId = new Id(value: "test").encodeAsFormattedId
My un-elegant solution is to rename the original asType(), and make a new asType() that calls it, and to also make your BootStrap overwrite astType with a call to that method:
so, your class:
class Id {
def value
#Override
public Object asType(Class type) {
return oldAsType(type);
}
public Object oldAsType(Class type) {
if (type == FormattedId) {
return new FormattedId(value: value.toUpperCase())
}
}
}
In my app, I had asType defined in a number of classes, so I ended up using a common closure in BootStrap.groovy:
def useOldAsType = {Class clazz ->
delegate.oldAsType(clazz)
}
Id.metaClass.asType = useOldAsType;
Value.metaClass.asType = useOldAsType;
OtherClass.metaClass.asType = useOldAsType;
SubclassOfValue.metaClass.asType = useOldAsType;
Note that if you have a subclass that does not override asType, but you want it to use the superclass's, you must also set it in BootStrap.
To reproduce this problem use the following steps.
Create a new Grails application.
Create a new controller called FooController
Add an action "bar" to FooController
In src/groovy, create a new class called Bar
In resources.groovy configure a SpringBean called bar
bar(Bar) {bean ->
bean.autowire = 'byName'
}
Start the application and navigate to http:localhost:8080/[appContext]/foo/bar
You should get a stacktrace similar to this:
java.lang.ClassCastException: Bar cannot be cast to groovy.lang.Closure
at org.grails.plugin.resource.DevModeSanityFilter.doFilter(DevModeSanityFilter.groovy:44)
at java.lang.Thread.run(Thread.java:680)'
Why is this occuring? Is it a bug in Grails or expected behaviour?
I would expect that there should not be name clashes between configured SpringBeans and action names.
The problem is that Groovy syntax like
class FooController {
def bar = {
// do something
}
}
gives the FooController class two public methods
public Object getBar() {
return bar;
}
public void setBar(Object newBar) {
bar = newBar;
}
The existence of the setBar method makes Spring consider it as a property to be autowired, and it replaces the closure value with your bean. Grails itself only requires the getter method, so if instead you say
class FooController {
final bar = {
// do something
}
}
(i.e. declare bar to be final) then Groovy will synthesize only the getter and not the setter, and Spring will not see bar as a property it can autowire.
The action in the controller is a closure, which is transformed into a inner class. Your bean has the same name, so I believe you come to name collision through this. Did you try to rename the bean or the action?