Post on 23-Nov-2014
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 << "© 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.