Grails

Post on 23-Nov-2014

482 views 1 download

Tags:

transcript

1/173 - 1%

Grails Introduction

2/173 - 2%

Introduction

3/173 - 2%

Grailsfull 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

4/173 - 3%

Installationhttp://grails.org/Download (grails-1.3.5.zip)

Add GRAILS_HOME environment variable

Add %GRAILS_HOME%\bin to PATH

5/173 - 3%

Test Grails installation$ grailsWelcome to Grails 1.3.5 - http://grails.org/Licensed under Apache Standard License 2.0Grails home is set to: /home/miguel/.grails

No script name specified. Use 'grails help' for more info or 'grails interactive' to enter interactive mode

6/173 - 4%

First Grails application

7/173 - 5%

Create application$ grails create-app tutorial$ cd tutorial

8/173 - 5%

Create controller$ grails create-controller hello

9/173 - 6%

grails-app/controllers/tutorial/HelloController.groovy

package tutorial

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

10/173 - 6%

Run application$ grails run-app

11/173 - 7%

browse http://localhost:8080/tutorial

12/173 - 7%

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

13/173 - 8%

Grails directory structuregrails-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 Pagesscripts - scripts for grailssrc - Supporting sourcestest - unit, integration and functional tests

14/173 - 9%

Grails commands

15/173 - 9%

Running a Grails application$ grails run-app

16/173 - 10%

Testing a Grails application$ grails test-app

17/173 - 10%

Deploying a Grails application$ grails war

18/173 - 11%

Scaffolding$ grails generate-all tutorial.Hello

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

19/173 - 11%

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

20/173 - 12%

Configuration

21/173 - 13%

Basic configurationgrails/conf/Config.groovy

22/173 - 13%

custom configurationset: my.app.value = "some value"

read (controller/taglibs): grailsApplication.config.my.app.value

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

CodeHolder.config.my.app.hello

23/173 - 14%

logginglog4j = { error 'package1', 'package2' warn 'package3'}

24/173 - 14%

logging packagesorg.codejaus.groovy.grails.commons - class loadingorg.codejaus.groovy.grails.web - web request processingorg.codejaus.groovy.grails.web.mapping - url mappingorg.codejaus.groovy.grails.plugins - plugin activityorg.springframework - spring activityorg.hibernate - hibernate activity

25/173 - 15%

GORMgrails.gorm.failOnError

save() method throws Exception on validation failure

grails.gorm.autoFlush=true

save(),delete(),merge() to flush session

26/173 - 16%

Environmentsper environment configuration

- Config.groovy- DataSource.groovy

preset- dev- test- prod

27/173 - 16%

Environments in command linegrails [environment] [command name]grails test war // creates war for the test //environment

28/173 - 17%

Programmatic environmentdetection

import grails.util.Environment

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

29/173 - 17%

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

30/173 - 18%

Environments in application codeimport grails.util.EnvironmentEnvironment.executeForCurrentEnvironment { production { someConfig() } development { someOtherConfig() }}

31/173 - 18%

DataSourceJDBC

- 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'}

32/173 - 19%

JDBC configuration- driverClassName- username- password- url- dbCreate- pooled- logSql- dialect- properties - jndiName

33/173 - 20%

JDBC configuration exampledataSource { 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 */" }}

34/173 - 20%

Externalized configurationconfiguration

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

35/173 - 21%

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

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

def version = grails.util.GrailsUtil.grailsVersion

36/173 - 21%

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

- $ grails doc # generate documentation

37/173 - 22%

Dependency resolution- Repositories - maven - directory

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

38/173 - 22%

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

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

39/173 - 23%

Command line

40/173 - 24%

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

41/173 - 24%

Ant- $ grails integrate-with --ant - build.xml - ivy.xml - ivisettings.xml

- CuiseControl/Hudson

42/173 - 25%

GORM

43/173 - 25%

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

44/173 - 26%

GORM- Grails' Object Relational Mapping (ORM)- Hibernate 3

45/173 - 27%

Create DB MySQLmysql -uroot -pcreate database tutorial;create user tutorial@localhost identified by 'tutorial';grant all on tutorial.* to tutorial@localhost;

46/173 - 27%

Config database connectiongrails-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" } }}

47/173 - 28%

Enable Maven remotegrails-app/conf/BuildConfig.groovy:mavenCentral()

48/173 - 28%

Add dependencygrails-app/conf/BuildConfig.groovy:dependencies { runtime 'mysql:mysql-connector-java:5.1.5'}

49/173 - 29%

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

$ grails create-domain-class Person

50/173 - 29%

grails-app/domain/tutorial/Person.groovy

package tutorial

class Person {

static constraints = { }}

51/173 - 30%

grails-app/domain/tutorial/Person.groovy

package tutorial

class Person { String name Integer age Date lastVisit

static constraints = { }}

52/173 - 31%

Example// grails consoleimport tutorial.*

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

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

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

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

// listdef l = Person.list()l.each { println

53/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

54/173 - 32%

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

55/173 - 32%

one-to-one 1class 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)

