$ grails -version
Grails version: 2.1.0
          
$ grails create-app tutorial
$ cd tutorial
          
$ grails create-controller hello
          grails-app/controllers/tutorial/HelloController.groovy
package tutorial
class HelloController {
  def world = {
    render "Hello World!"
  }
}
          
$ grails run-app
          
$ grails integrate-with --intellij
            
$ grails integrate-with --textmate
            
grails-app     - top level app dir
  conf         - Configuration files
  controllers  - web controllers
  domain       - application domain models
  i18n         - i18n resource files
  services     - services layer
  taglib       - custom tag libraries
  views        - Groovy Server Pages
scripts        - scripts for grails
src            - Supporting sources
test           - unit, integration and functional tests
          
$ grails run-app
          
$ grails test-app
          
$ grails war
          
$ grails generate-all tutorial.Hello
          
$ grails create-controller
$ grails create-domain-class
$ grails create-unit-test
$ grails create-tag-lib
          grails/conf/Config.groovy
Set values
my.app.value = "some value"
          Read values (in controllers/services/taglibs)
grailsApplication.config.my.app.value
          
import org.codehaus.groovy.grails.commons.*
CodeHolder.config.my.app.hello
          
log4j = {
  error 'com.example.package1', 'com.example.package2'
  warn 'com.example.package3'
}
          Class loading
org.codejaus.groovy.grails.commons
            Web request processing
org.codejaus.groovy.grails.web
            URL mapping
org.codejaus.groovy.grails.web.mapping
            Plugin activity
org.codejaus.groovy.grails.plugins
            Spring activity
org.springframework
            Hibernate activity
org.hibernate
            
grails.gorm.failOnError = true
  throws Exception on validation failure on save() method
grails.gorm.autoFlush = true
  to force flushing of Hibernate session on save(), delete(), merge() methods
          Config files
Application code
grails [environment] [command name]
grails run-app        // runs app in default mode (default is dev)
grails prod run-app   // runs app in production mode
grails test war       // creates war for the test environment
          
import grails.util.Environment
switch(Environment.current) {
  case Environment.DEVELOPMENT:
    someConfigForDev()
    break
  case Environment.PRODUCTION:
    someConfigForProd()
    break
}
          
def init = { ServletContext ctx ->
  environments {
    production {
      ctx.setAttribute("env", "prod")
    }
    development {
      someConfigForDev()
    }
  }
  someConfigForAllEnvironments()
}
          
import grails.util.Environment
Environment.executeForCurrentEnvironment {
  production {
    someConfig()
  }
  development {
    someOtherConfig()
  }
}
          
dependencies {
  // mysql
  runtime 'mysql:mysql-connector-java:5.1.5'
  // sqlserver
  runtime 'net.sourceforge.jtds:jtds:1.2.4'
}
          
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 */"
  }
}
          
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
          
// 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
            Textile variation
src/doc/guide/1. first chapter.gdoc
src/doc/guide/2. this will be the second chapter.gdoc
          
$ grails doc    # generate documentation
          
// 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'
}
          
USER_HOME/.grails/scripts
PROJECT_HOME/scripts
PROJECT_HOME/plugins/*/scripts
GRAILS_HOME/scripts
            Script
$ grails run-app
            Searches
USER_HOME/.grails/scripts/RunApp.groovy
PROJECT_HOME/scripts/RunApp.groovy
PLUGINS_HOME/*/scripts/RunApp.groovy
GLOBAL_PLUGINS_HOME/*/scripts/RunApp.groovy
GRAILS_HOME/scripts/RunApp.groovy
            
$ grails integrate-with --ant 
build.xml
            
$ grails create-pom com.mycompany
pom.xml
            
mysql -u root -p
create database tutorial;
create user tutorial@localhost identified by 'tutorial';
grant all on tutorial.* to tutorial@localhost;
          
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"
    }
  }
}
          
grails-app/conf/BuildConfig.groovy:
mavenCentral()
          
grails-app/conf/BuildConfig.groovy:
dependencies {
  runtime 'mysql:mysql-connector-java:5.1.5'
}
          
$ grails create-app tutorial        // default package: tutorial
$ grails create-domain-class Person
          
package tutorial
class Person {
  static constraints = {
  }
}
          
package tutorial
class Person {
  String name
  Integer age
  Date lastVisit
  static constraints = {
  }
}
          
// grails console
import tutorial.*
// save
def p = new Person(name: 'Miguel', age: 31, lastVisit: new Date())
p.save()
// read
p = Person.get(1)
println p.name
// update
p = Person.get(1)
p.name = "Bob"
p.save()
// delete
p = Person.get(1)
p.delete()
// list
def l = Person.list()
l.each {
  println "name:${it.name}, age:${it.age}, lastVisit:${it.lastVisit}"
}
          
$ grails create-domain-class Face
$ grails create-domain-class Nose
$ grails create-domain-class Book
$ grails create-domain-class Author
          
class Face {
  Nose nose       // property
}
class Nose {
}
          
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    |                |
+---------+------------+------+-----+---------+----------------+
          
class Face {
  Nose nose
  static constraints = {
    nose unique: true
  }
}
class Nose {
}
          
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    |                |
+---------+------------+------+-----+---------+----------------+
          
class Face {
  Nose nose
}
class Nose {
  static belongsTo = [ face:Face ]
}
          
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    |                |
+---------+------------+------+-----+---------+----------------+
          
// Nose is saved automatically
new Face(nose: new Nose()).save() 
          
// Won't work. Face is transient
new Nose(face: new Face()).save()
          
def f = new Face(1)
f.delete()        // Face and Nose are deleted
          
class Face {
  static hasOne = [ nose:Nose ]
}
class Nose {
  Face face
}
          
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    |                |
+---------+------------+------+-----+---------+----------------+
          
class Author {
  static hasMany = [ books:Book ]
  
  String name
}
class Book {
  String title
}
          
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    |       |
+-----------------+------------+------+-----+---------+-------+
          
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
}
          
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 |
+-----------------+---------+
          
// 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
          
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)
          
class Author {
  static hasMany = [ books:Book ]
  String name
}
class Book {
  static belongsTo =  [ author:Author ]
  String title
}
          
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    |                |
+-----------+--------------+------+-----+---------+----------------+
          
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
          
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 |
+----+---------+-----------+-----------------------+
          
class Book {
  static belongsTo = Author
  static hasMany = [ authors:Author ]
  String title
}
class Author {
  static hasMany = [ books:Book ]
  
  String name
}
          
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
          
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 |
+-----------+---------+
          
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
          
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 |
+-----------+---------+
          
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
}
          
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
}
          http://spring.io/blog/2010/06/23/gorm-gotchas-part-1
// 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
          
class Author {
  String name
  Location location
  static mapping = {
    location fetch: 'join'
  }
}
Author.list(fetch: [location: 'join']).each { a -> 
  println a.location.city
}
          
// 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"
}
          
// 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)
          
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"])
          
def c = Book.createCriteria()
def results = c {
  eq("releaseDate", someDate)
  or {
    like("title", "%programming%")
    like("title", "%Ring%")
  }
  maxResults(100)
  order("title", "desc")
}
          
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])
          
$ grails create-controller book     // default package: tutorial
          
package tutorial
class BookController {
  def index = {}
}
// mapped to /book URI
          
// 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
          
static defaultAction = "list"
          
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)
      }
    }
  }
}
          
def show = {
  [ book: Book.get(params.id) ]
}
          
class BookController {
  List books
  List authors
  
  def list = {
    books = Book.list()
    authors = Author.list()
  }
}
          
class BookController {
  def show = {
    [ book:Book.get(params.id) ]
  }
}
          
def show = {
  def map = [ book: Book.get(1) ]
  render(view: "display", model: map)
}
          
    def show = {
      def map = [ book: Book.get(1) ]
      render(view: "/shared/display", model: map)
    }
          
class BookController {
  def greet = {
    render "hello!"
  }
}
          
class BookController {
  def greet = {
    render "hello!"
  }
  def redirect     = {
    redirect(action: greet)
  }
}
          
redirect(action:list)
          
redirect(controller: 'author', action: 'list')
          
redirect(uri: "/help.html")
          
redirect(url: 'http://yahoo.com')
          
// 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()
}
          
def listXML = {
  def results = Book.list()
  render(contentType: 'text/xml') {
    books {
      for(b in results) {
        book(title: b.title)
      }
    }
  }
}
          
   
          
def listJSON = {
  def results = Book.list()
  render(contentType: 'text/json') {
    books = array {
      for(b in results) {
        book(title: b.title)
      }
    }
  }
}
          
"books":[
  {"title": "title one"},
  {"title": "title two"}
]
          
import grails.converters.*
def list = {
  render Book.list() as XML
}
def list2 = {
  render Book.list().encodeAsXML() // using codecs
}
          
render Book.list() as JSON
render Book.list().encodeAsJSON()
          
def total = params.int('total')
def checked = params.boolean('checked')
          
// returns a model with key called book.
def show = {
  [ book: Book.get(1) ]
}
          
<%-- the key named book from the model is referenced by 
    name in the gsp --%>
<%= book.title %>
          
<% %> blocks  to embed groovy code (discouraged)
<html>
  <body>
    <% out << "Hello world!" %>
    <%= "this is equivalent" %>
  </body>
</html>
          
<% now = new Date() %>
  ...
Time: <%= now %>
          
<html>
  <body>
    Hello ${params.name}
    Time is: ${new Date()}
    2 + 2 is: ${ 2 + 2 }
    but also: ${ /* any valid groovy statement */ }
  </body>
</html>
          
<g:example param="a string param"
  otherParam="${new Date()}" 
  aMap="[aString:'a string', aDate: new Date()]">
  Hello world
</g:example>
          
<g:set var="myVar" value="${new Date()}"/>
<g:set var="myText">
  some text with expressions ${myVar}
</g:set>
<p>${myText}</p>
          
<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>
          
<g:link action="show" id="2">Item 2</g:link>
<g:link controller="user" action="list">Users</g:link>
          
<g:form name="myForm" 
      url="[controller:'book', action:'submit']">
  Text: <g:textField name="text"/>
  <g:submitButton name="button" value="Submit form"/>
</g:form>
          
Static Resource: ${createLinkTo(dir:"images", file:"logo.jpg")}
<img src="${createLinkTo(dir:'images', file:'logo.jpg')}" />
          
def imageLocation = createLinkTo(dir:"images", file:"logo.jpg")        
def imageLocation = g.createLinkTo(dir:"images", file:"logo.jpg")
          
<div class="book" id="${book.id}">
  Title: ${book.title}
</div>
          
<g:render template="info" 
    var="book" 
    collection="${tutorial.Book.list()}"/>
          
def useTemplate = {
  def b = Book.get(1)
  render(template: "info", model: [ book:b ])
}
          Add "showSource=true" to the url. Only in development mode
Add "debugTemplates" to the url. Only in development mode
class UtilsTagLib {
  def copyright = { attrs, body ->
    out << "© Copyright 2014"
  }
}
          
<div><g:copyright/></div>
          
<div>© Copyright 2014</div>
          
import java.text.*
def dateFormat = { attrs, body ->
  def fmt = new SimpleDateFormat(attrs.format)
  out << fmt.format(attrs.date)
}
          
Date: <g:dateFormat format="dd-MM-yyyy" date="${new Date()}"/>
          
Date: 23-04-2014
          
def formatBook = { attrs, body ->
  out << render(template: "info", model: [ book:attrs.book ])
}
          
<div><g:formatBook book="${tutorial.Book.get(1)}"/></div>
          
<div>
<div class="book" id="1">
  Title: Some title
</div>
</div>
          
<head>
  <g:javascript library="prototype"/>
  <g:javascript library="scriptaculous"/>
</head>
          
    <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
class AjaxController {
  def index = {}
  def delete = {
    def b = Book.get(params.id)
    b.delete()
    render "Book ${b.title} was deleted"
  }
}
            
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>
            
def success = {
  render status:200, text:"request OK"
}
def failure = {
  render status:503, text:"request failed"
}
            
<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>
            
def ajaxAdd = {
  def b = new Book(params)
  b.save()
  render "Book '${b.title}' created"
}
            
<g:remoteLink action="ajaxContent"
      update="book">
  Update Content
</g:remoteLink>
<div id="book"><!--existing book mark-up --></div>
            
def ajaxContent = {
  def b = Book.get(2)
  render "Book: ${b.title} found at ${new Date()}!"
}
            
<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>
            
import grails.converters.*
def ajaxData = {
  def b = new Book(title: 'new book title').save()
  render b as JSON
}
            
$ svnadmin create /home/miguel/repo
$ svn list file:///home/miguel/repo
            
$ 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
            
$ grails create-app GrailsProject
            
$ 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"
            
$ cd ..
$ svn co file:///home/miguel/repo/GrailsProject/trunk OtherUserGrailsProject
$ cd OtherUserGrailsProject
$ grails upgrade
            
$ grails create-app GrailsProject
$ cd GrailsProject
$ git init
.gitignore:
web-app/WEB-INF/
target/
$ git add .
$ git commit -m "First commit on GrailsProject"    
            
$ cd ..
$ git clone GrailsProject OtherUserGrailsProject
$ cd OtherUserGrailsProject
$ grails upgrade
            Miguel Cobá
Github: miguelcoba