Grails

Grails features

  • Full stack framework + plugins
  • Groovy language
  • Convention over configuration
  • Object Relational Mapping (GORM) build on top of Hibernate
  • View technology (GSPs)
  • Controller layer built on Spring MVC
  • Embedded Tomcat
  • Dependency Injection with Spring
  • i18n support

Grails 2.1

Java setup

  1. http://www.oracle.com/technetwork/java/javase/downloads/index.html
  2. Java SE Development Kit 7u21
  3. Crear variable de ambiente JAVA_HOME
  4. (Windows) Agregar %JAVA_HOME%\bin al PATH
  5. (GNU/Linux) Agregar $JAVA_HOME/bin al PATH

Grails setup

  1. http://grails.org/download
  2. Grails 2.1.0
  3. Unzip to C:\grails-2.1.0
  4. Create GRAILS_HOME environment variable
  5. (Windows) Add %GRAILS_HOME%\bin to PATH
  6. (GNU/Linux) Add $GRAILS_HOME/bin to PATH

Test grails installation


$ grails -version

Grails version: 2.1.0
          

Create application


$ grails create-app tutorial
$ cd tutorial
          

Create controller


$ grails create-controller hello
          

grails-app/controllers/tutorial/HelloController.groovy


package tutorial

class HelloController {
  def world = {
    render "Hello World!"
  }
}
          

Run application


$ grails run-app
          

Access application

http://localhost:8080/tutorial

Integrated Development Environments

IntelliJ IDEA

http://www.jetbrains.com/idea


$ grails integrate-with --intellij
            

NetBeans

http://netbeans.org

Eclipse

http://eclipse.org

Spring Tool Suite

http://spring.io/tools/sts

Groovy/Grails Tool Suite

http://spring.io/tools/ggts

TextMate

http://macromates.com


$ grails integrate-with --textmate
            

Sublime Text

http://www.sublimetext.com/

Grails directory structure


grails-app     - top level app dir
  conf         - Configuration files
  controllers  - web controllers
  domain       - application domain models
  i18n         - i18n resource files
  services     - services layer
  taglib       - custom tag libraries
  views        - Groovy Server Pages
scripts        - scripts for grails
src            - Supporting sources
test           - unit, integration and functional tests
          

Grails commands

Running a Grails application


$ grails run-app
          

Testing a Grails application


$ grails test-app
          

Deploying a Grails application


$ grails war
          

Scaffolding


$ grails generate-all tutorial.Hello
          
  • generates skeleton code
    • controller
    • views
  • it SHOULD always be customized
  • it is only a starting point

Creating artifacts


$ grails create-controller
$ grails create-domain-class
$ grails create-unit-test
$ grails create-tag-lib
          

Configuration

Basic configuration

grails/conf/Config.groovy

Custom configuration

Set values


my.app.value = "some value"
          

Read values (in controllers/services/taglibs)


grailsApplication.config.my.app.value
          

import org.codehaus.groovy.grails.commons.*

CodeHolder.config.my.app.hello
          

Logging


log4j = {
  error 'com.example.package1', 'com.example.package2'
  warn 'com.example.package3'
}
          

Logging packages

Class loading


org.codejaus.groovy.grails.commons
            

Web request processing


org.codejaus.groovy.grails.web
            

URL mapping


org.codejaus.groovy.grails.web.mapping
            

Plugin activity


org.codejaus.groovy.grails.plugins
            

Spring activity


org.springframework
            

Hibernate activity


org.hibernate
            

GORM


grails.gorm.failOnError = true

  throws Exception on validation failure on save() method

grails.gorm.autoFlush = true

  to force flushing of Hibernate session on save(), delete(), merge() methods
          

Environments

Per environment configuration

Config files

Application code

Preset

  • dev
  • test
  • prod

Environments in command line


grails [environment] [command name]
grails run-app        // runs app in default mode (default is dev)
grails prod run-app   // runs app in production mode
grails test war       // creates war for the test environment
          

Programmatic environment detection


import grails.util.Environment

switch(Environment.current) {
  case Environment.DEVELOPMENT:
    someConfigForDev()
    break
  case Environment.PRODUCTION:
    someConfigForProd()
    break
}
          

Per environment bootstrap


def init = { ServletContext ctx ->
  environments {
    production {
      ctx.setAttribute("env", "prod")
    }
    development {
      someConfigForDev()
    }
  }
  someConfigForAllEnvironments()
}
          

Environments in application code


import grails.util.Environment

Environment.executeForCurrentEnvironment {
  production {
    someConfig()
  }
  development {
    someOtherConfig()
  }
}
          

DataSource

