grails - beginners workshop - imada.sdu.dkjamik/gr8conf/grails - beginners workshop.pdf · grails...

Post on 13-May-2018

228 Views

Category:

Documents

3 Downloads

Preview:

Click to see full reader

TRANSCRIPT

GRAILS - BEGINNERS WORKSHOPJacob Aae Mikkelsen

AGENDAInstalling Grails

Grails Intro

Application Scenario

Creating the application

Exercises

JACOB AAE MIKKELSENSenior Engineer at Lego

Microservice based architechture on JVM

Previously 4 years at Gennemtænkt IT

Consultant on Groovy and Grails

External Associate Professor - University of SouthernDenmark

@JacobAae

Blogs The Grails Diary

INSTALLING GRAILS

USB WITH MATERIALOn the USB Sick passed around, the following materials are

available

These slides

Git repository with Grails application

Grails

IntelliJ editor

Bootstrap files

MAC & LINUXUse Gvm tool

curl ­s get.gvmtool.net | bash

Restart terminal

gvm install grails 3.0.1

WINDOWSOption 1: Use GVM Tool in Cygwin

Option 2: Use Posh-GVM in Powershell( )https://github.com/flofreud/posh-gvm

Option 3 Download Grails, and setup GRAILS_HOME(

)http://grails.asia/grails-tutorial-for-beginners-setup-your-

windows-development-environment/

GRAILS INTROGrails is a full stack framework

-

Embrace the Don’t Repeat Yourself (DRY) principle

-

Encourages proper testing

-

Use the Groovy language and extensive use of DomainSpecific Languages (DSLs)

INCLUDED WITH GRAILSObject Relational Mapping (ORM) layer → Hibernate

Expressive view technology → Groovy Server Pages (GSP)

Controller layer → Spring MVC

Interactive command line env and build system → Gradle

Embedded Tomcat container with on the fly reloading

Dependency injection → Spring container

i18n → Spring’s core MessageSource concept

Transactional service layer → Spring’s transactionabstraction

LATEST VERSION: 3.0.1Groovy 2.4

Spring 4.1 and Spring Boot 1.2

Gradle Build System

Application Profiles

Redesigned API based on Traits

Filters → Interceptors

Main class → Easy to run from any IDE

APPLICATION SCENARIOLets make a small conference application that can

keep track at what talks and workshops you haveattended

let attendees rate and supply feedback

MODELWe need 3 domain classes in this small application

Attendee

Talk

Rating

ATTENDEEName

Email

Which talks attended

TALKSpeaker name

Starting time

List of ratings and comments

RATINGAttendee who supplied the rating

The talk it rates

The rating (1-5)

Comment

FUNCTIONALITYAttendees must be able to register which talks heattended

An attendee must be able to rate a talk from 1-5 andsubmit a comment too

A list of comments and an average grade must beavailable when vieving a talk

STEP 1: CREATING THE APPLICATIONTo create a new Grails Application, run the grails

create-app command in a terminal

grails create­app eu­gr8conf­grailsdemo

Lets go through the tree view of whats generated

You don’t have to do the above step, if you clone thisrepo - then it is all done :)

git clone https://github.com/JacobAae/eu­gr8conf­grailsdemo.git

WHATS GENERATED (1). build.gradle gradle wrapper gradle­wrapper.jar gradle­wrapper.properties gradle.properties gradlew gradlew.bat

WHATS GENERATED (2) grails­app controllers UrlMappings.groovy domain services taglib utils views error.gsp index.gsp layouts main.gsp notFound.gsp

WHATS GENERATED (3) grails­app assets images javascripts stylesheets conf application.yml logback.groovy spring resources.groovy i18n

WHATS GENERATED (4) grails­app init BootStrap.groovy eu gr8conf grailsdemo Application.groovy

WHATS GENERATED (5) src integration­test groovy main groovy webapp test groovy

RUNNING OUR APPTo get to the interactive console:

grails

In interactive mode

run­app

RESULTGo to: http://localhost:8080/

STEP 2: FIRST DOMAIN CLASSLets create the first domin class with controller and views:

Attendee

CREATING THE DOMAIN CLASSIn interactive mode

create­domain­class eu.gr8conf.grailsdemo.Attendee

Resulting in| Created grails-app/domain/eu/gr8conf/grailsdemo/Attendee.groovy| Created src/test/groovy/eu/gr8conf/grailsdemo/AttendeeSpec.groovy

RUNNING TESTSIn interactive mode

