Grails – Integrated Development Environments – Logging packages



Grails – Integrated Development Environments – Logging packages

0 0


grails-intro

Grails Introduction

On Github miguelcoba / grails-intro

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 features

Grails 2.1

Java setup

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

Grails setup

http://grails.org/download Grails 2.1.0 Unzip to C:\grails-2.1.0 Create GRAILS_HOME environment variable (Windows) Add %GRAILS_HOME%\bin to PATH (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"
if only one action exists, the default URI maps to it if an index action exists, it handle requests when no action specified 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

<books>
  <book title="title one"></book>
  <book title="title two"></book>
</books>

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() %>
  ...
<p>Time: <%= now %></p>

<!--   
  predefined:
  - application
  - applicationContext
  - flash
  - grailsApplication
  - out
  - params
  - request
  - response
  - session
  - webRequest
-->

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: <strong>${b.title}</strong> 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