JDBC

  • put jar in grails project lib/ directory
  • environment aware
  • use a runtime dependency
  • 
    dependencies {
      // mysql
      runtime 'mysql:mysql-connector-java:5.1.5'
      // sqlserver
      runtime 'net.sourceforge.jtds:jtds:1.2.4'
    }
              

JDBC Configuration

  • driverClassName
  • username
  • password
  • url
  • dbCreate
  • pooled
  • logSql
  • dialect
  • properties
  • jndiName

Configuration example


dataSource {
  pooled = true
  dbCreate = "update"
  url = "jdbc:mysql://localhost/yourDB"
  driverClassName = "com.mysql.jdbc.Driver"
  dialect = org.hibernate.dialect.MySQL5InnoDBDialect
  username = "yourUser"
  password = "yourPassword"
  properties {
    maxActive = 50
      maxIdle = 25
      minIdle = 5
      initialSize = 5
      minEvictableIdleTimeMillis = 60000
      timeBetweenEvictionRunsMillis = 60000
      maxWait = 10000
      validationQuery = "/* ping */"
  }
}
          

Externalized configuration


grails.config.locations = [
  "classpath:${appName}-config.properties",
  "classpath:${appName}-config.groovy",
  "file:${userHome}/.grails/${appName}-config.properties",
  "file:${userHome}/.grails/${appName}-config.groovy"
]

read:                           
  grailsApplication
          

Versioning


// set
$ grails set-version 0.99
application.properties

// read in controllers
def version = grailsApplication.metadata['app.version']
def grailsVer = grailsApplication.metadata['app.grails.version']

def version = grails.util.GrailsUtil.grailsVersion
            

Documentation

Textile variation


src/doc/guide/1. first chapter.gdoc
src/doc/guide/2. this will be the second chapter.gdoc
          

$ grails doc    # generate documentation
          

Dependency resolution

Repositories

  • maven
  • directory

Scope

  • build
  • compile
  • runtime
  • test
  • provided

Configuration


// group:name:version
runtime "com.mysql:mysql-connector-java:5.1.5"
runtime(group: 'com.mysql', 
        name: 'mysql-connector-java',
        version: '5.1.5')

// plugin dependencies
plugins {
  test ':spock:0.5-groovy'
  runtime ':jquery:1.4.2.7'
}
          

Command line

Gant

Groovy wrapper around Apache Ant

Search locations


