grails beginners workshop
Post on 10-Aug-2015
40 Views
Preview:
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
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 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
WHATS GENERATED (1). build.gradle gradle wrapper gradlewrapper.jar gradlewrapper.properties gradle.properties gradlew gradlew.bat
WHATS GENERATED (2) grailsapp controllers UrlMappings.groovy domain services taglib utils views error.gsp index.gsp layouts main.gsp notFound.gsp
WHATS GENERATED (3) grailsapp assets images javascripts stylesheets conf application.yml logback.groovy spring resources.groovy i18n
WHATS GENERATED (4) grailsapp init BootStrap.groovy eu gr8conf grailsdemo Application.groovy
WHATS GENERATED (5) src integrationtest groovy main groovy webapp test groovy
RUNNING OUR APPTo get to the interactive console:
grails
In interactive mode
runapp
STEP 2: FIRST DOMAIN CLASSLets create the first domin class with controller and views:
Attendee
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
RUNNING TESTSIn interactive mode
testapp
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
generateall 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 createdomainclass eu.gr8conf.grailsdemo.Talk createdomainclass eu.gr8conf.grailsdemo.Rating generateall eu.gr8conf.grailsdemo.Talk generateall 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="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>
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
HINTScreateservice 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
createtaglib 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='averagerating'> $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='averagerating'>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:assetpipeline"
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 glyphiconshalflingsregular.eot ... + rest of fonts files images ... javascripts bootstrap.css.map bootstrap.js stylesheets bootstrap.css bootstraptheme.css combinedbootstrap.css ...
ASSET FILELets make a Bootstrap css asset package
combined-bootstrap.css
/**= require bootstrap*= require bootstraptheme*= 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="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>
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>
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');
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