View
 

Todos 06-Building with Grails

Page history last edited by Cam MacRae 14 years, 7 months ago

What is Grails?

 

Grails is web framework for the JVM built with Groovy on Spring

 

Before you start

 

You'll need to download and install Grails. For this tutorial I've use Grails 1.2.2, but it should work with Grails 1.1.x too.

 

Test your installation by entering the following into a terminal:

 

$ grails help

 

After a little bit of whirring you should see a welcome banner and help.

 

You might also consider setting up your editor or IDE, but it's not necessary to complete this tutorial.

 

Create the application

 

We'll make a small Grails application to serve as the backend for the Todos application.

 

Change into your projects or source directory and enter the following commands into a terminal:

 

$ grails create-app todos-backend
 

Be sure to do this outside of your SproutCore application folder.

 

This will create a folder called todos-backend containing a template Grails application. By default an in memory HSQLDB data source has been configured so we can start building our application right away.

 

Create the model

 

Grails models (domain classes in Grails parlance) are essentially a normal Groovy class, but behind the scenes use GORM (Grails object relational mapping) which is build on top of Hibernate3.

 

To create our model:

 

$ cd todos-backend 
$ grails create-domain-class sc.Task

 

This will create a Task domain class in the sc namespace and generate some unit test boilerplate.

 

Open [path to project]/todos-backend/grails-app/domain/sc/Task.groovy in your editor and add the following:

 

package sc
 
class Task {
 
    String description
    Integer order = 0
    Boolean isDone = false
 
    static constraints = {
    }
 
    static mapping = {
        order column: "task_order"
    }
}

 

Note: we map the order property to the task_order column because HSQLDB (and many other databases) will choke on column name that's a keyword.

 

Next we'll bootstrap some data. Open [path to project]/todos-backend/grails-app/conf/BootStrap.groovy and add the following: 

 

import sc.Task
 
class BootStrap {
 
     def init = { servletContext ->
        new Task(description: 'Finish the SproutCore Todos Intro').save()
        new Task(description: 'Flip my startup to Google').save()
     }
 
     def destroy = {
     }
}  

 

To ensure we're on the right track let's run the tests:

 

$ grails test-app

 

The empty unit test created with the Task domain class will pass (it tests nothing, a trap which can test you into believing that your coverage is better than it really is!!), but what we're interested in is the bootstrapping of the database before the integration tests are run: you shouldn't get a stacktrace.

 

Create the controller 

 

A controller handles requests and creates or prepares the response (or delegate the response to a view).

 

To create the controller:

 

$ grails create-controller sc.Task

 

This will generate a controller class called TaskController and some boilerplate test code.

 

Before we start work on the controller we need to map the REST URIs and request method to actions of the controller.

 

Open [path to project]/todos-backend/grails-app/conf/UrlMappings.groovy and edit:

 

class UrlMappings {
    static mappings = {
        "/$controller/$action?/$id?" {
            constraints {
                // apply constraints here
            }
        }
        "/"(view: "/index")
        "500"(view: '/error')
        "/tasks/$id?"(controller: "task") {
            action = [GET: "show", PUT: "update", DELETE: "delete", POST: "save"]
        }
    }
}

 

This maps HTTP GET to TaskController.show, PUT to TaskController.update etc. and the passes id as a parameter to each action (if supplied).

 

Next open [path to project]/todos-backend/grails-app/controllers/sc/TaskController.groovy and add the following:

 

package sc
 
import grails.converters.JSON
 
class TaskController {
 
    def task2map = {t ->
        [guid: "/todos-backend/tasks/$t.id",
         description: t.description,
         order: t.order ?: 0,
         isDone: t.isDone]
    }
 
    def list = {
        def tasks = Task.list()
 
        render(contentType: "text/json") {
            content = array {
                tasks.each {task(task2map(it))}
            }
        }
    }
 
    def show = {
        if (params.id) {
            def task = Task.get(params.id)
 
            if (task) {
                render(contentType: "text/json") {
                    content(task2map(task))
                }
            }
            else {
                render text: "${params.id} not found.", status: 404
            }
        }
        else {
            list()
        }
    }
 
    def delete = {
        def task = Task.get(params.id)
 
        if (task) {
            task.delete()
            render "" //would normally return 204 No Content here, but sc-server barfs on 0 bytes.
        }
        else {
            render text: "Task ${params.id} not found.", status: 404
        }
    }
 
    def save = {id=null ->                                                                                                              
        def payload = JSON.parse(request.reader.text)
        def task = id ? Task.get(id) : new Task()
 
        if (task) {
            task.properties = payload
            if (task.save()) {
                response.setHeader('Location', "/todos-backend/tasks/$task.id")
                render text: "", status: 201
            }
            else {
                render text: "Task could not be saved.", status: 500
            }
        }
        else {
            render text: "Task ${params.id} not found.", status: 404
        }
    }
 
    def update = {
        save(params.id)
    }

}

 

The task2map closure is a convenience method to aid serialising the model into JSON (there are nicer ways to do this but they're beyond the scope of this tutorial).

 

As a matter of taste I've rolled update into save, and in doing so return the location header and status 201 for both the POST and PUT. I'd typically return a 204 for DELETE instead of a 200 too, however sc-server strenuously objects. You should rework things to suit your tastes (or just flame me in the comments!).

 

Starting the server

 

Depending on your version of Grails the application will start in a development instance of either jetty or tomcat. To start:

 

$ grails run-app

 

Browse to http://localhost:8080/todos-backend/tasks and you should see your bootstrap tasks serialized as JSON. 

 

You can also test the other methods with curl or HTTP Client.

 

Setting up the proxy

 

Add the following line to the Buildfile in your SproutCore project:

 

proxy "/todos-backend/tasks", :to => "localhost:8080"

 

Note the application name in the path. In Todos 07-Hooking Up to the Backend you'll need to modify the calls to 

SC.Request.getX('/tasks').json() in fetch and createRecord to 


SC.Request.getUrl('/todos-backend/tasks').json() 

 

and

 

SC.Request.postUrl('/todos-backend/tasks').json()

 

respectively.

 

Next step

 

Continue to the next step: Step 7: Hooking Up to the Backend ยป

 

 

Comments (1)

Jeff Potts said

at 1:32 pm on Jan 7, 2011

Worked the first time with no adjustments. Thanks for the write up!

You don't have permission to comment on this page.