grails beginners workshop
TRANSCRIPT
![Page 1: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/1.jpg)
GRAILS - BEGINNERS WORKSHOPJacob Aae Mikkelsen
![Page 2: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/2.jpg)
AGENDAInstalling Grails
Grails Intro
Application Scenario
Creating the application
Exercises
![Page 3: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/3.jpg)
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
![Page 4: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/4.jpg)
INSTALLING GRAILS
![Page 5: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/5.jpg)
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
![Page 6: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/6.jpg)
MAC & LINUXUse Gvm tool
curl s get.gvmtool.net | bash
Restart terminal
gvm install grails 3.0.1
![Page 7: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/7.jpg)
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/
![Page 8: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/8.jpg)
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)
![Page 9: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/9.jpg)
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
![Page 10: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/10.jpg)
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
![Page 11: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/11.jpg)
APPLICATION SCENARIOLets make a small conference application that can
keep track at what talks and workshops you haveattended
let attendees rate and supply feedback
![Page 12: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/12.jpg)
MODELWe need 3 domain classes in this small application
Attendee
Talk
Rating
![Page 13: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/13.jpg)
ATTENDEEName
Which talks attended
![Page 14: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/14.jpg)
TALKSpeaker name
Starting time
List of ratings and comments
![Page 15: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/15.jpg)
RATINGAttendee who supplied the rating
The talk it rates
The rating (1-5)
Comment
![Page 16: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/16.jpg)
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
![Page 17: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/17.jpg)
STEP 1: CREATING THE APPLICATIONTo create a new Grails Application, run the grails
create-app command in a terminal
grails createapp eugr8confgrailsdemo
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/eugr8confgrailsdemo.git
![Page 18: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/18.jpg)
WHATS GENERATED (1). build.gradle gradle wrapper gradlewrapper.jar gradlewrapper.properties gradle.properties gradlew gradlew.bat
![Page 19: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/19.jpg)
WHATS GENERATED (2) grailsapp controllers UrlMappings.groovy domain services taglib utils views error.gsp index.gsp layouts main.gsp notFound.gsp
![Page 20: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/20.jpg)
WHATS GENERATED (3) grailsapp assets images javascripts stylesheets conf application.yml logback.groovy spring resources.groovy i18n
![Page 21: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/21.jpg)
WHATS GENERATED (4) grailsapp init BootStrap.groovy eu gr8conf grailsdemo Application.groovy
![Page 22: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/22.jpg)
WHATS GENERATED (5) src integrationtest groovy main groovy webapp test groovy
![Page 23: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/23.jpg)
RUNNING OUR APPTo get to the interactive console:
grails
In interactive mode
runapp
![Page 25: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/25.jpg)
STEP 2: FIRST DOMAIN CLASSLets create the first domin class with controller and views:
Attendee
![Page 26: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/26.jpg)
CREATING THE DOMAIN CLASSIn interactive mode
createdomainclass eu.gr8conf.grailsdemo.Attendee
Resulting in| Created grails-app/domain/eu/gr8conf/grailsdemo/Attendee.groovy| Created src/test/groovy/eu/gr8conf/grailsdemo/AttendeeSpec.groovy
![Page 27: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/27.jpg)
RUNNING TESTSIn interactive mode
testapp
Which fails since we have not yet implemented any test
![Page 28: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/28.jpg)
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
![Page 29: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/29.jpg)
GORMAdds lots of convenience
validate()
save()
get(id)
delete()
list()
Dynamic finders: Attendee.findByName('Jacob')
![Page 30: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/30.jpg)
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 '' | '[email protected]' | 'Danish' | 'Blank name'
INFO: Lets add positive and more negative tests for theconstraints.
![Page 31: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/31.jpg)
CONTROLLER AND VIEWSIn interactive mode
generateall eu.gr8conf.grailsdemo.Attendee
And run the app again - and click the controller
![Page 32: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/32.jpg)
EXERCISEOpen AttendeeControllerSpec.groovy and update
it, so the tests passes
![Page 33: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/33.jpg)
STEP 3: MORE DOMAIN CLASSESLets make the other two domain classes, and see how they
can interact
![Page 34: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/34.jpg)
HINTS createdomainclass eu.gr8conf.grailsdemo.Talk createdomainclass eu.gr8conf.grailsdemo.Rating generateall eu.gr8conf.grailsdemo.Talk generateall eu.gr8conf.grailsdemo.Rating
![Page 35: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/35.jpg)
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]
![Page 36: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/36.jpg)
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
![Page 37: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/37.jpg)
VIEWS AND TEMPLATESMade with Groovy Server Pages (GSP)
Extensive suite of tags
formatting
looping
input fields
![Page 38: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/38.jpg)
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 :)
![Page 39: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/39.jpg)
THE ACTIONdef display(Long id) Attendee attendee = Attendee.get(id) respond attendee
![Page 40: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/40.jpg)
THE VIEWCopy and paste the show.gsp view, and replace
<f:display bean="attendee" />
with
![Page 41: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/41.jpg)
THE VIEW (2)<ol class="propertylist attendee"> <li class="fieldcontain"> <span id="namelabel" class="propertylabel">Name</span> <span class="propertyvalue" arialabelledby="namelabel"> $attendee.name</span></li> <! email and nationalityleft out > <li class="fieldcontain"> <span id="talkslabel" class="propertylabel">Talks</span> <span class="propertyvalue" arialabelledby="talkslabel"> <ul> <g:each in="$attendee.talks" var="talk"> <li>$talk.title</li> </g:each> </ul> </span> </li></ol>
![Page 42: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/42.jpg)
EXERCISESMake a link for each talk to go to an add rating page
Implement the add rating functionality
![Page 43: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/43.jpg)
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
![Page 44: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/44.jpg)
SERVICESAre transactional by default
Easy to autoinject
Great place for logic and functionality
![Page 45: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/45.jpg)
HINTScreateservice eu.gr8conf.grailsdemo.AttendeeService
![Page 46: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/46.jpg)
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
![Page 47: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/47.jpg)
CLEANING UP CONTROLLERSThe default generated controllers does everything
Lets cleanup the save method.
![Page 48: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/48.jpg)
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
![Page 49: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/49.jpg)
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)
![Page 50: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/50.jpg)
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]
![Page 51: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/51.jpg)
TASKClean up the rest of the controller, removig everything that
needs the @Transactional annotation
![Page 52: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/52.jpg)
STEP 5: TAGLIBSTaglibs can help make the views more clean and DRY
![Page 53: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/53.jpg)
TAGLIBPlaced in grails-app/taglib
must end with TagLib.groovy
Closure taking parameters attrs and body
createtaglib eu.gr8conf.grailsdemo.RatingTagLib
![Page 54: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/54.jpg)
SERVICE TO CALCULATE AVERAGERATING
class TalkService
BigDecimal calculateAverageRating(Talk talk) if( !talk || !talk.ratings ) return null talk.ratings*.value.sum() / talk.ratings.size()
![Page 55: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/55.jpg)
THE TAGLIBTalkService talkService
def showRating = attrs > Talk talk = attrs.talk BigDecimal avg = talkService.calculateAverageRating(talk)
if( avg ) out << """<span class='averagerating'> $roundAverage(avg)</span>""" else out << "N/A" private roundAverage(BigDecimal average) "$Math.round(1.0 * average * 100) / 100 "
![Page 56: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/56.jpg)
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='averagerating'>5</span>
![Page 57: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/57.jpg)
STEP 6: ASSETS, STYLING, PLUGINSAND LAYOUTS
![Page 58: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/58.jpg)
ASSETSJavascript, images and css files are handled by asset-
pipeline plugins
build.gradle
runtime "org.grails.plugins:assetpipeline"
Compiles Less
Minifies JS
Combines files
![Page 59: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/59.jpg)
BOOTSTRAPLets include the (Twitter) Bootstrap project in our
application.
![Page 60: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/60.jpg)
BOOTSTRAPIn the grails-app/assets folder, place the Bootstrap
files
. fonts glyphiconshalflingsregular.eot ... + rest of fonts files images ... javascripts bootstrap.css.map bootstrap.js stylesheets bootstrap.css bootstraptheme.css combinedbootstrap.css ...
![Page 61: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/61.jpg)
ASSET FILELets make a Bootstrap css asset package
combined-bootstrap.css
/**= require bootstrap*= require bootstraptheme*= require_self*/
![Page 62: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/62.jpg)
LAYOUTSIn grails-app/views/layouts Layonts are placed
that can be reused across pages.
![Page 63: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/63.jpg)
LAYOUTSviews/layouts/bootstrap.gsp
<!doctype html><html lang="en" class="nojs"> <head> <meta httpequiv="ContentType" content="text/html; charset=UTF8 <meta httpequiv="XUACompatible" content="IE=edge"> <title><g:layoutTitle default="Grails"/></title> <meta name="viewport" content="width=devicewidth, initialscale=1 <asset:stylesheet src="combinedbootstrap.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>
![Page 64: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/64.jpg)
USING LAYOUTSviews/attendee/display.gsp
<head> <meta name="layout" content="bootstrap">...
views/attendee/display.gsp
<g:link class="btn btnprimary" action="edit" resource="$attendee" <span class="glyphicon glyphiconedit" ariahidden="true"></span> <g:message code="default.button.edit.label" default="Edit" /></g:link>
![Page 65: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/65.jpg)
FIXING ICONSIn bootstrap.css, update the path to Glyphicons font
(line 267++)
views/attendee/display.gsp
@fontface fontfamily: 'Glyphicons Halflings'; src: url('../assets/fonts/glyphiconshalflingsregular.eot'); src: url('../assets/fonts/glyphiconshalflingsregular.eot?#iefix') format('embeddedopentype'), url('../fonts/glyphiconshalflingsregular.woff2') format('woff2'), url('../fonts/glyphiconshalflingsregular.woff') format('woff'), url('../fonts/glyphiconshalflingsregular.ttf') format('truetype'), url('../fonts/glyphiconshalflingsregular.svg#glyphicons_halflingsregular') format('svg');
![Page 66: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/66.jpg)
TASKSInclude Bootstrap
Restyle a page using a bootstrap layout
![Page 67: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/67.jpg)
EXERCISESShow the distribution of grades in the talk page (2*3, 2*4,4*5)
![Page 68: Grails beginners workshop](https://reader033.vdocument.in/reader033/viewer/2022042602/55c7dd31bb61eb65728b4665/html5/thumbnails/68.jpg)
LITERATUREhttp://gvmtool.net/
https://github.com/flofreud/posh-gvm
https://grails.org/single-page-documentation.html
http://getbootstrap.com/