USER_HOME/.grails/scripts
PROJECT_HOME/scripts
PROJECT_HOME/plugins/*/scripts
GRAILS_HOME/scripts
            

Example

Script


$ grails run-app
            

Searches


USER_HOME/.grails/scripts/RunApp.groovy
PROJECT_HOME/scripts/RunApp.groovy
PLUGINS_HOME/*/scripts/RunApp.groovy
GLOBAL_PLUGINS_HOME/*/scripts/RunApp.groovy
GRAILS_HOME/scripts/RunApp.groovy
            

Ant and Maven

Ant integration


$ grails integrate-with --ant 
build.xml
            

Targets

  • clean
  • compile
  • test
  • run
  • war
  • deploy

Maven integration


$ grails create-pom com.mycompany
pom.xml
            

Targets

  • compile
  • package
  • install
  • test
  • clean
  • grails:create-controller
  • grails:create-domain-class
  • grails:create-integration-test

GORM

Domain classes

  • hold state about business processes
  • implement behavior
  • relationships between domain classes
    • one-to-one
    • one-to-many

GORM

  • Grails' Object Relational Mapping (ORM)
  • Hibernate 3

Create DB - MySQL


mysql -u root -p
create database tutorial;
create user tutorial@localhost identified by 'tutorial';
grant all on tutorial.* to tutorial@localhost;
          

Config DB Connection


grails-app/conf/DataSource.groovy:

environments {
  development {
    dataSource {
      dbCreate = "create-drop" // one of 'create', 'create-drop','update'
      //loggingSql = true
      url = "jdbc:mysql://localhost/tutorial"
      driverClassName = "com.mysql.jdbc.Driver"
      dialect = org.hibernate.dialect.MySQL5InnoDBDialect
      username = "tutorial"
      password = "tutorial"
    }
  }
}
          

Enable Maven remote


grails-app/conf/BuildConfig.groovy:

mavenCentral()
          

Add dependency


grails-app/conf/BuildConfig.groovy:

dependencies {
  runtime 'mysql:mysql-connector-java:5.1.5'
}
          

Demo


$ grails create-app tutorial        // default package: tutorial

$ grails create-domain-class Person
          

grails-app/domain/tutorial/Person.groovy


package tutorial

class Person {

  static constraints = {
  }
}
          

grails-app/domain/tutorial/Person.groovy


package tutorial

class Person {
  String name
  Integer age
  Date lastVisit

  static constraints = {
  }
}
          

Example


// grails console
import tutorial.*

// save
def p = new Person(name: 'Miguel', age: 31, lastVisit: new Date())
p.save()

// read
p = Person.get(1)
println p.name

// update
p = Person.get(1)
p.name = "Bob"
p.save()

// delete
p = Person.get(1)
p.delete()

// list
def l = Person.list()
l.each {
  println "name:${it.name}, age:${it.age}, lastVisit:${it.lastVisit}"
}
          

Relationships

Relationship

  • define how domain classes interact with each other
  • unless specified in both ends, exists only in the direction it is defined

Cardinality

  • one-to-one
  • one-to-many
  • many-to-many

Direction

  • unidirectional
  • bidirectional

Example domain classes


$ grails create-domain-class Face
$ grails create-domain-class Nose
$ grails create-domain-class Book
$ grails create-domain-class Author
          

one-to-one 1


class Face {
  Nose nose       // property
}
class Nose {
}
          
  • Defined using a property of the type of another domain class
  • unidirectional (Face -> Nose)
  • many-to-one (many faces can have a given nose)

Database


mysql> describe face;
+---------+------------+------+-----+---------+----------------+
| Field   | Type       | Null | Key | Default | Extra          |
+---------+------------+------+-----+---------+----------------+
| id      | bigint(20) | NO   | PRI | NULL    | auto_increment |
| version | bigint(20) | NO   |     | NULL    |                |
| nose_id | bigint(20) | NO   | MUL | NULL    |                |
+---------+------------+------+-----+---------+----------------+

mysql> describe nose;
+---------+------------+------+-----+---------+----------------+
| Field   | Type       | Null | Key | Default | Extra          |
+---------+------------+------+-----+---------+----------------+
| id      | bigint(20) | NO   | PRI | NULL    | auto_increment |
| version | bigint(20) | NO   |     | NULL    |                |
+---------+------------+------+-----+---------+----------------+
          

one-to-one 2


class Face {
  Nose nose
  static constraints = {
    nose unique: true
  }
}
class Nose {
}
          
  • unidirectional (Face -> Nose)
  • one-to-one (A nose can only be in one face)

Database


mysql> describe face;
+---------+------------+------+-----+---------+----------------+
| Field   | Type       | Null | Key | Default | Extra          |
+---------+------------+------+-----+---------+----------------+
| id      | bigint(20) | NO   | PRI | NULL    | auto_increment |
| version | bigint(20) | NO   |     | NULL    |                |
| nose_id | bigint(20) | NO   | UNI | NULL    |                |
+---------+------------+------+-----+---------+----------------+

mysql> describe nose;
+---------+------------+------+-----+---------+----------------+
| Field   | Type       | Null | Key | Default | Extra          |
+---------+------------+------+-----+---------+----------------+
| id      | bigint(20) | NO   | PRI | NULL    | auto_increment |
| version | bigint(20) | NO   |     | NULL    |                |
+---------+------------+------+-----+---------+----------------+
          

one-to-one 3


class Face {
  Nose nose
}
class Nose {
  static belongsTo = [ face:Face ]
}
          
  • bidirectional (Face <-> Nose)
  • many-to-one (Many faces can have a given nose)

Database


mysql> describe face;
+---------+------------+------+-----+---------+----------------+
| Field   | Type       | Null | Key | Default | Extra          |
+---------+------------+------+-----+---------+----------------+
| id      | bigint(20) | NO   | PRI | NULL    | auto_increment |
| version | bigint(20) | NO   |     | NULL    |                |
| nose_id | bigint(20) | NO   | MUL | NULL    |                |
+---------+------------+------+-----+---------+----------------+

mysql> describe nose;
+---------+------------+------+-----+---------+----------------+
| Field   | Type       | Null | Key | Default | Extra          |
+---------+------------+------+-----+---------+----------------+
| id      | bigint(20) | NO   | PRI | NULL    | auto_increment |
| version | bigint(20) | NO   |     | NULL    |                |
+---------+------------+------+-----+---------+----------------+
          

Behavior

insert/updates cascade from Face to Nose


// Nose is saved automatically
new Face(nose: new Nose()).save() 
          

the inverse ins't true


// Won't work. Face is transient
new Nose(face: new Face()).save()
          

deletes are cascaded too!


def f = new Face(1)
f.delete()        // Face and Nose are deleted
          

foreign key stored in _parent_ (Face) as nose_id

one-to-one 4


class Face {
  static hasOne = [ nose:Nose ]
}
class Nose {
  Face face
}
          
  • bidirectional (Face <-> Nose)
  • one-to-one

Database


mysql> describe face;
+---------+------------+------+-----+---------+----------------+
| Field   | Type       | Null | Key | Default | Extra          |
+---------+------------+------+-----+---------+----------------+
| id      | bigint(20) | NO   | PRI | NULL    | auto_increment |
| version | bigint(20) | NO   |     | NULL    |                |
+---------+------------+------+-----+---------+----------------+

mysql> describe nose;
+---------+------------+------+-----+---------+----------------+
| Field   | Type       | Null | Key | Default | Extra          |
+---------+------------+------+-----+---------+----------------+
| id      | bigint(20) | NO   | PRI | NULL    | auto_increment |
| version | bigint(20) | NO   |     | NULL    |                |
| face_id | bigint(20) | NO   | UNI | NULL    |                |
+---------+------------+------+-----+---------+----------------+
          

one-to-many


class Author {
  static hasMany = [ books:Book ]
  
  String name
}
class Book {
  String title
}
          
  • unidirectional (Author -> Book)
  • one-to-many (An author can have many books)
  • mapped with a join table by default

Database


mysql> describe author;
+---------+--------------+------+-----+---------+----------------+
| Field   | Type         | Null | Key | Default | Extra          |
+---------+--------------+------+-----+---------+----------------+
| id      | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| version | bigint(20)   | NO   |     | NULL    |                |
| name    | varchar(255) | NO   |     | NULL    |                |
+---------+--------------+------+-----+---------+----------------+

mysql> describe book;
+---------+--------------+------+-----+---------+----------------+
| Field   | Type         | Null | Key | Default | Extra          |
+---------+--------------+------+-----+---------+----------------+
| id      | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| version | bigint(20)   | NO   |     | NULL    |                |
| title   | varchar(255) | NO   |     | NULL    |                |
+---------+--------------+------+-----+---------+----------------+

mysql> describe author_book;
+-----------------+------------+------+-----+---------+-------+
| Field           | Type       | Null | Key | Default | Extra |
+-----------------+------------+------+-----+---------+-------+
| author_books_id | bigint(20) | YES  | MUL | NULL    |       |
| book_id         | bigint(20) | YES  | MUL | NULL    |       |
+-----------------+------------+------+-----+---------+-------+
          

Example


import tutorial.*

def a = new Author(name: 'Tolkien')

a.addToBooks(title: 'The Hobbit')
a.addToBooks(title: 'The Lord of the Rings')
a.save()

a.books.each {
  println it.title
}
          

Database


mysql> select * from author;
+----+---------+---------+
| id | version | name    |
+----+---------+---------+
|  1 |       0 | Tolkien |
+----+---------+---------+

mysql> select * from book;
+----+---------+-----------------------+
| id | version | title                 |
+----+---------+-----------------------+
|  1 |       0 | The Lord of the Rings |
|  2 |       0 | The Hobbit            |
+----+---------+-----------------------+

mysql> select * from author_book;
+-----------------+---------+
| author_books_id | book_id |
+-----------------+---------+
|               1 |       1 |
|               1 |       2 |
+-----------------+---------+
          

Behavior


// save/update are cascaded
// deletes are not cascaded
import tutorial.*

def a = Author.get(1)

a.delete()

Author.list().size()    // 0
Book.list().size()      // 2
          

Database


mysql> select * from author;
Empty set (0.00 sec)

mysql> select * from book;
+----+---------+-----------------------+
| id | version | title                 |
+----+---------+-----------------------+
|  1 |       0 | The Lord of the Rings |
|  2 |       0 | The Hobbit            |
+----+---------+-----------------------+
2 rows in set (0.00 sec)

mysql> select * from author_book;
Empty set (0.00 sec)
          

one-to-many 2


class Author {
  static hasMany = [ books:Book ]

  String name
}
class Book {
  static belongsTo =  [ author:Author ]

  String title
}
          
  • bidirectional (Author <-> Book)
  • one-to-many (An author can have many books, a book has only an author)

Database


mysql> describe author;
+---------+--------------+------+-----+---------+----------------+
| Field   | Type         | Null | Key | Default | Extra          |
+---------+--------------+------+-----+---------+----------------+
| id      | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| version | bigint(20)   | NO   |     | NULL    |                |
| name    | varchar(255) | NO   |     | NULL    |                |
+---------+--------------+------+-----+---------+----------------+

mysql> describe book;
+-----------+--------------+------+-----+---------+----------------+
| Field     | Type         | Null | Key | Default | Extra          |
+-----------+--------------+------+-----+---------+----------------+
| id        | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| version   | bigint(20)   | NO   |     | NULL    |                |
| author_id | bigint(20)   | NO   | MUL | NULL    |                |
| title     | varchar(255) | NO   |     | NULL    |                |
+-----------+--------------+------+-----+---------+----------------+
          

Example


import tutorial.*

def a = new Author(name: 'Tolkien')
def b = new Book(title: 'The Hobbit')
def b2 = new Book(title: 'The Lord of the Rings')

a.addToBooks(b)
a.addToBooks(b2)
a.save()

println(a.books.size())    // 2
a.books.each {
  println it.title
}
// The Hobbit
// The Lord of the Rings
println b.author.name   // Tolkien
println b2.author.name  // Tolkien
          

Database


mysql> select * from author;
+----+---------+---------+
| id | version | name    |
+----+---------+---------+
|  1 |       0 | Tolkien |
+----+---------+---------+

mysql> select * from book;
+----+---------+-----------+-----------------------+
| id | version | author_id | title                 |
+----+---------+-----------+-----------------------+
|  1 |       0 |         1 | The Hobbit            |
|  2 |       0 |         1 | The Lord of the Rings |
+----+---------+-----------+-----------------------+
          

many-to-many


class Book {
  static belongsTo = Author
  static hasMany = [ authors:Author ]

  String title
}
class Author {
  static hasMany = [ books:Book ]
  
  String name
}
          
  • hasMany on both sides
  • belongsTo on the owned (subordinated) side of the relationship
  • owning side takes responsibility for persisting relationship
  • owning side cascade saves
  • use a join table

Example


import tutorial.*

new Author(name: 'Tolkien')
    .addToBooks(new Book(title: 'The Hobbit'))
    .addToBooks(new Book(title: 'The Lord of the Rings'))
    .save()
    
println Author.list().size()    // 1 
println Book.list().size()      // 2
          

Database


mysql> select * from author;
+----+---------+---------+
| id | version | name    |
+----+---------+---------+
|  1 |       0 | Tolkien |
+----+---------+---------+

mysql> select * from book;
+----+---------+-----------------------+
| id | version | title                 |
+----+---------+-----------------------+
|  1 |       0 | The Hobbit            |
|  2 |       0 | The Lord of the Rings |
+----+---------+-----------------------+

mysql> select * from author_books;
+-----------+---------+
| author_id | book_id |
+-----------+---------+
|         1 |       1 |
|         1 |       2 |
+-----------+---------+
          

Example


import tutorial.*

new Book(title: 'The C programming language')
    .addToAuthors(name: 'Kernighan')
    .addToAuthors(name: 'Ritchie')
    .save()
    
println Author.list().size()    // 1 
println Book.list().size()      // 3
          

Database


mysql> select * from author;
+----+---------+---------+
| id | version | name    |
+----+---------+---------+
|  1 |       0 | Tolkien |
+----+---------+---------+

mysql> select * from book;
+----+---------+----------------------------+
| id | version | title                      |
+----+---------+----------------------------+
|  1 |       0 | The Hobbit                 |
|  2 |       0 | The Lord of the Rings      |
|  3 |       0 | The C programming language |
+----+---------+----------------------------+

mysql> select * from author_books;
+-----------+---------+
| author_id | book_id |
+-----------+---------+
|         1 |       1 |
|         1 |       2 |
+-----------+---------+
          

Save/Update


def p = Person.get(1)
p.name = "Bob"
p.save()

// no SQL update guaranteed in that point
// Hibernate batches SQL statements

def p = Person.get(1)
p.name = "Bob"
p.save(flush: true)       // Forces a synchronization with DB

// Handling exceptions on saving
def p = Person.get(1)
try {
    p.save(flush:true)
}
catch(Exception e) {
    // deal with exception
}
          

Delete


def p = Person.get(1)
p.delete()

// same as save/update

def p = Person.get(1)
p.delete(flush:true)      // Forces synchronization with DB

// Handling exceptions
def p = Person.get(1)
try {
  p.delete(flush:true) 
} catch(org.springframework.dao.DataIntegrityViolationException e) {
  // deal with exception
}
          

Tips

http://spring.io/blog/2010/06/23/gorm-gotchas-part-1

http://spring.io/blog/2010/07/02/gorm-gotchas-part-2

http://spring.io/blog/2010/07/28/gorm-gotchas-part-3

Eager and lazy fetching


// by default lazy

class Location {
  String city
}
class Author {
  String name
  Location location
}
Author.list().each { author ->
  println author.location.city
}

// If there are N = 4 authors
// 1 query to fetch all authors
// 1 query per author to get the location (because we're printing the city)
// Total = 4 + 1 = N + 1 querys
          

Eager loading


class Author {
  String name
  Location location

  static mapping = {
    location fetch: 'join'
  }
}

Author.list(fetch: [location: 'join']).each { a -> 
  println a.location.city
}
          

Eager loading


// Dynamic finders
Author.findAllByNameLike("John%", 
  [ sort: 'name', 
    order: 'asc', 
    fetch: [location: 'join'] ]).each { a ->
  // ...
}

// Criteria queries
def authors = Author.withCriteria {
  like("name", "John%")
  join "location"
}
          

Querying


// list
def books = Book.list()
def books = Book.list(offset:10, max:20)
def books = Book.list(sort:"title", order: "desc")

// retrieval
def b = Book.get(23)
def list = Book.getAll(1,3,24)
          

Dynamic finders


class Book {
  String title
  Date releaseDate
  Author author
}
class Author {
  String name
}

def b
b = Book.findByTitle("The Hobbit")
b = Book.findByTitleLike("%Hobb%")
b = Book.findByReleaseDateBetween(firstDate, secondDate)
b = Book.findByReleaseDateGreaterThan(someDate)
b = Book.findByTitleLikeOrReleaseDateLessThan("%obbi%", someDate)
b = Book.findByReleaseDateIsNull()
b = Book.findByReleaseDateIsNotNull()
b = Book.findAllByTitleLike("The %", 
    [max:3, offset:10, sort: "title", order: "desc"])
          

Criteria


def c = Book.createCriteria()
def results = c {
  eq("releaseDate", someDate)
  or {
    like("title", "%programming%")
    like("title", "%Ring%")
  }
  maxResults(100)
  order("title", "desc")
}
          

HQL


def list = Book.findAll(
  "from Book as b where b.title like 'Lord of the%'")
def list = Book.findAll(
  "from Book as b where b.author = ?",
  [author])
def list = Book.findAll(
  "from Book as b where b.author = :author",
  [author:someAuthor])
          

Controllers

Controllers

  • handle request
  • create response
    • can generate the response
    • delegate to a view
  • scope: request (a new instance is created for each client request)
  • Class with Controller suffix
  • grails-app/controllers

Create controller


$ grails create-controller book     // default package: tutorial
          

grails-app/controllers/tutorial/BookController.groovy


package tutorial

class BookController {
  def index = {}
}

// mapped to /book URI
          

Controller actions


// properties that are assigned a block of code
// each property maps to an URI
// public by default

class BookController {
  def list = {
    // some statements
  }
}

// maps to /book/list
          

Default action


static defaultAction = "list"
          
  1. if only one action exists, the default URI maps to it
  2. if an index action exists, it handle requests when no action specified
  3. explicit declaration

Scopes

  • servletContext: application wide
  • session: session of a user
  • request: current request
  • params: _mutable_ map of incoming request params
  • flash: only for this request and the subsequent (e.g. set a message before redirect)

Accessing scopes


class BookController {
  def find = {
    def findBy = params["findBy"]
    def userAgent = request.getHeader("User-Agent")       
    def loggedUser = session["logged_user"] 
        // session.logged_user
  }
  
  def delete = {
    def b = Book.get( params.id )
    if(!b) {
        flash.message = "User not found for id ${params.id}"
        redirect(action:list)
      }
    }
  }
}
          

Models and views

Model

  • Map of objects that the view uses to render the response
  • Keys of map translate to variables in the view

Explicit return of model


def show = {
  [ book: Book.get(params.id) ]
}
          

Implicit return of model


class BookController {
  List books
  List authors
  
  def list = {
    books = Book.list()
    authors = Author.list()
  }
}
          

Implicit view


class BookController {
  def show = {
    [ book:Book.get(params.id) ]
  }
}
          

Grails looks for view at

  • grails-app/views/book/show.jsp
  • grails-app/views/book/show.gsp

Explicit view


def show = {
  def map = [ book: Book.get(1) ]
  render(view: "display", model: map)
}
          

Grails will try

  • grails-app/views/book/display.jsp
  • grails-app/views/book/display.gsp

    def show = {
      def map = [ book: Book.get(1) ]
      render(view: "/shared/display", model: map)
    }
          

Grails will try

  • grails-app/views/shared/display.jsp
  • grails-app/views/shared/display.gsp

Direct rendering of the response


class BookController {
  def greet = {
    render "hello!"
  }
}
          

Redirect


class BookController {
  def greet = {
    render "hello!"
  }

  def redirect     = {
    redirect(action: greet)
  }
}
          

Redirect expects

other closure on the same class


redirect(action:list)
          

controller and action


redirect(controller: 'author', action: 'list')
          

URI


redirect(uri: "/help.html")
          

URL


redirect(url: 'http://yahoo.com')
          

Data binding


// implicit constructor
def save = {
  def b = new Book(params)
  b.save()
}

// explicit binding
def save = {
  def b = Book.get(params.id)
  b.properties = params   // sets every parameter 
                          // as a property in the object
  b.someParam = params.foo   // only some parameters are set
  b.otherParam = params.bar
  b.save()
}
          

JSON and XML responses

XML


def listXML = {
  def results = Book.list()
  render(contentType: 'text/xml') {
    books {
      for(b in results) {
        book(title: b.title)
      }
    }
  }
}
          

Output



  
  

          

JSON


def listJSON = {
  def results = Book.list()
  render(contentType: 'text/json') {
    books = array {
      for(b in results) {
        book(title: b.title)
      }
    }
  }
}
          

Output


"books":[
  {"title": "title one"},
  {"title": "title two"}
]
          

Automatic XML and JSON marshaling

XML


import grails.converters.*

def list = {
  render Book.list() as XML
}

def list2 = {
  render Book.list().encodeAsXML() // using codecs
}
          

JSON


render Book.list() as JSON

render Book.list().encodeAsJSON()
          

Type converters


def total = params.int('total')
def checked = params.boolean('checked')
          
  • null safe
  • safe from parsing errors

Groovy Server Pages

GSP

  • similar to ASP, JSP
  • more flexible
  • live in grails-app/views
  • rendered automatically (by convention) or with the render method
  • Mark-up (HTML) and GSP tags
  • embedded logic possible but _discouraged_
  • uses the model passed by the controller action

Controller


// returns a model with key called book.
def show = {
  [ book: Book.get(1) ]
}
          

View


<%-- the key named book from the model is referenced by 
    name in the gsp --%>
<%= book.title %>
          

GSP Basics


<% %> blocks  to embed groovy code (discouraged)

<html>
  <body>
    <% out << "Hello world!" %>
    <%= "this is equivalent" %>
  </body>
</html>
          

Variables in GSPs


<% now = new Date() %>
  ...

Time: <%= now %>

GSP Expressions


<html>
  <body>
    Hello ${params.name}
    Time is: ${new Date()}
    2 + 2 is: ${ 2 + 2 }
    but also: ${ /* any valid groovy statement */ }
  </body>
