The getStarted action redirects to companyInfo action which renders companyInfo.gsp and immediately after the page rendering, companyInfo action getting called one more time. I don't understand what the problem is.
class MyController {
#Secured('ROLE_USER')
def getStarted(){
def renderParams = [view: 'getStarted', model: [:]]
if(request.method != 'POST') {
render(view: 'getStarted')
} else {
def company = new Company()
.......
redirect(action: 'companyInfo', params: [id: company.id])
}
}
#Secured('ROLE_USER')
def companyInfo() {
def renderParams = [view: 'companyInfo', model: [:]]
if (request.method != 'POST') {
renderParams.model.cmpId = params?.id
render(renderParams)
}
}
}
See this answer. Grails trys to map get* to properties. And when the controller is called grails tries to map getStarted to a property called started, calling the method. So, Never Use get**** as your action name
Related
Initial Problem
If you have different methods that basically have only one line different, would there be a way to make it DRY by creating one method.
Example:
def showA( ) {
def instance
try {
instance = A.findById( params.id )
} catch ( Exception e ) {
def message = "Error while retrieving details for the given id ${ params.id }, $e"
log.error message
responseAsJson( 400, "Invalid id", message )
return false
}
return checkAndRender(instance, params.id);
}
def showB( ) {
def instance
try {
instance = B.findByBId( params.BId )
} catch ( Exception e ) {
def message = "Error while retrieving details for the given id ${ params.id }, $e"
log.error message
responseAsJson( 400, "Invalid id", message )
return false
}
return checkAndRender(instance, params.id);
}
So, would there be a way to make one method and simply pass as parameter:
The domain class
the ID to search for
Or would it be better to pass an SQL statement instead?
Update
Based on #dmahapatro comment, I came up with the following:
def showA( ) {
def clos = {id -> A.findByAId( id ) }
return findAndShow(clos, params.AId, params )
}
def showB( ) {
def clos = {id -> B.findByBId( id ) }
return findAndShow(clos, params.BId, params )
}
def findAndShow(Closure closure, def id, def p)
{
def instance
try {
instance = closure(id)
}
catch ( Exception e ) {
def message = "Error while retrieving instance details for the given id ${ id }, $e"
log.error message
responseAsJson( 400, "Invalid Id", message )
return false
}
return checkAndRender(instance, id);
}
Only remaining issues are:
How to cleanup even further / make it cleaner.
How to bypass warning:
The [findAndShow] action in [ApiController] accepts a parameter of
type [groovy.lang.Closure]. Interface types and abstract class types
are not supported as command objects. This parameter will be ignored.
def findAndShow(Closure closure, def id, def p)
First thing you should worry if you want a DRY code, is define a better exception handling. Try-catching your code everywhere to handle response to the client is not very DRY, if you put your data-access code in services, you can throw exceptions from them and use a global controller for catch the errors and handle the responses. E.g:
class ErrorController {
def serverError() {
if (request.format == 'json') {
//Code for handling errors in json request, request.exception stores the data about the exception.
} else {
//Code for handling errors in non-json request, e.g:
render(view: 'error', model: [msg: 'Something went wrong']) //add an error view for this
}
}
}
If you like, you can also add handlers for other types of errors (403, 404, etc)
Add to UrlMappings.groovy
"500"(controller: "error", action: "serverError")
Now you can refactor your code using your new error handling, and reflection:
Controller:
class MyController {
def myService
def show() {
def result = myService.myFind(params.className,params.id)
render result as JSON //Render stuff
}
}
Service:
import grails.util.Holders
class MyService {
def myFind(String className, Long id) {
def result = Holders.getGrailsApplication().getDomainClass('com.mypack.'+ className).findById(id)
if(!result) {
throw new ServiceException('really descriptive and usefull error msg')
}
}
}
I defined a ServiceException class so i can add custom logic for it in my ErrorController using the instanceOf operator.
I have a Grails 2.1.1 application which runs fine, at least until I try to define a filter for all Controller to redirect to the index.gsp if user is not set in the session variable...
what ever I try, I'm not able to redirect to "/index", nor render "/index" when starting the server - if I remove the filter and redirect to "/index" from my AuthenticationController on false parameters, all works like charm...
so, here's what I have so far:
class AuthenticationFilters {
all(controller:'*', action'*') {
before = {
def user = (User) session.getValue("user")
if(user == null || !user.loginState) {
redirect(controller: 'authentication', action: 'index')
return false
}
}
}
}
class AuthenticationController {
def authenticationService
def index = {
render(view: '/index')
}
def login(LoginCommand cmd) {
if(cmd.hasErrors()) {
redirect(action: index)
return
}
}
....
}
now, if I comment out the all Filters definition, everything works well. I got the page (index.gsp) shown on start up and if the LoginCommand has errors, I'm redirected to the index.gsp page without any problems.
if I now comment in the all Filters definition, I get a 404.
I tried:
Grails: Redirect to index.gsp that is not in any controller
Upgrade to Grails 2.0: /index.gsp not found
Grails: what are the main issues (and his solutions) when deploying in weblogic?
but I didn't had any luck...
I'm developing on Intellij IDEA 11.1.4 Premium (evaluation)
EDIT: I tried to get the User object from the session property in my AuthenticationFilters class, surrounded by a try/catch block and now facing the problem that obviously the session property is not available? why?
try {
def user = (User) session.getValue("user")
if((user == null || !user.loginState)) {
...
}
} catch(Exception e) {
println("... couldn't get user from session! ${e.getMessage()}")
}
console output:
... couldn't get user from session! No such property: session for class: grailstest001.AuthenticationFilters
any suggestions on this?
So, just to add my experience here to close this question as solved and for further usage for other users:
to check if the user is entering the page for the first time, you could easily do this by checking for the controllerName field. It should be null or "" (empty String) if the user was not referred to the site by any controller.
Also, I'm not using any Database within my application for authentication because all of this issuses are backended by an API. So I created a UserService.groovy class which is acting as a SessionScopedBean and I store all my user related infos within this class.
So, your filter definition could look like:
class MyFilters {
def filters = {
before = {
if(!controllerName || controllerName.equals("")) {
redirect(controller:'home',action:'index')
}
if(!applicationContext.userService?.getUser() || !applicationContext.userService?.getUser.isLoggedIn) {
redirect(controller:'auth',action:'login')
}
}
}
}
Otherwise, if you don't want to redirect the user which 'freshly' entered your page from within your Filters.groovy class, you could use the UrlMappings.groovy class to do so. Just map your / (root) index to the page you want:
class UrlMappings {
static mappings = {
"/"(controller:'mycontroller',action:'myaction') // change it to your liking
"/$controller/$action?/$id?" {
constraints {
// apply constraints here
}
}
"500"(view:'/error')
}
}
I think your filter syntax may be incorrect - try
Class AuthenticationFilters {
def filters = { // <--- added this
all(controller:'*', action'*') {
before = {
def user = (User) session.getValue("user")
if(user == null || !user.loginState) {
redirect(controller: 'authentication', action: 'index')
return false
}
}
}
} // <-- added
}
I need to change the gsp to render dynamically according certain params. the thing is: in the render action I get
org.codehaus.groovy.grails.commons.metaclass.PropertyExpression#680dc2a instead of the params I passed.
promoFlow = {
start {
action {
flow.inputPage = "landing1/input/${params.land}"
flow.pinPage = "landing1/pin/${params.land}"
flow.finishPage = "landing1/finish/${params.land}"
...
success()
...
}on('success'){
...
}.to 'preview'
}
preview {
render(view: flow.inputPage )
on('next') {...}.to 'pin'
}
Paul if you are using Grails2 why not add a parameter to the action method instead of accessing the params directly, eg:
def myAction(String land) {
...
flow.inputPage = "landing1/input/${land}"
...
}
I'm using Grails 1.3.6. I have this file ...
grails-app/views/home/design/index.gsp
Here is what is defined in my HomeController. Sadly, whenever I visit, "http://localhost:port/context-path/design/", I get a 404 error. The server starts normally and there are no errors in the logs. What can I do to get my page instead of the 404?
def index = {
def folder = params.folder;
def page = params.page;
if (page) {
try {
def contents = IOService.getFileContents(folder, page)
response.setContentType("application/json")
response << contents
} catch (FileNotFoundException e) {
response.status = 404;
} // try
} else {
render(view: "/home/${folder}/index")
} // if
}
My URLMappings file consists of ...
static mappings = {
"/$folder?/$page"{
controller = "home"
action = "index"
}
"/"(view:"/index")
"500"(view:'/error')
}
Thanks, - Dave
If you want to be able to access
/context-path/home/design
Your action needs to be named design, i.e.
class HomeController {
def design = {
}
}
The Grails convention is always /context-path/controllerName/actionName (unless you have it mapped differently in grails-app/conf/URLMappings.groovy).
Your example's a bit unclear which path you're trying to access. To address both:
If you want /context-path/design, you need a DesignController with an index action (because if no action is supplied in the URL, Grails looks for the index action).
If you want /context-path/home/design, you need a HomeController with a design action.
Edit:
In the comments, you express the want to be able to have /context-path/design map to the HomeController index action. You can do this with grails-app/conf/URLMappings.groovy:
"/design"(controller: 'home', action: 'index')
Since it seems you have two distinct actions, I would set things up a bit differently:
def indexWithPage = {
def folder = params.folder;
def page = params.page;
try {
def contents = IOService.getFileContents(folder, page)
response.setContentType("application/json")
response << contents
} catch (FileNotFoundException e) {
e.printStackTrace();
response.status = 404;
} // try
}
def index
def folder = params.folder;
render(view: "/home/${folder}/index")
}
with a URLMaping of:
static mappings = {
"/$folder/$page"{
controller = "home"
action = "indexWithPage"
}
"/$folder"{
controller = "home"
action = "index"
}
"/"(view:"/index")
"500"(view:'/error')
}
I also threw a e.printStackTrace(); in there to help us determine whether you're getting YOUR 404 or the action is truely not being called.
Below is a very simple controller with a "search" action that is fired from g:submitButton in a gsp file. My question is, instead of redirecting to the "index" action and view, how do I return to the view that contained the submit button that called this controller's search action?
class DefaultSearchController {
def searchableService
def index = {
}
def search = {
def query = params.query
if(!query){
redirect(action:"index", params:params)
}
try{
def searchResults = searchableService.searchEvery( query )
redirect(action:"index", searchResults)
}
catch( e ){
params.errors = "${e.toString()}"
redirect(action: "index", params:params)
}
}
}
If the search action is going to be called from various places, I would pass in a parameter to it telling search controller where to redirect to or which view to render the search results with.
cheers
Lee