Groovy on Grails

lyhcode

TWJUG Sep 20, 2014

Hello, World.

 

Slides

 

Example code in GitHub

https://github.com/lyhcode/grails-realestate

lyhcode.info

  • Senior Software Engineer
  • vCard: lyhcode.info

Groovy Tutorial

CodeData

http://www.codedata.com.tw/tag/groovy/

Projects

  • PLWeb
  • ContPub
  • CodeCanaan
  • FoodPrint.ws
  • E7READ

Groovy for Web Development

Groovylets

  • Java Servlets in Groovy
  • http://groovy.codehaus.org/Groovlets
html.html {
    head {
        title("Groovy Servlet")
    }
    body {
        ul {
            ['Java', 'Groovy', 'Scala'].each {
                li("Hello, ${request.remoteHost}")
            }
        }
    }
}

GSP

  • Groovy Servlet Pages
  • http://groovy.codehaus.org/GSP
<html>
<head>
    <title>GSP</title>
</head>
<body>
    <ul>
    <% ['Java', 'Groovy', 'Scala'].each { %>
        <li>Hello, ${it}</li>
    <% } %>
    </ul>
</body>
</html>

Grails

Grails

an Open Source,

full stack,

web application framework for the JVM.

Modern Web Development

in

Groovy programming language

for

Java developers

Don't Reinvent the Wheel

Spring

Hibernate

 

(Grails 2.4.3)

GORM

Model

 

SiteMesh

View Decorator

Based on Groovy

Groovy DSL

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

DSR

domain-specific runtime environments

Spring IO Platform
Spring IO Platform

Install

GVM

使用 GVM 管理開發環境

the Groovy enVironment Manager

http://gvmtool.net

# Install GVM
curl -s get.gvmtool.net | bash

# Install Grails
gvm install grails

Getting Started

Create-App

grails create-app realestate

What's Inside

  • grails-app
  • web-app
  • wrapper
  • src
  • test
  • scripts
  • lib
  • application.properties

Run-App

grails run-app

Done

 

Server running. Browse to

http://localhost:8080/realestate

Domain Class

Domain Class = Model = Entity

Define an entity

房屋 {
    縣市;
    鄉鎮區;
    地址;
    坪數;
    售價;
    完成日期;
}

Create-Domain-Class

grails create-domain-class house

What's Inside

Domain Class

grails-app/domain/realestate/House.groovy

Unit Test

test/unit/realestate/HouseSpec.groovy

The House Class

class House {
    String city
    String region
    String address
    float feet
    int price
    Date buildDate
}

Unit Test

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

Test-App

grails test-app

Test Report

Browse to

target/test-reports/html/index.html

Shell

grails shell

Scaffolding

Generate-All

$ grails generate-all realestate.House

That's all?

Run-App

again

$ grails run-app

CRUD

Browse to

http://localhost:8080/realestate/house

What's Inside

  • Controller
  • Controller Test
  • View

URL Mappings

grails-app/conf/UrlMappings.groovy

class UrlMappings {
    static mappings = {
        "/$controller/$action?/$id?(.$format)?"{
            constraints {
                // apply constraints here
            }
        }
    }
}

XML and JSON

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

Spring Security

Spring Security is...

a framework that

focuses on providing both authentication and authorization

to Java applications.

Grails

+

Spring Security

Spring Security Core Plugin

plugins {
    // Spring Security Core Plugin
    compile ":spring-security-core:2.0-SNAPSHOT"
}

s2-quickstart

grails s2-quickstart realestate User Role

Config.groovy

// 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']
]

User and Role

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)

BootStrap

grails-app/conf/BootStrap.groovy

class BootStrap {
    def init = { servletContext ->
        //...
    }
    def destroy = {
    }
}

Fix Bug

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

Spring Security Tags

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

Secured Annotation

for controller or actions

@Secured(["ROLE_ADMIN"])

OAuth

OAuth Plugin

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

s2-init-oauth

grails s2-init-oauth realestate OAuthID

Add relationship to User domain class.

static hasMany = [oAuthIDs: OAuthID]

OAuth Provider: Facebook

grails-app/conf/Config.groovy

// OAuth Facebook
oauth {
    providers {
        facebook {
            key = '363532783795859'
            secret = '----App Secret----'
        }
    }
}

~/.grails/realestate-config.groovy

oauth.providers.facebook.secret = '....'

Server URL

grails.serverURL = "http://dev.teamcollab.org:8080/realestate"

Facebook Connect

grails-app/views/index.gsp

<oauth:connect provider="facebook">Sign-in with Facebook</oauth:connect>

Layout

Twitter Bootstrap

plugins {
    // Twitter Bootstrap
    runtime ":twitter-bootstrap:3.2.0.2"
}

Assets Pipeline

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

Require Bootstrap

grails-app/assets/javascripts/application.js

//= require bootstrap

grails-app/assets/stylesheets/application.css

/*
*= require bootstrap
*/

SiteMesh

Apply a "layout" for views.

<html>
<head>
    <meta name="layout" content="main" />
    <title>Page Title</title>

SiteMesh Layout

<!DOCTYPE html>
<html>
<head>
    <title><g:layoutTitle default="Untitled" /></title>
    <g:layoutHead/>
</head>
<body>
    <g:layoutBody/>
</body>
</html>

TagLib

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>

Elasticsearch

Install Plugin

plugins {
    // Elasticsearch
    compile ":elasticsearch:0.0.3.3"
}

Configuration

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

Searchable

class House {
    static searchable = true

    //...
}

Searching

def search() {
    render House.search(params.q) as JSON
}

/house/search?q=KEYWORDS

Web Test

Geb

Selenium WebDriver

+

Spock

Install Plugin

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

Functional Test

grails test-app functional:spock

Deployment

War Package

grails war

Deploy to AWS

Elastic Beanstalk

The End