Simple CRUD with Gaelyk

October 21, 2010 at 7:26 pm | Posted in GAE, Groovy, Search | 5 Comments
Tags: , , , , , , ,

After having written a Grails application for Google App Engine I know exactly what the pain points are in production: Cold startup times make using the application an unenjoyable experience. App Engine calls for a lightweight application with far less JAR baggage and fast startup times. Yesterday I attended Guillaume LaForge’s session “Gaelyk, a lightweight Groovy toolkit for Google App Engine” at SpringOne2GX presenting the nuts and bolts of Gaelyk. The session was reason enough to think about rewriting my application to Gaelyk. To get started I whipped up a simple CRUD example in Gaelyk. All you need to do is to download the Gaelyk template application and you are ready to go. The test application will have four simple use cases: show all entities of a certain type (in my case a “Goal” entity), create new ones, update and delete them.

Gaelyk doesn’t support multiple closures in a controller class to automatically expose URLs by convention like Grails does. Instead you have to define your URL routing for each operation separately.

war/WEB-INF/routes.groovy:

get  "/goals",                forward:  "/showGoals.groovy"
get  "/goals/add",            forward:  "/WEB-INF/pages/goal.gtpl"
post "/goals/insert",         forward:  "/insertGoal.groovy"
get  "/goals/delete/@id",     forward:  "/deleteGoal.groovy?id=@id"
get  "/goals/edit/@id",       forward:  "/editGoal.groovy?id=@id"
post "/goals/update",         forward:  "/updateGoal.groovy"

To show all Goals I defined a Groovlet named showGoals.groovy that uses the App Engine datastore low-level API to retrieve them and forward to the view goals.gtpl. In the view you can add a new goal or edit/delete existing goals.

war/WEB-INF/groovy/showGoals.groovy:

import com.google.appengine.api.datastore.Query
import com.google.appengine.api.datastore.PreparedQuery
import static com.google.appengine.api.datastore.FetchOptions.Builder.*

log.info "Getting all goals"

def query = new Query("Goal")
PreparedQuery preparedQuery = datastore.prepare(query)
query.addSort("created", Query.SortDirection.DESCENDING)
def goals = preparedQuery.asList(withDefaults())

request.goals = goals

forward '/WEB-INF/pages/goals.gtpl'

war/WEB-INF/pages/goals.gtpl:

<% include '/WEB-INF/includes/header.gtpl' %>

<h2>Goals</h2>

<a href="/goals/add">Add Goal</a>
<br><br>
<table border="1">
  <thead>
     <tr>
        <th>Name</th>
        <th>Description</th>
        <th>Created</th>
        <th>&nbsp;</th>
     </tr>
  </thead>
  <tbody>
     <% request.goals.each { goal -> %>
        <tr>
           <td>${goal.name}</td>
           <td>${goal.description}</td>
           <td>${goal.created}</td>
           <td><a href="/goals/delete/${goal.key.id}">Delete</a> | <a href="/goals/edit/${goal.key.id}">Edit</a></td>
        </tr>
     <% } %>
  </tbody>
</table>

<% include '/WEB-INF/includes/footer.gtpl' %>

The operations insert and edit share a common view named goal.gtpl. Based on whether the request contains the attribute goal the view will be in insert or edit mode. In case an existing Goal entity was provided the form gets populated with the entity data and is ready to be updated. The Goal to be edited will be retrieved by providing the entity’s ID in the editGoal.groovy Groovlet.

war/WEB-INF/groovy/editGoal.groovy:

import com.google.appengine.api.datastore.KeyFactory
import com.google.appengine.api.datastore.Key

log.info "Editing goal"

def id = Long.parseLong(params.id)
Key key = KeyFactory.createKey("Goal", id)
def goal = datastore.get(key)

request.goal = goal
forward '/WEB-INF/pages/goal.gtpl'

war/WEB-INF/pages/goal.gtpl:

<% include '/WEB-INF/includes/header.gtpl' %>

<%
  def goal = request.getAttribute("goal")
  boolean existingKey = goal?.key
  String action = !existingKey ? 'Add' : 'Update'
%>

<h2>${action} Goal</h2>

<form action="/goals/${!existingKey ? 'insert' : 'update'}" method="POST">
   <table border="0">
      <tbody>
         <tr>
            <td>Name:</td>
            <td><input type="text" name="name" value="${goal?.name ? goal.name : ''}"></td>
         </tr>
         <tr>
            <td>Description:</td>
            <td><input type="text" name="description" value="${goal?.description ? goal.description : ''}"></td>
         </tr>
      </tbody>
      <% if(existingKey) { %>
         <input type="hidden" name="id" value="${goal.key.id}">
      <% } %>
   </table>
   <br>
   <input type="submit" value="${action}">
   <input type="button" value="Cancel" onclick="javascript:document.location.href = '/goals';">
</form>

<% include '/WEB-INF/includes/footer.gtpl' %>

At last we need to implement the Groovlets for persisting the changes to your entity. All of them are quite straight forward to implement. Gaelyk’s abstractions for the datastore work quite nicely.

war/WEB-INF/groovy/insertGoal.groovy:

import com.google.appengine.api.datastore.Entity

log.info "Inserting goal"

def goal = new Entity("Goal")
goal.name = params.name
goal.description = params.description
goal.created = new Date()
goal.save()

redirect '/goals'

war/WEB-INF/groovy/updateGoal.groovy:

import com.google.appengine.api.datastore.KeyFactory
import com.google.appengine.api.datastore.Key

log.info "Updating goal"

def id = Long.parseLong(params.id)
Key key = KeyFactory.createKey("Goal", id)
def goal = datastore.get(key)
goal.name = params.name
goal.description = params.description
goal.save()

redirect '/goals'

war/WEB-INF/groovy/deleteGoal.groovy:

import com.google.appengine.api.datastore.KeyFactory
import com.google.appengine.api.datastore.Key

log.info "Deleting goal"

def id = Long.parseLong(params.id)
Key key = KeyFactory.createKey("Goal", id)
key.delete()

redirect '/goals'
Advertisements

5 Comments »

RSS feed for comments on this post. TrackBack URI

  1. Good write up. You might even shame me into writing up my own experiences!

    One thing to note is that when you are taking param arguments and adding them to your entity with the same name you can use the following groovy sugar:

    goal << params.subMap(['name', 'description'])

    P.

  2. Nice write up!

    -Ben

  3. Great post – I found this really useful for my first gaelyk app.

  4. You can also use this sugar for your goals:


    params.with {
    goal.name = name
    goal.description = description
    }

    or

    goal.with {
    name = params.name
    description = params.description
    }

  5. Very helpful, thanks!


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.
Entries and comments feeds.

%d bloggers like this: