Está en la página 1de 173

Grails Introduction

1/173 - 1%
Introduction

2/173 - 2%
Grails
full stack framework + plugins
Groovy language
Convention over configuration
Object Relational Mapping (GORM) built on top Hibernate
View technology (GSPs)
Controller layer built on Spring MVC
Embedded Tomcat
Dependency Injection with Spring
i18n support

3/173 - 2%
Installation
http://grails.org/Download (grails-1.3.5.zip)
Add GRAILS_HOME environment variable
Add %GRAILS_HOME%\bin to PATH

4/173 - 3%
Test Grails installation
$ grails
Welcome to Grails 1.3.5 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: /home/miguel/.grails

No script name specified. Use 'grails help' for more info or


'grails interactive' to enter interactive mode

5/173 - 3%
First Grails application

6/173 - 4%
Create application
$ grails create-app tutorial
$ cd tutorial

7/173 - 5%
Create controller
$ grails create-controller hello

8/173 - 5%
grails-app/controllers/tutorial
/HelloController.groovy
package tutorial

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

9/173 - 6%
Run application
$ grails run-app

10/173 - 6%
browse http://localhost:8080/tutorial

11/173 - 7%
IDE
- IntelliJ IDEA
grails integrate-with --intellij
- NetBeans (grails supported)
- Eclipse (SpringSource Tool Suite, STS)
- TextMate
grails integrate-with --textmate

12/173 - 7%
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
13/173 - 8%
Grails commands

14/173 - 9%
Running a Grails application
$ grails run-app

15/173 - 9%
Testing a Grails application
$ grails test-app

16/173 - 10%
Deploying a Grails application
$ grails war

17/173 - 10%
Scaffolding
$ grails generate-all tutorial.Hello

- generates skeleton code


- controller
- views
- it SHOULD always be customized
- it is only a starting point

18/173 - 11%
Creating artifacts
$ grails create-controller
$ grails create-domain-class
$ grails create-unit-test
$ grails create-tag-lib

19/173 - 11%
Configuration

20/173 - 12%
Basic configuration
grails/conf/Config.groovy

21/173 - 13%
custom configuration
set:
my.app.value = "some value"
read (controller/taglibs):
grailsApplication.config.my.app.value
import org.codehaus.groovy.grails.commons.*

CodeHolder.config.my.app.hello

22/173 - 13%
logging
log4j = {
error 'package1', 'package2'
warn 'package3'
}

23/173 - 14%
logging packages
org.codejaus.groovy.grails.commons - class loading
org.codejaus.groovy.grails.web - web request processing
org.codejaus.groovy.grails.web.mapping - url mapping
org.codejaus.groovy.grails.plugins - plugin activity
org.springframework - spring activity
org.hibernate - hibernate activity

24/173 - 14%
GORM
grails.gorm.failOnError

save() method throws Exception on validation failure


grails.gorm.autoFlush=true
save(),delete(),merge() to flush session

25/173 - 15%
Environments
per environment configuration
- Config.groovy
- DataSource.groovy
preset
- dev
- test
- prod

26/173 - 16%
Environments in command line
grails [environment] [command name]
grails test war // creates war for the test
//environment

27/173 - 16%
Programmatic environment
detection
import grails.util.Environment

switch(Environment.current) {
case Environment.DEVELOPMENT:
someConfigForDev()
break
case Environment.PRODUCTION:
someConfigForProd()
break
}
28/173 - 17%
Per environment bootstrap
def init = { ServletContext ctx ->
environments {
production {
ctx.setAttribute("env", "prod")
}
development {
someConfigForDev()
}
}
someConfigForAllEnvironments()
}
29/173 - 17%
Environments in application code
import grails.util.Environment
Environment.executeForCurrentEnvironment {
production {
someConfig()
}
development {
someOtherConfig()
}
}