</html>
          

GSP built-in tags


<g:example param="a string param"
  otherParam="${new Date()}" 
  aMap="[aString:'a string', aDate: new Date()]">
  Hello world
</g:example>
          
  • start with g: prefix
  • no need to import tag libraries

<g:set/>


<g:set var="myVar" value="${new Date()}"/>

<g:set var="myText">
  some text with expressions ${myVar}
</g:set>
<p>${myText}</p>
          

Logic/Iteration


<g:if test="${1 > 3}">
  some html
</g:if>
<g:else>
  something else
</g:else>

<g:each in="${tutorial.Book.list()}" var="b">
  title: ${b.title}
</g:each>
                                                     
<g:set var="n" value="${0}"/>
<g:while test="${n<5}">
  ${n++}         
</g:while>
          

Links and resources


<g:link action="show" id="2">Item 2</g:link>
<g:link controller="user" action="list">Users</g:link>
          

Forms and fields


<g:form name="myForm" 
      url="[controller:'book', action:'submit']">
  Text: <g:textField name="text"/>
  <g:submitButton name="button" value="Submit form"/>
</g:form>
          
  • textField
  • checkbox
  • radio
  • hiddenField
  • select
  • submitButton

Tags and method calls

In views


Static Resource: ${createLinkTo(dir:"images", file:"logo.jpg")}

