Archive for January 23rd, 2011
A Pattern to Simplify Grails Controllers
The WithDomainObject Pattern
Note, the sample code was modified on 5/18/2011. The withPerson method is now private.
I generally follow the same patterns with my controllers. They start with “the big 7 actions” – closures for index, list, show, create, save, edit, update and delete. Then i include any necessary controller-specific actions. After unit testing my controllers for the up-teenth time, I realized that there is a consistent pattern for many of the actions – get the id from params, get the domain object, then use the domain object. I wanted to extract this pattern into its own method, and that extraction evolved into the “WithDomainObject” pattern.
The Problem
Lets say you had a contact manager application, and you had a domain object called Person and a controller called PersonController. In the controllers show, save, edit, update and delete actions all follow the same pattern:
- Get the ID from the params map.
- Get the person from the domain.
- Doing something with the person.
The problem is that steps 1 and 2 are repeated for the 5 actions. Not very DRY.
The Solution
Lets encapsulate steps 1 and 2 into a method called withPerson:
private def withPerson(id="id", Closure c) {
def person = Person.get(params[id])
if(person) {
c.call person
} else {
flash.message = "The person was not found."
redirect action:"list"
}
}
}
and lets put our code from step 3 into its own anonymous closure. Heres a example of a action that uses the withPerson method and the update action:
def update = {
withPerson { person ->
person.properties = params
if(person.validate() && person.save()) {
redirect action:"show", id:person.id
} else {
render view:"edit", model:[person:person]
}
}
}
Example
See the withPerson method at the botttom of the controller.
The Person domain class
package contact
class Person {
String name
static constraints = {
name blank:false, unique:true
}
}
The PersonController controller
package contact
class PersonController {
def index = {
redirect action:"list", params:params
}
def list = {
[ people:Person.list(params), count:Person.count() ]
}
def show = {
withPerson { person ->
[person:person]
}
}
def create = {
[person:new Person()]
}
def save = {
def person = new Person(params)
if(person.validate() && person.save()) {
redirect action:"show", id:person.id
} else {
render view:"create", model:[person:person]
}
}
def edit = {
withPerson { person ->
[person:person]
}
}
def update = {
withPerson { person ->
person.properties = params
if(person.validate() && person.save()) {
redirect action:"show", id:person.id
} else {
render view:"edit", model:[person:person]
}
}
}
def delete = {
withPerson { person ->
person.delete()
redirect action:"list"
}
}
private def withPerson(id="id", Closure c) {
def person = Person.get(params[id])
if(person) {
c.call person
} else {
flash.message = "The person was not found."
redirect action:"list"
}
}
}
The PersonControllerTests unit tests
package contact
import grails.test.*
import org.junit.*
class PersonControllerTests extends ControllerUnitTestCase {
def p7 = new Person(id:7, name:"alpha")
def p9 = new Person(id:9, name:"beta")
@Before
public void setUp() {
super.setUp()
mockDomain Person, [p7,p9]
}
@After
public void tearDown() {
super.tearDown()
}
@Test
public void index() {
controller.index()
assert "list" == controller.redirectArgs.action
}
@Test
public void list() {
def model = controller.list()
assert 2 == model.people.size()
assert p7 == model.people[0]
assert p9 == model.people[1]
assert 2 == model.count
}
@Test
public void show() {
controller.params.id = 7
def model = controller.show()
assert p7 == model.person
}
@Test
public void create() {
def model = controller.create()
assert model.person instanceof Person
}
@Test
public void save_success() {
controller.params.name = "Paul Woods"
controller.save()
assert "show" == controller.redirectArgs.action
assert null != controller.redirectArgs.id
}
@Test
public void save_failure() {
controller.params.name = ""
controller.save()
assert "create" == controller.renderArgs.view
assert controller.renderArgs.model.person instanceof Person
}
@Test
public void edit() {
controller.params.id = 9
def model = controller.edit()
assert p9 == model.person
}
@Test
public void update_success() {
controller.params.name = "Paul Woods"
controller.params.id = 7
controller.update()
assert "show" == controller.redirectArgs.action
assert 7 == controller.redirectArgs.id
}
@Test
public void update_failure() {
controller.params.name = ""
controller.params.id = 9
controller.update()
assert "edit" == controller.renderArgs.view
assert controller.renderArgs.model.person instanceof Person
}
@Test
public void delete() {
controller.params.id = 7
controller.delete()
assert "list" == controller.redirectArgs.action
assert 1 == Person.count()
}
@Test
public void withPerson_success() {
controller.params.id = 7
def person = null
controller.withPerson() { p ->
person = p
}
assert 7 == person.id
}
@Test
public void withPerson_fail() {
controller.params.id = 0
controller.withPerson() { p ->
assert false
}
assert "The person was not found." == controller.flash.message
assert "list" == controller.redirectArgs.action
}
}
Advantages of this Pattern:
- The controller actions are simpler, as they don’t require code to detect not-found domain objects.
- The unit tests can be simpler. There is less code to test.





