Example code in GitHub
lyhcode.info
CodeData
http://www.codedata.com.tw/tag/groovy/
html.html {
head {
title("Groovy Servlet")
}
body {
ul {
['Java', 'Groovy', 'Scala'].each {
li("Hello, ${request.remoteHost}")
}
}
}
}
<html>
<head>
<title>GSP</title>
</head>
<body>
<ul>
<% ['Java', 'Groovy', 'Scala'].each { %>
<li>Hello, ${it}</li>
<% } %>
</ul>
</body>
</html>
in
Groovy programming language
for
Java developers
Don't Reinvent the Wheel
(Grails 2.4.3)
Model
View Decorator
http://localhost:8080/user/forgetPassword/103
class UserController {
def forgetPassword(User user) {
sendMail {
to user.email
subject "Forget Your Password?"
body "Your password is ${user.password}"
}
respond user
}
}
domain-specific runtime environments
使用 GVM 管理開發環境
http://gvmtool.net
# Install GVM
curl -s get.gvmtool.net | bash
# Install Grails
gvm install grails
grails create-app realestate
grails-app
web-app
wrapper
src
test
scripts
lib
application.properties
grails run-app
Server running. Browse to
http://localhost:8080/realestate
房屋 {
縣市;
鄉鎮區;
地址;
坪數;
售價;
完成日期;
}
grails create-domain-class house
Domain Class
grails-app/domain/realestate/House.groovy
Unit Test
test/unit/realestate/HouseSpec.groovy
class House {
String city
String region
String address
float feet
int price
Date buildDate
}
@TestFor(House)
class HouseSpec extends Specification {
void "create a house"() {
given:
def house = new House(
city: 'Taichung',
region: 'Taiping',
address: 'Yu Cai Rd. No. 440',
feet: 45,
price: 960,
buildDate: new Date()
)
when:
house.save(flush: true)
then:
House.countByCity('Taichung') > 0
}
}
grails test-app
Browse to
target/test-reports/html/index.html
grails shell
$ grails generate-all realestate.House
$ grails run-app
Browse to
http://localhost:8080/realestate/house
grails-app/conf/UrlMappings.groovy
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?(.$format)?"{
constraints {
// apply constraints here
}
}
}
}
http://localhost:8080/realestate/house/index.json
http://localhost:8080/realestate/house/index.xml
http://localhost:8080/realestate/house/show/1.json
http://localhost:8080/realestate/house/show/1.xml
a framework that
focuses on providing both authentication and authorization
to Java applications.
plugins {
// Spring Security Core Plugin
compile ":spring-security-core:2.0-SNAPSHOT"
}
grails s2-quickstart realestate User Role
// Added by the Spring Security Core plugin:
grails.plugin.springsecurity.userLookup.userDomainClassName = 'realestate.User'
grails.plugin.springsecurity.userLookup.authorityJoinClassName = 'realestate.UserRole'
grails.plugin.springsecurity.authority.className = 'realestate.Role'
grails.plugin.springsecurity.controllerAnnotations.staticRules = [
'/**': ['permitAll'],
'/index': ['permitAll'],
'/index.gsp': ['permitAll'],
'/assets/**': ['permitAll'],
'/**/js/**': ['permitAll'],
'/**/css/**': ['permitAll'],
'/**/images/**': ['permitAll'],
'/**/favicon.ico': ['permitAll']
]
def roleUser = new Role(authority: 'ROLE_USER')
def roleAdmin = new Role(authority: 'ROLE_ADMIN')
roleUser.save flush: true
roleAdmin.save flush: true
def admin = new User(
username: 'admin',
password: 'admin',
enabled: true,
accountExpired: false,
accountLocked: false,
passwordExpired: false
)
admin.save flush: true
UserRole.create(admin, roleUser, true)
UserRole.create(admin, roleAdmin, true)
grails-app/conf/BootStrap.groovy
class BootStrap {
def init = { servletContext ->
//...
}
def destroy = {
}
}
grails-app/conf/DataSource.groovy
dialect = realestate.ImprovedH2Dialect
src/groovy/realestate/ImprovedH2Dialect.groovy
public class ImprovedH2Dialect extends H2Dialect {
@Override
public String getDropSequenceString(String sequenceName) {
return "drop sequence if exists " + sequenceName;
}
@Override
public boolean dropConstraints() {
return false;
}
}
<sec:ifLoggedIn>...</sec:ifLoggedIn>
<sec:username />
grails-app/views/index.gsp
<sec:ifLoggedIn>
<g:link uri="/j_spring_security_logout">Logout</g:link>
<h1>Hello, <sec:username />. Welcome to Grails</h1>
</sec:ifLoggedIn>
<sec:ifNotLoggedIn>
<g:link controller="login" action="auth">Login</g:link>
<h1>Welcome to Grails</h1>
</sec:ifNotLoggedIn>
for controller or actions
@Secured(["ROLE_ADMIN"])
grails-app/conf/BuildConfig.groovy
dependencies {
// OAuth required scribe framework
runtime "org.scribe:scribe:1.3.5"
}
plugins {
// Spring Security Core Plugin
compile ":spring-security-core:2.0-SNAPSHOT"
// Spring Security OAuth2
compile (":oauth:2.5") { exclude "scribe" }
compile ":spring-security-oauth:2.1.0-RC4"
compile ":spring-security-oauth-facebook:0.2"
}
grails s2-init-oauth realestate OAuthID
Add relationship to User domain class.
static hasMany = [oAuthIDs: OAuthID]
grails-app/conf/Config.groovy
// OAuth Facebook
oauth {
providers {
facebook {
key = '363532783795859'
secret = '----App Secret----'
}
}
}
~/.grails/realestate-config.groovy
oauth.providers.facebook.secret = '....'
grails.serverURL = "http://dev.teamcollab.org:8080/realestate"
grails-app/views/index.gsp
<oauth:connect provider="facebook">Sign-in with Facebook</oauth:connect>
plugins {
// Twitter Bootstrap
runtime ":twitter-bootstrap:3.2.0.2"
}
plugins {
compile ":asset-pipeline:1.9.6"
// Uncomment these to enable additional asset-pipeline capabilities
//compile ":sass-asset-pipeline:1.9.0"
//compile ":less-asset-pipeline:1.10.0"
//compile ":coffee-asset-pipeline:1.8.0"
//compile ":handlebars-asset-pipeline:1.3.0.3"
}
grails-app/assets/javascripts/application.js
//= require bootstrap
grails-app/assets/stylesheets/application.css
/*
*= require bootstrap
*/
Apply a "layout" for views.
<html>
<head>
<meta name="layout" content="main" />
<title>Page Title</title>
<!DOCTYPE html>
<html>
<head>
<title><g:layoutTitle default="Untitled" /></title>
<g:layoutHead/>
</head>
<body>
<g:layoutBody/>
</body>
</html>
class BootstrapTagLib {
static namespace = "bs3"
def alert = { attrs, body ->
out << "<div class=\"alert alert-${attrs.type?:'success'}\">"
out << body()
out << "</div>"
}
}
<bs3:alert><b>Well done!</b> You successfully ...</bs3:alert>
plugins {
// Elasticsearch
compile ":elasticsearch:0.0.3.3"
}
elasticSearch {
datastoreImpl = "hibernateDatastore"
date.formats = [
"yyyy-MM-dd'T'HH:mm:ss'Z'"
]
client.hosts = [
[host: 'localhost', port: 9300]
]
defaultExcludedProperties = ["password"]
disableAutoIndex = false
bulkIndexOnStartup = true
maxBulkRequest = 500
searchableProperty.name = 'searchable'
}
environments {
development {
elasticSearch.client.mode = 'local'
}
test {
elasticSearch {
client.mode = 'local'
index.store.type = 'memory'
}
}
production {
elasticSearch.client.mode = 'node'
//elasticSearch.client.mode = 'local'
}
}
class House {
static searchable = true
//...
}
def search() {
render House.search(params.q) as JSON
}
/house/search?q=KEYWORDS
dependencies {
test "org.seleniumhq.selenium:selenium-firefox-driver:2.42.2"
test "org.gebish:geb-spock:0.9.3"
}
plugins {
test ":geb:0.9.3"
}
grails test-app functional:spock
grails war