30/173 - 18%
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'
}
31/173 - 18%
JDBC configuration
- driverClassName
- username
- password
- url
- dbCreate
- pooled
- logSql
- dialect
- properties
- jndiName

32/173 - 19%
JDBC 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 */"
}
}
33/173 - 20%
Externalized configuration
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

34/173 - 20%
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

35/173 - 21%
Documentation
- Textile variation
- src/doc/guide
1. first chapter.gdoc
2. this will be the second chapter.gdoc

- $ grails doc # generate documentation

36/173 - 21%
Dependency resolution
- Repositories
- maven
- directory

- Scope
- build
- compile
- runtime
- test
- provided

37/173 - 22%
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'
}
38/173 - 22%
Command line

39/173 - 23%
Gant
- Groovy wrapper around Apache Ant
- Search locations:
- USER_HOME/.grails/scripts
- PROJECT_HOME/scripts
- PROJECT_HOME/plugins/*/scripts
- GRAILS_HOME/scripts
- example
$ 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
40/173 - 24%
Ant
- $ grails integrate-with --ant
- build.xml
- ivy.xml
- ivisettings.xml

- CuiseControl/Hudson

41/173 - 24%
GORM

42/173 - 25%
Domain classes
- hold state about business processes
- implement behavior
- relationships between domain classes
- one-to-one
- one-to-many

43/173 - 25%
GORM
- Grails' Object Relational Mapping (ORM)
- Hibernate 3

44/173 - 26%
Create DB MySQL
mysql -uroot -p
create database tutorial;
create user tutorial@localhost identified by 'tutorial';
grant all on tutorial.* to tutorial@localhost;

45/173 - 27%
Config database 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"
}
}
}

46/173 - 27%
Enable Maven remote
grails-app/conf/BuildConfig.groovy:
mavenCentral()

47/173 - 28%
Add dependency
grails-app/conf/BuildConfig.groovy:
dependencies {
runtime 'mysql:mysql-connector-java:5.1.5'
}

48/173 - 28%
Demo
$ grails create-app tutorial // default package: tutorial

$ grails create-domain-class Person

49/173 - 29%
grails-app/domain/tutorial
/Person.groovy
package tutorial

class Person {

static constraints = {
}
}

50/173 - 29%
grails-app/domain/tutorial
/Person.groovy
package tutorial

class Person {
String name
Integer age
Date lastVisit
static constraints = {
}
}
51/173 - 30%
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 52/173 - 31%
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
53/173 - 31%
Example domain classes
$ grails create-domain-class Face
$ grails create-domain-class Nose
$ grails create-domain-class Book
$ grails create-domain-class Author

54/173 - 32%
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 have can have a given nose)

55/173 - 32%
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 | |
+---------+------------+------+-----+---------+----------------+

56/173 - 33%
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)
57/173 - 33%
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 | |
+---------+------------+------+-----+---------+----------------+

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

59/173 - 35%
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 | |
+---------+------------+------+-----+---------+----------------+

60/173 - 35%
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

61/173 - 36%
one-to-one 3
class Face {
static hasOne = [ nose:Nose ]
}
class Nose {
Face face
}

// bidirectional (Face <-> Nose)


// one-to-one

62/173 - 36%
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 | |
+---------+------------+------+-----+---------+----------------+

63/173 - 37%
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
64/173 - 37%
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 | |
+-----------------+------------+------+-----+---------+-------+

65/173 - 38%
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
}
66/173 - 39%
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 |
+-----------------+---------+

67/173 - 39%
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

68/173 - 40%
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)

69/173 - 40%
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)

70/173 - 41%
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 | |
+-----------+--------------+------+-----+---------+----------------+

71/173 - 42%
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

72/173 - 42%
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 |
+----+---------+-----------+-----------------------+

73/173 - 43%
many-to-many
// 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

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

String title
}
class Author {
static hasMany = [ books:Book ]

String name
}

74/173 - 43%
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

75/173 - 44%
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 |
+-----------+---------+

76/173 - 44%
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

77/173 - 45%
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 |
+-----------+---------+

78/173 - 46%
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
}
79/173 - 46%
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
}

80/173 - 47%
Tips
http://blog.springsource.com/2010/06/23/gorm-gotchas-part-1/

81/173 - 47%
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

82/173 - 48%
Eager loading
class Author {
String name
Location location

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

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


println a.location.city
}

83/173 - 48%
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"
}

84/173 - 49%
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)

85/173 - 50%
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"])
86/173 - 50%
Criteria
def c = Book.createCriteria()
def results = c {
eq("releaseDate", someDate)
or {
like("title", "%programming%")
like("title", "%Ring%")
}
maxResults(100)
order("title", "desc")
}

87/173 - 51%
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])

88/173 - 51%
Controllers

89/173 - 52%
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

90/173 - 53%
Create controller
$ grails create-controller book // default package: tutorial

91/173 - 53%
grails-app/controllers/tutorial
/BookController.groovy
package tutorial

class BookController {
def index = {}
}

// mapped to /book URI

92/173 - 54%
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

93/173 - 54%
Default action
// 1. if only one action exists, the default URI
// maps to it
// 2. if an index action exists, it handle request
// when no action specified
// 3. explicit declaration
static defaultAction = "list"

94/173 - 55%
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)

95/173 - 55%
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)
}
}
}

96/173 - 56%
Models and views
Model
- Map of objects that the view uses to render the
response
- Keys of map translate to variables in the view

97/173 - 57%
Explicit return of model
def show = {
[ book: Book.get(params.id) ]
}

98/173 - 57%
Implicit return of mode
class BookController {
List books
List authors

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

99/173 - 58%
Implicit view
class BookController {
def show = {
[ book:Book.get(params.id) ]
}
}
// grails look for view at
// grails-app/views/book/show.gsp (show.jsp first)

100/173 - 58%
Explicit view
def show = {
def map = [ book: Book.get(1) ]
render(view: "display", model: map)
}

// grails will try 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.gsp

101/173 - 59%
Direct rendering of the response
class BookController {
def greet = {
render "hello!"
}
}

102/173 - 59%
Redirect
class BookController {
def greet = {
render "hello!"
}

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

103/173 - 60%
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')

104/173 - 61%
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()
}

105/173 - 61%
JSON and XML responses

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

107/173 - 62%
<books>
<book title="title one"/>
<book title="title two"/>
</books>

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

109/173 - 64%
"books":[
{"title": "title one"},
{"title": "title two"}
]

110/173 - 64%
Automatic XML and JSON
marshaling

111/173 - 65%
XML
import grails.converters.*

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

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

112/173 - 65%
JSON
render Book.list() as JSON

render Book.list().encodeAsJSON()

113/173 - 66%
Type converters
def total = params.int('total')
def checked = params.boolean('checked')

// null safe
// safe from parsing errors

114/173 - 66%
Groovy Server Pages

115/173 - 67%
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

116/173 - 68%
Controller
// returns a model with key called book.
def show = {
[ book: Book.get(1) ]
}

117/173 - 68%
View
<%-- the key named book from the model is referenced by
name in the gsp --%>
<%= book.title %>

118/173 - 69%
GSP Basics
<% %> blocks to embed groovy code (discouraged)

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

119/173 - 69%
Variables in GSPs
<% now = new Date() %>
...
<p>Time: <%= now %></p>
<!--
predefined:
- application
- applicationContext
- flash
- grailsApplication
- out
- params
- request
- response
- session
- webRequest
-->
120/173 - 70%
GSP Expressions
${expression}
<html>
<body>
Hello ${params.name}
Time is: ${new Date()}
2 + 2 is: ${ 2 + 2 }
but also: ${ /* any valid groovy statement */ }
</body>
</html>

