Friday, 3 September 2010

Grails Redirect After Post

Currently in most of our applications we do not perform redirects after post to our controllers.

This is bad practice and needs changing (with all of our free time we have).

The current difficulty we have with this is that when we post a form to our controller and there are validation errors, we need to redirect to the form view again but keep the values that were posted in the first place.

For example :
  • Edit user.
  • Get user object.
  • Put user object in model
  • Populate input fields with properties from user object
  • Submit form.
  • Validation Errors.
  • Redirect back to edit user.
What now happens is that because the form gets the values from the user object it can't keep the changed values that are in the command.

Mark and myself came up with what we think is the best practice.

In our grails command we define a constructor that takes the object and populates the command.

In our controller we then get the object we wish to edit. Construct the command object and pass this into the model.

So now our model only ever deals with our command object.

When we redirect after post now all we have to do is place our command object in the flash scope and it will be accessible in our redirected method.

A simple check of existence and we can decide if we want to just place the command in the model or construct the command object again.

For example:
  • Edit User
  • Get User
  • Construct EditUserCommand using user object
  • Place command in the model
  • Populate input fields with properties from command object
  • Submit form.
  • Validation Errors.
  • Redirect back to edit user.
  • Check for command.
  • Place command back in model.

Below is some sample code for the User example case.

class UserCommand {
    String name

    public UserCommand() {}

    public UserCommand(User user) {
        name = user.name       
    }

    static constraints = {
        name(nullable:false, blank:false)
    }
}

class UserController {
    def edit = {      
        def user = getUser()       

        if (flash.form == null) {
            flash.form = new UserCommand(user)
        }

        return render(view:'edit', model:[form:flash.form])
    }

    def update = { UserCommand form ->       
        if (form.hasErrors()) {
            flash.form = form
            return redirect(action:'edit')
        }       

        return redirect(action:'detail', params:[id:form.id])           
    }
}


Our view will now only deal with the command object and can access all user fields it needs through the command object, as well as all error messages returned by the command validation.

No comments: