$ 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