56/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 | MUL | NULL | |+---------+------------+------+-----+---------+----------------+

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

57/173 - 33%

one-to-one 2class Face { Nose nose static constraints = { nose unique: true }}class Nose {}

// unidirectional (Face -> Nose)// one-to-one (A nose can only be in one face)

58/173 - 34%

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 | |+---------+------------+------+-----+---------+----------------+

59/173 - 35%

one-to-one 3class Face { Nose nose}class Nose { static belongsTo = [ face:Face ]}

// bidirectional (Face <-> Nose)// many-to-one (Many faces have can have a given nose)

60/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 | |+---------+------------+------+-----+---------+----------------+

61/173 - 36%

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

62/173 - 36%

one-to-one 3class Face { static hasOne = [ nose:Nose ]}class Nose { Face face}

// bidirectional (Face <-> Nose)// one-to-one

63/173 - 37%

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 | |+---------+------------+------+-----+---------+----------------+

64/173 - 37%

one-to-manyclass 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

65/173 - 38%

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 | |+-----------------+------------+------+-----+---------+-------+

66/173 - 39%

Exampleimport 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}

67/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 |+-----------------+---------+

68/173 - 40%

behavior// save/update are cascaded// deletes are not cascadedimport tutorial.*

def a = Author.get(1)

a.delete()

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

69/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)

70/173 - 41%

one-to-many 2class 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)

71/173 - 42%

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 | |+-----------+--------------+------+-----+---------+----------------+

72/173 - 42%

Exampleimport 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()) // 2a.books.each { println it.title}// The Hobbit// The Lord of the Ringsprintln b.author.name // Tolkienprintln b2.author.name // Tolkien

73/173 - 43%

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 |+----+---------+-----------+-----------------------+

74/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}

75/173 - 44%

Exampleimport 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

76/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 |+-----------+---------+

77/173 - 45%

Exampleimport 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

78/173 - 46%

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 |+-----------+---------+

79/173 - 46%

Save/updatedef 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 savingdef p = Person.get(1)try { p.save(flush:true)}catch(Exception e) { // deal with exception}

80/173 - 47%

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

// same as save/update

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

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

81/173 - 47%

Tipshttp://blog.springsource.com/2010/06/23/gorm-gotchas-part-1/

82/173 - 48%

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

83/173 - 48%

Eager loadingclass Author { String name Location location

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

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

84/173 - 49%

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

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

85/173 - 50%

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

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

86/173 - 50%

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

def bb = 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"])

87/173 - 51%

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

88/173 - 51%

HQLdef 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])

89/173 - 52%

Controllers

90/173 - 53%

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

91/173 - 53%

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

92/173 - 54%

grails-app/controllers/tutorial/BookController.groovy

package tutorial

class BookController { def index = {}}

// mapped to /book URI

93/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

94/173 - 55%

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"

95/173 - 55%

ScopesservletContext - application widesession - session of a userrequest - current requestparams - _mutable_ map of incoming request paramsflash - only for this request and the subsequent (e.g. set a message before redirect)

96/173 - 56%

Accessing scopesclass 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) } } }

97/173 - 57%

Models and viewsModel

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

98/173 - 57%

Explicit return of modeldef show = { [ book: Book.get(params.id) ]}

99/173 - 58%