121/173 - 70%
GSP built-in tags
- start with g: prefix
- no need to import tag libraries
<g:example param="a string param"
otherParam="${new Date()}"
aMap="[aString:'a string', aDate: new Date()]">
Hello world
</g:example>

122/173 - 71%
<g:set/>
<g:set var="myVar" value="${new Date()}"/>

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

123/173 - 72%
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>124/173 - 72%
Links and resources
<g:link action="show" id="2">Item 2</g:link>
<g:link controller="user" action="list">Users</g:link>

125/173 - 73%
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>
fields
- textField
- checkbox
- radio
- hiddenField
- select
- submitButton

126/173 - 73%
Tags as method calls
In views
Static Resource: ${createLinkTo(dir:"images", file:"logo.jpg")}

<img src="/image/intro/${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")

127/173 - 74%
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 ])
}

128/173 - 74%
GSP debugging
GSPs
- Add "showSource=true" to the url. Only in development mode
Templates
Add "debugTemplates" to the url. Only in development mode

129/173 - 75%
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

130/173 - 76%
Taglibs 1
Taglib
class UtilsTagLib {
def copyright = { attrs, body ->
out << "&copy; Copyright 2010"
}
}
view
<div><g:copyright/></div>

131/173 - 76%
Example 2
Taglib
import java.text.*
def dateFormat = { attrs, body ->
def fmt = new SimpleDateFormat(attrs.format)
out << fmt.format(attrs.date)
}
view
<g:dateFormat format="dd-MM-yyyy" date="${new Date()}"/>