test­app

Which fails since we have not yet implemented any test

PROPERTIES AND CONSTRAINTSEdit the Attendee class to contain

String nameString emailString nationality

Date dateCreatedDate lastUpdated

static constraints = name blank: false email blank: false, unique: true, email: true nationality nullable: true

GORMAdds lots of convenience

validate()

save()

get(id)

delete()

list()

Dynamic finders: Attendee.findByName('Jacob')

TESTINGHere is an example of a test for constraints that are violated

@Unrollvoid "Test invalid properties for attendee: #comment"() when: Attendee attendee = new Attendee(name: name, email: email, nationality: nationality)

then: !attendee.validate()

where: name | email | nationality | comment '' | 'jacob@gr8conf.org' | 'Danish' | 'Blank name'

INFO: Lets add positive and more negative tests for theconstraints.

CONTROLLER AND VIEWSIn interactive mode

generate­all eu.gr8conf.grailsdemo.Attendee

And run the app again - and click the controller

EXERCISEOpen AttendeeControllerSpec.groovy and update

it, so the tests passes

STEP 3: MORE DOMAIN CLASSESLets make the other two domain classes, and see how they

can interact

HINTS create­domain­class eu.gr8conf.grailsdemo.Talk create­domain­class eu.gr8conf.grailsdemo.Rating generate­all eu.gr8conf.grailsdemo.Talk generate­all eu.gr8conf.grailsdemo.Rating

RELATIONSAttendee.groovy

static hasMany = [talks: Talk]

Talk.groovy

static belongsTo = Attendee // Where the addTo method will work from i.e. cascading savestatic hasMany = [attendees: Attendee,ratings: Rating]

Rating.groovy

static belongsTo = [talk:Talk]

CONTROLLERSA controller handles requests and creates or prepares theresponse

Business logic placed elsewhere (services)

Placed in controller folder and ends in Controller

Methods → actions

Databinding

VIEWS AND TEMPLATESMade with Groovy Server Pages (GSP)

Extensive suite of tags

formatting

looping

input fields

CONTROLLERS AND VIEWSProblem: Lets make a new Display Attendee page, where we

can see talks attended better.

What to do:

Make a display action in the AttendeeController

Add a display.gsp view

add link to the new display or replace the show action

We could also rewrite the show action :)

THE ACTIONdef display(Long id) Attendee attendee = Attendee.get(id) respond attendee

THE VIEWCopy and paste the show.gsp view, and replace

<f:display bean="attendee" />

with

THE VIEW (2)<ol class="property­list attendee"> <li class="fieldcontain"> <span id="name­label" class="property­label">Name</span> <span class="property­value" aria­labelledby="name­label"> $attendee.name</span></li> <!­­ email and nationalityleft out ­­> <li class="fieldcontain"> <span id="talks­label" class="property­label">Talks</span> <span class="property­value" aria­labelledby="talks­label"> <ul> <g:each in="$attendee.talks" var="talk"> <li>$talk.title</li> </g:each> </ul> </span> </li></ol>

EXERCISESMake a link for each talk to go to an add rating page

Implement the add rating functionality

STEP 4: SERVICESThe default code leaves too much logic in the controller, thisshould be in a service, that also should handle transactions

etc.

Lets try to cleanup the attendee controller and place someof the logic in a service

SERVICESAre transactional by default

Easy to autoinject

Great place for logic and functionality

HINTScreate­service eu.gr8conf.grailsdemo.AttendeeService

USING SERVICES - AUTOINJECTIONIn the AttendeeController, where we will use the service, all

we need to do, is add this line

AttendeeService attendeeService

Then it will be autoinjected because Grails recognices thename

CLEANING UP CONTROLLERSThe default generated controllers does everything

Lets cleanup the save method.

HELPER CLASSESIn src/main/groovy you can place helper classes, or

non-Grails-artefacts

class Result def item Status status

enum Status OK, NOT_FOUND, HAS_ERRORS

SERVICE METHODThe service method could look like

Result saveAttendee(Attendee attendee) if (attendee == null) transactionStatus.setRollbackOnly() return new Result(status: Status.NOT_FOUND) if (attendee.hasErrors()) transactionStatus.setRollbackOnly() return new Result(status: Status.HAS_ERRORS, item: attendee) attendee.save flush:true return new Result(status: Status.OK, item: attendee)