Implicit return of modeclass BookController { List books List authors

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

100/173 - 58%

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

// grails look for view at // grails-app/views/book/show.gsp (show.jsp first)

101/173 - 59%

Explicit viewdef 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

102/173 - 59%

Direct rendering of the responseclass BookController { def greet = { render "hello!" }}

103/173 - 60%

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

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

104/173 - 61%

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')

105/173 - 61%

Data binding// implicit constructordef save = { def b = new Book(params) b.save()}

// explicit bindingdef 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()}

106/173 - 62%

JSON and XML responses

107/173 - 62%

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

108/173 - 63%

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

109/173 - 64%

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

110/173 - 64%

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

111/173 - 65%

Automatic XML and JSONmarshaling

112/173 - 65%

XMLimport grails.converters.*

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

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

113/173 - 66%

JSONrender Book.list() as JSON

render Book.list().encodeAsJSON()

114/173 - 66%

Type convertersdef total = params.int('total')def checked = params.boolean('checked')

// null safe// safe from parsing errors

115/173 - 67%

Groovy Server Pages

116/173 - 68%

GSPsimilar 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

117/173 - 68%

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

118/173 - 69%

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

119/173 - 69%

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

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

120/173 - 70%

Variables in GSPs<% now = new Date() %> ...<p>Time: <%= now %></p>

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

121/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>

122/173 - 71%

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>

123/173 - 72%

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

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

124/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>

125/173 - 73%

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

126/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

127/173 - 74%

Tags as method callsIn 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")

128/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 ])}

129/173 - 75%

GSP debuggingGSPs

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

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

130/173 - 76%

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

131/173 - 76%

Taglibs 1Taglib

class UtilsTagLib { def copyright = { attrs, body -> out << "&copy; Copyright 2010" }}

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

132/173 - 77%

Example 2Taglib

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()}"/>

133/173 - 77%

Example 3Taglib

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

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

134/173 - 78%

AJAX

135/173 - 79%

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

136/173 - 79%

Remote linkview

<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" }}

137/173 - 80%

Independent updates for failure and successView

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>

138/173 - 80%

Independent updates for failure and successController

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

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

139/173 - 81%

Ajax form submissionview

<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"}

140/173 - 81%

Ajax returning contentview

<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()}!"}

141/173 - 82%

Ajax returning JSONview

<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

142/173 - 83%

Version Control

143/173 - 83%

SubversionCreate 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

144/173 - 84%

GitCreate 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

145/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.

146/173 - 85%

Grails Advanced

147/173 - 85%

Advanced

148/173 - 86%

Excel

149/173 - 87%

Options

Apache POI

Grails export plugin

Grails excel-import plugin

150/173 - 87%

Excel with Apache POISource

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

151/173 - 88%

package tutorial

import org.apache.poi.hssf.model.Workbook;import org.apache.poi.hssf.usermodel.HSSFWorkbook;import org.apache.poi.hslf.model.Sheetimport 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) }}

152/173 - 88%

Web Services

153/173 - 89%

Options

REST: Grails only

SOAP: Grails xfire plugin

SOAP: Grails springws plugin

154/173 - 90%

SOAP with XFire pluginSource

http://groovy.codehaus.org/Using+the+Grails+XFire+plugin+and+GroovyWS

Install XFire plugin

$ grails install-plugin xfire$ grails create-service test

155/173 - 90%

Test Servicepackage 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 }}

156/173 - 91%

WSDLhttp://localhost:8080/tutorial/services/test?wsdl

157/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"

158/173 - 92%

Consuming the service$ groovy client.groovy10 AUD are worth 1000.0 USD

159/173 - 92%

Jasper

160/173 - 93%

Options

Grails jasper plugin

Grails dynamic-jasper plugin

161/173 - 94%

Reports with dynamic-jasper pluginSource

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

162/173 - 94%

User controller class

package tutorial

class UserController {

def scaffold = true}

163/173 - 95%

User domain class

package tutorial

class User { String username String name String lastName Boolean active

static reportable = [:]

static constraints = { }}

164/173 - 95%

View a reporthttp://localhost:8080/tutorial/djReport/index?entity=user

165/173 - 96%

jQuery

166/173 - 96%

Options

Thousands of jQuery libraries

jQuery UI

jQuery Tools

167/173 - 97%

jQuery ToolsSource

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>

168/173 - 98%

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>

169/173 - 98%

Book Controllerclass BookController { def jquery = {}}

170/173 - 99%

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>

171/173 - 99%

jQuery Toolshttp://jqueryui.com/http://jqueryui.com/demo

- Accordion- Autocomplete- Button- Dialog- Datepicker- Progressbar- Tabs- Slider

172/173 - 100%

SecuritySpring Security Core Plugin

http://www.grails.org/plugin/spring-security-corehttp://burtbeckwith.github.com/grails-spring-security-core/

- Spring Security- DB- LDAP- OpenID- CAS

173/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.