132/173 - 77%
Example 3
Taglib
def formatBook = { attrs, body ->
out << render(template: "info", model: [ book:attrs.book ])
}
view
<div><g:formatBook book="${tutorial.Book.get(1)}"/></div>

133/173 - 77%
AJAX

134/173 - 78%
Javascript
<head>
<g:javascript library="prototype"/>
<g:javascript library="scriptaculous"/>
</head>

135/173 - 79%
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"
}
}

136/173 - 79%
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>

137/173 - 80%
Independent updates for failure and success
Controller
def success = {
render status:200, text:"request OK"
}

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

138/173 - 80%
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"
}

139/173 - 81%
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()}!"
}

140/173 - 81%
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
141/173 - 82%
Version Control

142/173 - 83%
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
$ grails upgrade

143/173 - 83%
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
$ grails upgrade

144/173 - 84%
Thanks
Miguel Cobá

miguel@trantaria.com

Groovy Intro by Miguel Cobá is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.

145/173 - 84%
Grails Advanced

146/173 - 85%
Advanced

147/173 - 85%
Excel

148/173 - 86%
Options
Apache POI

Grails export plugin

Grails excel-import plugin

149/173 - 87%
Excel with Apache POI
Source
http://www.michaelvanvliet.nl/node/4
Get POI
- http://www.apache.org/dyn/closer.cgi/poi/release/
- unzip package
- put *.jar in tutorial/lib
- grails create-controller excelPOI

150/173 - 87%
package tutorial

import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hslf.model.Sheet
import org.apache.poi.hssf.record.formula.functions.Row
class ExcelPOIController {
def index = {
def nameOfWorkbook = 'workbook.xls'
def locationToStoreWorkbook = '/tmp/' // Or C:\temp
def wb = new HSSFWorkbook()
def sheet = wb.createSheet("Demo")
def row = sheet.createRow((short)0)
row.createCell(1).setCellValue('Column 1')
row.createCell(2).setCellValue('Column 2')
row = sheet.createRow((short)1);
row.createCell(0).setCellValue('Row 1')
row.createCell(1).setCellValue('Hello')
row.createCell(2).setCellValue('World')
def fileWorkbook = new FileOutputStream(locationToStoreWorkbook +
nameOfWorkbook)
wb.write(fileWorkbook)
fileWorkbook.close()
render('Workbook was saved to: ' + locationToStoreWorkbook +
nameOfWorkbook)
}
}

151/173 - 88%
Web Services

152/173 - 88%
Options
REST: Grails only