<img src="${createLinkTo(dir:'images', file:'logo.jpg')}" />
          

In controllers and taglibs


def imageLocation = createLinkTo(dir:"images", file:"logo.jpg")        
def imageLocation = g.createLinkTo(dir:"images", file:"logo.jpg")
          

Templates

  • maintainable and reusable chunks of views
  • name starts with a underscore: _myTemplate.gsp

grails-app/views/book/_info.gsp


<div class="book" id="${book.id}">
  Title: ${book.title}
</div>
          

From other view


<g:render template="info" 
    var="book" 
    collection="${tutorial.Book.list()}"/>
          

From controller


def useTemplate = {
  def b = Book.get(1)
  render(template: "info", model: [ book:b ])
}
          

GSP debugging

GSPs

Add "showSource=true" to the url. Only in development mode

Templates

Add "debugTemplates" to the url. Only in development mode

Tag libraries

Tag libraries

  • groovy class that ends with TagLib
  • live in grails-app/taglib
  • $ grails create-tag-lib utils
  • implicit out variable. Refers to the output Writer

Taglibs 1

Taglib


class UtilsTagLib {
  def copyright = { attrs, body ->
    out << "© Copyright 2014"
  }
}
          

GSP


<div><g:copyright/></div>
          

HTML


<div>© Copyright 2014</div>
          