CONTROLLERdef save(Attendee attendee) Result result = attendeeService.saveAttendee(attendee) switch( result.status) case Status.NOT_FOUND: notFound() break case Status.HAS_ERRORS: respond result.item.errors, view:'create' break case Status.OK: request.withFormat form multipartForm flash.message = message(code: 'default.created.message println attendee.dump() redirect attendee '*' respond attendee, [status: CREATED]

TASKClean up the rest of the controller, removig everything that

needs the @Transactional annotation

STEP 5: TAGLIBSTaglibs can help make the views more clean and DRY

TAGLIBPlaced in grails-app/taglib

must end with TagLib.groovy

Closure taking parameters attrs and body

create­taglib eu.gr8conf.grailsdemo.RatingTagLib

SERVICE TO CALCULATE AVERAGERATING

class TalkService

BigDecimal calculateAverageRating(Talk talk) if( !talk || !talk.ratings ) return null talk.ratings*.value.sum() / talk.ratings.size()

THE TAGLIBTalkService talkService

def showRating = attrs ­> Talk talk = attrs.talk BigDecimal avg = talkService.calculateAverageRating(talk)

if( avg ) out << """<span class='average­rating'> $roundAverage(avg)</span>""" else out << "N/A" private roundAverage(BigDecimal average) "$Math.round(1.0 * average * 100) / 100 "

TESTING TAGLIBSvoid "test output from showRating"() when: String output = tagLib.showRating(talk: new Talk(ratings: ratings), a ­> a )

then: output == expected

where: ratings | expected null | 'N/A' [new Rating(value: 5)] | "<span class='average­rating'>5</span>

STEP 6: ASSETS, STYLING, PLUGINSAND LAYOUTS

ASSETSJavascript, images and css files are handled by asset-

pipeline plugins

build.gradle

runtime "org.grails.plugins:asset­pipeline"

Compiles Less

Minifies JS

Combines files

BOOTSTRAPLets include the (Twitter) Bootstrap project in our

application.

BOOTSTRAPIn the grails-app/assets folder, place the Bootstrap

files

. fonts glyphicons­halflings­regular.eot ... + rest of fonts files images ... javascripts bootstrap.css.map bootstrap.js stylesheets bootstrap.css bootstrap­theme.css combined­bootstrap.css ...

ASSET FILELets make a Bootstrap css asset package

combined-bootstrap.css

/**= require bootstrap*= require bootstrap­theme*= require_self*/

LAYOUTSIn grails-app/views/layouts Layonts are placed

that can be reused across pages.

LAYOUTSviews/layouts/bootstrap.gsp

<!doctype html><html lang="en" class="no­js"> <head> <meta http­equiv="Content­Type" content="text/html; charset=UTF­8 <meta http­equiv="X­UA­Compatible" content="IE=edge"> <title><g:layoutTitle default="Grails"/></title> <meta name="viewport" content="width=device­width, initial­scale=1 <asset:stylesheet src="combined­bootstrap.css"/> <asset:javascript src="application.js"/> <g:layoutHead/> </head> <body class="container"> <g:layoutBody/> <div class="footer" role="contentinfo"></div> <div id="spinner" class="spinner" style="display:none;"><g:message </body></html>

USING LAYOUTSviews/attendee/display.gsp

<head> <meta name="layout" content="bootstrap">...

views/attendee/display.gsp

<g:link class="btn btn­primary" action="edit" resource="$attendee" <span class="glyphicon glyphicon­edit" aria­hidden="true"></span> <g:message code="default.button.edit.label" default="Edit" /></g:link>

FIXING ICONSIn bootstrap.css, update the path to Glyphicons font

(line 267++)

views/attendee/display.gsp

@font­face font­family: 'Glyphicons Halflings'; src: url('../assets/fonts/glyphicons­halflings­regular.eot'); src: url('../assets/fonts/glyphicons­halflings­regular.eot?#iefix') format('embedded­opentype'), url('../fonts/glyphicons­halflings­regular.woff2') format('woff2'), url('../fonts/glyphicons­halflings­regular.woff') format('woff'), url('../fonts/glyphicons­halflings­regular.ttf') format('truetype'), url('../fonts/glyphicons­halflings­regular.svg#glyphicons_halflingsregular') format('svg');

TASKSInclude Bootstrap

Restyle a page using a bootstrap layout

EXERCISESShow the distribution of grades in the talk page (2*3, 2*4,4*5)

LITERATUREhttp://gvmtool.net/

https://github.com/flofreud/posh-gvm

https://grails.org/single-page-documentation.html

http://getbootstrap.com/

top related