SOAP: Grails xfire plugin

SOAP: Grails springws plugin

153/173 - 89%
SOAP with XFire plugin
Source
http://groovy.codehaus.org/Using+the+Grails+XFire+plugin+and+GroovyWS
Install XFire plugin
$ grails install-plugin xfire
$ grails create-service test

154/173 - 90%
Test Service
package tutorial

class TestService {

boolean transactional = false

static expose=['xfire']

static conversions = [
'AUD': [ 'USD': 100.00D, 'GBP': 44.44D ],
'USD': [ 'AUD': 1.00D, 'GBP': 88.88D ],
'GBP': [ 'AUD': 22.22D, 'USD': 33.33D ]
]

Double convert(String from, String to, Double amount) {


conversions[from][to] * amount
}
}

155/173 - 90%
WSDL
http://localhost:8080/tutorial/services/test?wsdl

156/173 - 91%
Web Service consumer
client.groovy

import groovyx.net.ws.WSClient

@Grab(group='org.codehaus.groovy.modules', module='groovyws', version='0.5.2')


def getProxy(wsdl, classLoader) {
new WSClient(wsdl, classLoader)
}
proxy = getProxy("http://localhost:8080/tutorial/services/test?wsdl", this.class.classLoader)
proxy.initialize()

result = proxy.convert("AUD", "USD", 10.0)


println "10 AUD are worth ${result} USD"

157/173 - 91%
Consuming the service
$ groovy client.groovy
10 AUD are worth 1000.0 USD

158/173 - 92%
Jasper

159/173 - 92%
Options
Grails jasper plugin

Grails dynamic-jasper plugin

160/173 - 93%
Reports with dynamic-jasper plugin
Source
http://www.grails.org/plugin/dynamic-jasper
Install dynamic-jasper plugin
$ grails install-plugin dynamic-jasper
$ grails create-domain-class User
$ grails create-controller user

161/173 - 94%
User controller class
package tutorial
class UserController {

def scaffold = true


}

162/173 - 94%
User domain class
package tutorial

class User {
String username
String name
String lastName
Boolean active
static reportable = [:]
static constraints = {
}
}

163/173 - 95%
View a report
http://localhost:8080/tutorial/djReport/index?entity=user

164/173 - 95%
jQuery

165/173 - 96%
Options
Thousands of jQuery libraries

jQuery UI

jQuery Tools

166/173 - 96%
jQuery Tools
Source
http://flowplayer.org/tools/download/index.html
Install jQuery Tools
<script src="http://cdn.jquerytools.org/1.2.5/full/jquery.tools.min.js">
</script>

167/173 - 97%
grails-app/view/book/jquery.gsp
<html>
<head>
<script src="http://cdn.jquerytools.org/1.2.5/full/jquery.tools.min.js">
</script>
</head>
<body>
</body>
</html>

168/173 - 98%
Book Controller
class BookController {
def jquery = {}
}

169/173 - 98%
Date Input
<!-- dateinput styling -->
<link rel="stylesheet"
type="text/css"
href="http://static.flowplayer.org/tools/
demos/dateinput/css/skin1.css"/>
<!-- HTML5 date input -->
<input type="date" />

<!-- make it happen -->


<script>
$(":date").dateinput();
</script>

170/173 - 99%
jQuery Tools
http://jqueryui.com/
http://jqueryui.com/demo
- Accordion
- Autocomplete
- Button
- Dialog
- Datepicker
- Progressbar
- Tabs
- Slider
171/173 - 99%
Security
Spring Security Core Plugin
http://www.grails.org/plugin/spring-security-core
http://burtbeckwith.github.com/grails-spring-security-core/

- Spring Security
- DB
- LDAP
- OpenID
- CAS

172/173 - 100%
Thanks
Miguel Cobá

miguel@trantaria.com

Groovy Intro by Miguel Cobá is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.

173/173 - 100%

También podría gustarte