Example 2

Taglib


import java.text.*

def dateFormat = { attrs, body ->
  def fmt = new SimpleDateFormat(attrs.format)
  out << fmt.format(attrs.date)
}
          

GSP


Date: <g:dateFormat format="dd-MM-yyyy" date="${new Date()}"/>
          

HTML


Date: 23-04-2014
          

Example 3

Taglib


def formatBook = { attrs, body ->
  out << render(template: "info", model: [ book:attrs.book ])
}
          

GSP


<div><g:formatBook book="${tutorial.Book.get(1)}"/></div>
          

View


<div>
<div class="book" id="1">
  Title: Some title
</div>
</div>
          

AJAX

Javascript


<head>
  <g:javascript library="prototype"/>
  <g:javascript library="scriptaculous"/>
</head>
          

Remote link

View


    <g:remoteLink action="delete" id="1">
    Delete Book
    </g:remoteLink>
            

asynchronous request to the delete action of the current controller with an id parameter with value of 1

Controller


class AjaxController {
  def index = {}

  def delete = {
    def b = Book.get(params.id)
    b.delete()
    render "Book ${b.title} was deleted"
  }
}
            

Independent updates for failure and success

View


Success: <div id="success"></div>
Failure: <div id="error"></div>
<g:remoteLink action="success" 
      update="[success:'success', failure:'error']">
  Success ajax request
</g:remoteLink><br/>
<g:remoteLink action="failure" 
      update="[success:'success', failure:'error']">
  Failure ajax request
</g:remoteLink>
            

Controller


def success = {
  render status:200, text:"request OK"
}

def failure = {
  render status:503, text:"request failed"
}
            

Ajax form submission

View


<g:formRemote url="[controller:'ajax', action:'ajaxAdd']"
      name="ajaxForm"
      update="[success:'addSuccess', failure:'addError']">
  Book title:  <input type="text" name="title"/>
  <input type="submit" value="Add Book!" />
</g:formRemote >
<div id="addSuccess"></div>
<div id="addError"></div>
            

Controller


def ajaxAdd = {
  def b = new Book(params)
  b.save()
  render "Book '${b.title}' created"
}
            

Ajax returning content

View


<g:remoteLink action="ajaxContent"
      update="book">
  Update Content
</g:remoteLink>
<div id="book"><!--existing book mark-up --></div>
            

Controller


def ajaxContent = {
  def b = Book.get(2)
  render "Book: ${b.title} found at ${new Date()}!"
}
            

Ajax returning JSON

View


<g:javascript>
  function updateBook(e) {
    // evaluate the JSON
    var book = eval("("+e.responseText+")")
    $("book_title").innerHTML = book.title
  }
</g:javascript>
<g:remoteLink action="ajaxData" 
      update="foo" 
      onSuccess="updateBook(e)">
  Update Book with JSON
</g:remoteLink>
<div id="book">
  <div id="book_title">The Hobbit</div>
</div>
            

Controller


import grails.converters.*

def ajaxData = {
  def b = new Book(title: 'new book title').save()
  render b as JSON
}
            

Version control

Subversion

Create repository


$ svnadmin create /home/miguel/repo
$ svn list file:///home/miguel/repo
            

Create empty dir in repository (remote create)


$ svn mkdir file:///home/miguel/repo/GrailsProject
$ svn mkdir file:///home/miguel/repo/GrailsProject/branches
$ svn mkdir file:///home/miguel/repo/GrailsProject/tags
$ svn mkdir file:///home/miguel/repo/GrailsProject/trunk
            

Create grails project (local)


$ grails create-app GrailsProject
            

Add grails code to working copy, inline


$ cd GrailsProject
$ svn co file:///home/miguel/repo/GrailsProject/trunk .
$ svn add .classpath .settings .project *
$ svn propset svn:ignore "WEB-INF" web-app/
$ svn propset svn:ignore "target" .
$ svn rm --force web-app/WEB-INF
$ svn commit -m "First commit of GrailsProject"
            

Checkout


$ cd ..
$ svn co file:///home/miguel/repo/GrailsProject/trunk OtherUserGrailsProject
$ cd OtherUserGrailsProject
$ grails upgrade
            

Git

Create repository


$ grails create-app GrailsProject
$ cd GrailsProject
$ git init

.gitignore:
web-app/WEB-INF/
target/

$ git add .
$ git commit -m "First commit on GrailsProject"    
            

Clone


$ cd ..
$ git clone GrailsProject OtherUserGrailsProject
$ cd OtherUserGrailsProject
$ grails upgrade
            

Thanks!

Miguel Cobá

http://miguel.leugim.com.mx

Github: miguelcoba

miguel.coba@gmail.com