Stacktrace

Grails är ett webbramverk som är designat för hög produktivitet. Det gör precis som Ruby On Rails och satsar på konventioner framför konfiguration. Detta förenklar och snabbar upp utveckling betydligt. Under motorhuven så använder sig Grails av bland annat Spring, Hibernate, SiteMesh, Ant, Log4j. Grails är baserat på Groovy, som är ett objektorienterat dynamiskt skriptspråk som är baserat på Java platformen.

Vi börjar med att installera Grails

  1. Ladda ner Grails (jag har använt Grails 1.0 RC1)
  2. Packa upp zipfilen på valfritt ställe
  3. Sätt miljövariablen GRAILS_HOME till där du valde att packa upp zippfilen
  4. Lägg till Grails bin-katalog till PATH miljövariabeln
  5. Verifiera att allt är uppsatt korrekt genom att öppna en prompt och köra följande kommando: grails version

Jag tycker att det enklaste sättet att lära sig något nytt är att köra lite ”hands-on”. Så vi börjar med att direkt skapa en ny applikation, kör följande kommando:

grails create-app myproj

create-app kommandot används för att skapa en ny applikation. Kommandot skapar en katalog med samma namn som din applikation. Eftersom Grails använder sig utav konvention framför konfiguration så är det en god idé att bekanta sig lite med katalogstrukturen:

  • myproj
    • grails-app
      • conf – konfigurationsfiler, bl a datakällor
        • hibernate – extra hibernate konfigurationsfiler
        • spring – extra spring konfigurationsfiler
      • controllers – kontroller klasserna skapas här
      • domain – domän klasserna skapas här
      • i18n – i18n resurser
      • services – service klasserna skapas här
      • taglib – taglib klasser skapas här
      • utils – codec klasser skapas här
      • views – vy-mallar. En underkatalog med mallar för varje kontroller skapas.
        • layouts – Sitemash layout mallar
    • lib – tredjeparts libbar som applikationen behöver
    • scripts – Gant scripts
    • src
      • groovy – övrig Groovy kod, annat än kontroller-, domän- eller serviceklasser
      • java – Java källkod
    • test – test klasser
    • web-app
      • css – css-filer
      • images – bilder
      • js – javascript filer, tredjeparts javascriptbibliotek (tex Prototype)
      • WEB-INF - webbapplikaitonens WEB-INF katalog

Vi börjar med att försöka få lite känsla för hur Grails fungerar. Grails är ett webbramverk, och som de flesta andra webbramverk så används MVC-mönstret. Detta betyder att alla anrop från klienten hanteras av en kontroller (*Controller.groovy) som i sin tur uppdaterar modellen (domänklasser) och sedan skickar anropet vidare till en vy som presenterar modellen (*.gsp). Vi börjar med att skapa en Controller. I mappen myproj\grails-app\controllers skapar vi en fil som heter TestController.groovy. Öppna filen med valfri editor och skriv följande kod:

class TestController {
def helloWorld = { render ”Hello Groovy Grails World!” }
}

Kör följande kommando i prompten (tänk på att du skall stå i rooten i ditt projekt):

grails run-app

Det kommandot startar din applikation i Jetty som kommer med Grails. När du ser följande i prompten: Server running. Browse to http://localhost:8080/myproj så öppnar du din webbläsare och skriver in följande url:

http://localhost:8080/myproj/test/helloWorld

Förhoppningsvis så såg du nu i din browser följande text ”Hello Groovy Grails World!”. Hur hänger det här ihop då? Jo, Grails mappar URLen på följande sätt, myproj = applikationskontextet, test = vilken kontroller som skall anropas (TestController.groovy), helloWorld = vilken händelse i kontrollern som skall anropas. Anropar men en kontroller utan en händelse så anropas den defaulta händelsen index. Anropar men en kontroller med en händelse som inte finns så returnerar Grails HTTP error 404. Eftersom Grails använder den här konventionen så behöver vi inte mappa någonting i någon xml-fil för att få det här att fungera. För att testa detta, lägg till följande kod i TestController.groovy

def index = { redirect(action: ‘helloWorld’) }

Anropa följande url:

http://localhost:8080/myproj/test/

Du skall nu ha fått samma resultat som när vi anropade med den tidigare url:en. Alla kontrollermotoder slutar på ett utav tre sätt, antingen med ett return, redirect eller render. Avslutas metoden med ett return, så renderas en GSP (GrailsServerPage) med samma namn som metoden. Redirect och render är båda dynamiska metoder som är tillgängliga från alla kontrollerklasser. Render metoden som vi använder i vår helloWorld metod kan anropas på flera olika sätt tex:

render ”text text text” – texten ”text text text” renderas till webbläsaren
render (view: ‘somePage’) – somePage.gsp renderas

Samma gäller redirect som vi använder i vår index metod:

redirect (action:’helloWorld’) – anropet skickas vidare till händelsen helloWorld i samma kontroller
redirect (controller: ‘another’: action: ‘someAction’) – händelsen someAction i kontrollern another anropas
redirect (url: ‘http://stacktrace.se’) – http://stacktrace.se anropas

Så där ja, nu har vi fått en liten hum om hur kontrollerna fungerar. Dags för att ta en titt på den mest centrala delen i Grails, domänklasserna. I mappen myproj\grail-app\domain skapa en fil som heter Test.groovy som du öppnar med din editor och skriv följande kod:

class Test {
String test1
String test2
}

Nu har vi skapat en domänklass Test som har som har två properties, test1 och test2. Dessutom så kommer Grails med automatik fylla på med två stycken andra properties, id och version. För att testa vår domänklass lägg till följande händelser i TestController.groovy

def create = {
new Test(test1: params.test1, test2: params.test2).save()
render ”created successfully!”
}
def list = {
String str = ””
Test.list().each(){
str += ”Test id[${it.id}] version[${it.version}] test1[${it.test1}] test2[${it.test2}]\n”
}
render str
}

I din browser, anropa följande url(några gånger om du så vill) :

http://localhost:8080/myproj/test/create?test1=test&test2=testtest

Nu går du till följande url med din webbläsare:

http://localhost:8080/myproj/test/list

Nu skall du se en lista med alla Test objekt som du har skapat när du anropade create urlen. Det är list händelsen i TestController.groovy som vi skapade tidigare som loopar igenom alla våra Test objekt och skapar en sträng som den returnerar tillbaka till webbläsaren.

Nu skall vi titta på en annan funktion som finns för att snabbt kunna skapa en CRUD applikation. Stäng ner servern med Ctrl+c och i vår kontrollerklass TestController.groovy, ta bort all kod och ersätt med följande:

class TestController {
def scaffold = true
}

Starta servern på nytt med kommandot:

grails run-app

Gå nu till url:en:

http://localhost:8080/myproj/test

så ser du att en komplett CRUD applikation har skapats. Du kan nu lista, skapa, editera och radera Test objekt. Scaffold tillåter dej alltså att skapa en komplett CRUD applikation för en domän klass. Alla kontrollerhändelser och vyer (gsp:er) genereras dynamiskt åt dej i runtime. Men var har alla våra Test objekt tagit vägen som vi skapade tidigare? Default så är Grails konfigurerat med att använda HSQLDB som databas och hantera all persistens i minnet. Dessutom så skapas alla tabeller om vid uppstart. Denna inställning kan man enkelt ändra i filen myproj\grails-app\conf\DataSource.groovy. Om vi vill att datat skall sparas mellan omstarter gör följande fetstilta ändring:


development {
dataSource {
dbCreate = ”update” // one of ‘create’, ‘create-drop’,'update’
url = ”jdbc:hsqldb:file:devDB”
}
}

Nu är det dags att bygga en riktig liten applikation. Som exempel tänkte jag skapa en godisaffär. Till denna applikation kommer vi behöva 3 stycken domän klasser, Candy, CandyBag och CandyBagItem. Vi börjar med att skapa en ny Grails applikation, i prompten kör följande kommando (verifiera att du inte står kvar i rooten på myproj applikationen)

grails create-app candystore

Flytta dej ner till rooten på vår ny applikation

cd candystore

skapa våra domän klasser med följande kommandon

grails create-domain-class candy
grails create-domain-class candyBag
grails create-domain-class candyBagItem

Öppna candystore\grails-app\domain\Candy.groovy och lägg till följande properties

class Candy {
String name
double price
}

Öpnna candystore\grails-app\domain\CandyBag.groovy och lägg till följande properties

class CandyBag {
List items = []
}

Öpnna candystore\grails-app\domain\CandyBagItem.groovy och lägg till följande properties

class CandyBagItem {
Candy candy
int quantity
}

Nu när vi har skapat våran modell, så måste vi bestämma hur/vad vår applikation skall göra. Den skall bestå av en index sida som är en välkommstsida. Från den sidan så skall man kunna gå till ”affären” där man kan fylla på sin godispåse. Där skall man kunna välja att avbryta eller att köpa. Väljer man att avbryta så kommer man tillbaka till indexsidan. Väljer man att köpa så kommer man till en checkout sida där man får bekräfta eller avbryta. Väljer man där att avbryta kommer man tillbaka till affären, väljer man att bekräfta så kommer man till indexsidan. Vi skall även ha en admin sida där man skall kunna skapa och editera godiset som finns i affären och så skall vi kunna administrera alla beställningar. Vi börjar med att implementera vår applikation. Modifiera konfigurationen av datakällan för utvecklingsmiljön, så att datan vi skapar inte försvinner mellan omstarter. Om du inte kommer ihåg hur, så kan du kika lite högre upp i artikeln när vi gjorde det tidigare. En annan vanlig grej vid utveckling är att man vill ladda applikationen med lite testdata. För att göra det använder vi en mekanism som Grails har, BootStrap klasser. Dessa klasser körs vid varje uppstart. Öppna candystore\grails-app\conf\BootStrap.groovy och lägg till följande kod:

class BootStrap {

def init = { servletContext ->
if(Candy.list().size() == 0){
new Candy(name: ‘Kexchoklad’, price: 15).save()
new Candy(name: ‘Japp’, price: 11).save()
new Candy(name: ‘Daim’, price: 10).save()
new Candy(name: ‘Vaniljfudge’, price: 1.5).save()
}
}
def destroy = {
}
}

Denna kod skapar lite basgodis till vår godisaffär. Init metoden i *BootStrap-klasser anropas varje gång när applikationen initieras. If-satsen har vi där eftersom vi inte vill att datan skall skapas varje gång, utan endast när det inte finns något godis i databasen.
Så där, nu har vi lite basdata att jobba med, så nu tar vi och fixar till en välkommstsida (index-sidan). Skapa följande fil candystore\grails-app\views\layouts\candystore.gsp och skriv följande kod:

<html>
<head>
<title><g:layoutTitle default=”CandyStore” /></title>
<g:layoutHead />
</head>
<body>
<div class=”logo”>[Godisaffären]</div>
<g:layoutBody />
</body>
</html>

Grails använder Sitemesh för hantera layouter, candystore.gsp är basmallen som alla andra sidor kommer att använda sig utav. Som ni ser så är inte detta någon artikel som lägger någon krut på att göra snygga sidor…
Öppna filen candystore\web-app\index.gsp och gör följande ändringar:

<html>
<head>
<title>Välkommen till [Godisaffären]</title>
<meta name=”layout” content=”candystore” />
</head>
<body>
<h1 style=”margin-left:20px;”>Välkommen till Godisaffären</h1>
<p style=”margin-left:20px;width:80%”>Välkommen in och <g:link controller=”candyStore” action=”shop”>shoppa lite snarre!!</g:link></p>
</body>
</html>

Välkommstsidan är väldigt enkel, lite text och en länk till shopen. <meta name=”layout” content=”candystore” /> pekar ut att vi använder candystore.gsp som basmall. <g:link> är en gsp-tag som renderar en html-länk, med attributet controller så mappar vi länken mot CandyStoreController och attributet action mappar länken mot händelsen shop i CandyStoreController. Denna kontroller måste vi nu skapa. Kör följande kommando:

grails create-controller candyStore

Öppna candystore\grails-app\controllers\CandyStoreController.groovy och skapa shop händelsen

class CandyStoreController {

def defaultAction = ”shop”

def shop = {
def candyBag = session.candyBag
if(!candyBag){
candyBag = new CandyBag()
session["candyBag"] = candyBag
}

if(!params.max) params.max = 10
[candyList: Candy.list(params), candyBag: candyBag]
}
}

Alla kontroller klasser har bland annat access till följande variabler, servletContext, session, request, params och flash. ServletContext är en instans av javax.servlet.ServletContext och tillåter dej att dela tillstånd över hela webbapplikationen. Session är en instans av javax.servlet.HttpSession. Request är en instans av javax.servlet.HttpServletRequest. Params är en Map med alla inkommande parametrar. Flash är ett special ”scope” som Grails har. Värden som lagras i flash-scopet lever i pågående request och den direkt efterkommande. Detta scope kan vara bra att använda för att sätta meddelanden som skall visas i vyn innan man gör en redirect.
Vår händelsen shop kollar först om vi har någon candyBag i vår session, om inte så skapas den. Sen så söker vi fram alla godissorter och stoppar dem i varibeln candyList, vi sätter även att vi max skall visa 10 rader. Den statiska metoden list() som vi anropar på vår Candy klass är en utav många dynamiska metoder som finns tillgängliga i Grails för domänklasser, vi behöver alltså inte skapa och implementera dessa metoder. Till sist så stoppar vi vår candyBag i variabeln candyBag. Dessa varibler används senare i gsp-sidan för att accessa datat. Dessutom så deklarerar vi vår händelse shop som den defaulta händelsen. Grails konvention är sådan, att om ingen speciell vy är specificerad så används en med samma namn som händelsen. Så i det här fallet så kommer shop.gsp att renderas. Det betyder att vi måste skapa filen candystore\grails-app\views\candyStore\shop.gsp

<html>
<head>
<meta http-equiv=”Content-Type” content=”text/html; charset=UTF-8″/>
<meta name=”layout” content=”candystore” />
<title>Shoppen</title>
</head>
<body>
<div class=”nav”>
<a href=”${createLinkTo(dir:”)}”>[Hem]</a>
</div>

<g:if test=”${candyBag?.items.size() > 0}”>
<div class=”candyBag” style=”float: right; width: 250px; top: 20px; border: thin solid black; padding: 5px;”>
Min godispåse
<table>
<tbody>
<g:each in=”${candyBag?.items}” var=”item” status=”idx”>
<tr>
<td>${item.quantity} st</td>
<td>${item.candy.name} á ${item.candy.price}</td>
<td style=”text-align: right”><g:link action=”dropCandy” style=”font: normal small-caps bold 10px arial;” id=”${idx}”>Ta bort</g:link></td>
</tr>
</g:each>
</tbody>
<tfoot>
<tr>
<td colspan=”2″ style=”font: normal bold 12px arial; text-align: left”>S:a: ${candyBag.total()}</td>
<td style=”text-align: right”>
<g:link action=”buy” style=”font: normal small-caps bold 10px arial”>
Köp
</g:link> | <g:link action=”cancel” style=”font: normal small-caps bold 10px arial”>Avbryt</g:link>
</td>
</tr>
</tfoot>
</table>
</div>
</g:if>

<div class=”body”>
<h1>Vårat godissortiment</h1>
<div class=”list”>
<table>
<thead>
<tr>
<g:sortableColumn property=”name” title=”Namn” />
<g:sortableColumn property=”price” title=”Pris” />
<th>Antal</th>
<th> </th>
</tr>
</thead>
<tbody>
<g:each in=”${candyList}” var=”candy”>
<g:form action=”add”>
<g:hiddenField name=”id” value=”${candy.id}” />
<tr>
<td>${candy.name?.encodeAsHTML()}</td>
<td>${candy.price?.encodeAsHTML()}</td>
<td><g:select name=”quantity” from=”${1..20}” /></td>
<td><g:submitButton name=”add” value=”Stoppa i påse” /></td>
</tr>
</g:form>
</g:each>
</tbody>
</table>
</div>
<div class=”paginateButtons”>
<g:paginate total=”${Candy.count()}” />
</div>
</div>
</body>
</html>

Inte mycket att säga om den här sidan. Som ni ser är gsp väldigt likt jsp, fast man kan använda Groovy istället för Java och gsp-taggar. Det man kan observera i den här sidan är att vi anropar en ny metod på klassen CandyBag:

..
<td colspan=”2″ style=”font: normal bold 12px arial; text-align: left”>S:a: ${candyBag.total()}</td>
..

och ett gäng nya händelser i CandyStoreController:


<g:link action=”dropCandy” style=”font: normal small-caps bold 10px arial;” id=”${idx}”>Ta bort</g:link>

<g:link action=”buy” style=”font: normal small-caps bold 10px arial”>Köp</g:link>

<g:link action=”cancel” style=”font: normal small-caps bold 10px arial”>Avbryt</g:link>

<g:submitButton name=”add” value=”Stoppa i påse” />

Dessa måste vi nu skapa, vi börjar med att lägga till den nya metoden total i CandyBag.groovy


double total(){
double sa = 0
items.each(){
sa += it.price()
}
return sa
}

Denna metod är väldigt enkel, vi loopar igenom alla CandyBagItem som vi har i listan items och plussar ihop priset som vi till sist returnerar. Metoden price() på CandyBagItem finns inte än, så den måste vi skapa. Öppna CandyBagItem.groovy och lägg till följande kod:

double price() {
return candy.price * quantity
}

Metoden multiplicerar priset på Candy med antalet. Då är det dags att skapa de nya händelserna i CandyStoreController. Öppna filen CandyStoreController.groovy och lägg till följande kod:


def add = {
def candyBag = session.candyBag
def item = new CandyBagItem(candy: Candy.findById(params.id), quantity: params.quantity)
candyBag.items << item

redirect (action: ”shop”)
}

Add-händelsen skapar ett nytt CandyBagItem objekt och sätter vald Candy och antal (Candy.findById() är återigen en utav all de dynamiska metoder som finns tillgängliga att använda). CandyBagItem objektet lagras i listan (ytterligare ett anrop till en dynamisk metod addTo*) och händelsen redirectar sedan till shop-händelsen. Nu lägger vi till dropCandy-händelsen:


def dropCandy = {
def candyBag = session.candyBag
int idx = Integer.parseInt(params.id)
candyBag.items.remove(idx)

redirect action: ”shop”
}

DropCandy-händelsen tar bort vald CandyBagItem från listan och avslutar med att redirecta till shop-händelsen. Vi måste även skapa cansel-händelsen:

def cancel = {
session.removeAttribute(”candyBag”)
redirect (uri: ”/index.gsp”)
}

Cancel-händelsen tar bort CandyBag objektet från sessionen och redirectar till index-sidan. Till sist så måste vi lägga till buy-händelsen:

def buy = {
render (view: ”confirm”, model:[candyBag: session.candyBag])
}

Buy-händelsen ser till att confirm.gsp renderas och sätter candyBag variabeln för den sidan till vårat CandyBag objekt.

Skapa sidan candystore\grails-app\views\candyStore\confirm.gsp och lägg till följande kod:

<html>
<head>
<meta http-equiv=”Content-Type” content=”text/html; charset=UTF-8″/>
<meta name=”layout” content=”candystore” />
<title>Shoppen – Bekräfta köp!</title>
</head>
<body>
<div class=”nav”>
<a href=”${createLinkTo(dir:”)}”>[Hem]</a>
</div>

<g:if test=”${candyBag?.items.size() > 0}”>
<div class=”candyBag” style=”margin: 10px”>
Min godispåse
<table style=”border: thin solid black; padding: 5px”>
<tbody>
<g:each in=”${candyBag?.items}” var=”item” status=”idx”>
<tr>
<td>${item.quantity} st</td>
<td>${item.candy.name} á ${item.candy.price}</td>
</tr>
</g:each>
</tbody>
<tfoot>
<tr>
<td colspan=”2″ style=”font: normal bold 14px arial; text-align: left”>
Att betala: ${candyBag.total()}
</td>
</tr>
<tr>
<td colspan=”2″ style=”text-align: right”>
<g:link action=”checkout”>KÖP</g:link> | <g:link action=”cancelCheckout”>AVBRYT</g:link>
</td>
</tr>
</tfoot>
</table>
</div>
</g:if>
</body>
</html>

Denna sida anropar två stycken nya händelser som vi nu måste skapa i CandyStoreController.groovy, checkout och cancelCheckout.

def cancelCheckout = {
redirect(action: ”shop”)
}

def checkout = {
def candyBag = session.candyBag
candyBag.save()
session.removeAttribute(”candyBag”)
flash.message = ”Tack för att du shoppade snarre hos oss! Välkommen åter!”
redirect (uri: ””)
}

CancelCheckout-händelsen tar oss bara tillbaka till shop-sidan. Checkout-händelsen lagrar vår CandyBag i databasen, tar bort den från sessionen och skriver ett meddelande i flash-scopet och skickar oss sedan vidare till index-sidan. Lägg till följande kod i index.gsp


<body>
<h1 style=”margin-left:20px;”>Välkommen till Godisaffären</h1>
<p style=”margin-left:20px;width:80%”>Välkommen in och <g:link controller=”candyStore” action=”shop”>shoppa lite snarre!!</g:link></p>

<g:if test=”${flash.message}”>
<div>
${flash.message}
</div>
</g:if>
</body>

Koden vi nu la till, kollar om det finns en parameter som heter message i flash scopet, om det gör det så skrivs det värdet ut.

Nu är det dags att testköra vår applikation. Starta applikationen med grails run-app och gå till följande adress med din browser:

http://localhost:8080/candystore

Klicka runt lite och testa för att se om den fungerar som tänkt. Köp några godispåsar fyllda med godis. Det finns självklart mycket kvar att finslipa på, men tanken med den här artikeln är bara att vi skall komma igång med Grails och få en känsla för hur det fungerar. En grej som vi kan fixa till är hur beloppen presenteras i applikation. Just nu så står det bara en en decimal och ingen valuta. För att fixa till det så tänkte jag att vi skulle göra en egen tag som formaterar beloppet. I Grails är det superenkelt att skapa en egen tag, skapa bara en fil som slutar på TagLib i mappen grails-app\taglib. Skapa följande fil candystore\grails-app\taglib\CandyStoreTagLib.groovy genom att köra kommandot

grails create-tag-lib candyStore

Öppna filen och lägg till följande kod:

class CandyStoreTagLib {

def csFormatAmount = {attrs ->
def amount = attrs.amount
if(!amount)
amount = new Double(0)

def format = attrs.format
if(!format)
format = ”#.00″

def currency = attrs.currency
if(!currency)
currency = ”kr”

amount = new java.text.DecimalFormat(format).format((Double)amount)
out << ”${amount} ${currency}”
}
}

Man defenierar sina taggar som groovy closures, parametern attrs innehåller attributen till våran tag. Vår tag heter csFormatAmount och har tre attribut, amount, format och currency. Koden kollar alla attribut och sätter defaultvärden om de inte är satta på taggen. Nu kan vi genom att skriva <g:csFormatAmount /> i våra gsp-sidor använda vår tag. Modifiera tre ställen i shop.gsp till att använda vår tag:


<td>${item.candy.name} á <g:csFormatAmount amount=”${item.candy.price}” /></td>

S:a: <g:csFormatAmount amount=”${candyBag.total()}” />

<td>${candy.name?.encodeAsHTML()}</td>
<td><g:csFormatAmount amount=”${candy.price}”/></td>
<td><g:select name=”quantity” from=”${1..20}” /></td>

Gör samma ändringar i confirm.gsp


<td>${item.candy.name} á <g:csFormatAmount amount=”${item.candy.price}” /></td>

Att betala: <g:csFormatAmount amount=”${candyBag.total()}” />

Mycket enklare än så kan det inte vara att skapa ett eget TagLib.

Nu är vi nöjda med vår applikation, men det kan ju vara lite intressant att kolla på datat som har sparats i databasen när vi genomförde vår testkörning. Eftersom vi använder HSQLDB så kan vi starta HSQLDB’s DatabaseManager för att kolla på våra tabeller. Kör följande kommando (se till att servern är avstängd, annars så kan du inte läsa db filen och att du står i root-katalogen till ditt projekt):

java -cp $GRAILS_HOME/lib/hsqldb-1.8.0.5.jar org.hsqldb.util.DatabaseManager

Fyll i följande i URL fältet och klicka på OK

jdbc:hsqldb:file:devDB

Kör följande SQL: select * from candy_bag

Då skall ni se en lista med alla CandyBags som ni köpte när ni testkörde applikationen, i mitt fall så ser jag två.

Kör följande SQL: select * from candy_bag_item

WTF?! Inga rader? Jag vet då att jag köpte en massa godis. Vad beror nu detta på? Jo, vi har ju inte berättat för Grails vilken relation våra domänklasser har till varandra. Det måste vi ju göra. Men först så dumpar vi vår felaktiga data från databasen:

delete from candy_bag

Stäng ner DatabaseManager och öppna CandyBag.groovy och lägg till följande kod:

class CandyBag {
List items = []

static hasMany = [items: CandyBagItem]

double total(){
double sa = 0
items.each(){
sa += it.price()
}
return sa
}
}

Vår klass CandyBag har många instanser av klassen CandyBagItem (en-till-många relation), detta defenieras i Grails med den statiska propertyn hasMany. Det defaulta cascading-beteendet är så att vid save och update av CandyBag så kommer även CandyBagItem sparas eller uppdateras. Men vi vill även att alla CandyBagItem’s tas bort om vi tar bort en CandyBag. För att få denna funktion så måste vi defeniera följande i CandyBagItem.groovy:

class CandyBagItem {
static belongsTo = [candyBag: CandyBag]

Candy candy
int quantity

double price() {
return candy.price * quantity
}
}

Vi måste även modifiera vår add händelse i CandyStoreController.groovy så vi använder en metod som Grails hanterar när vi lägger till CandyBagItems. Gör följande felstilta ändring i CandySoreController.groovy:

def add = {
def candyBag = session.candyBag
def item = new CandyBagItem(candy: Candy.findById(params.id), quantity: params.quantity)
candyBag.addToItems(item) // addTo* är en dynamisk metod som Grails hanterar

redirect (action: ”shop”)
}

Om vi nu testkör applikationen och genomför något köp och sedan kolla datan i databasen så skall vi se rader i båda tabellerna candy_bag och candy_bag_item (om det skulle visa sig att det inte finns några rader alls i databasen när du kollar beror på att du var för snabb med att stänga ner servern innan Grails hann flusha datat).

Nu är det dags att börja kolla på vår administrationsdel. Vi skall kunna kolla på vårat godissortiment och vi måste även kolla kunna se våra beställningar. Vi behöver alltså vanlig CRUD-funktionalitet. Kör följande kommandon:

grails generate-all candy
grails generate-all candyBag
grails generate-all candyBagItem

Ovanstående kommandon skapar en kontroller med alla CRUD-händelser och även alla vyer för varje domänklass. Vi behöver nu en startsida för vår admindel. Skapa följande fil candystore\web-app\admin.gsp och lägg till följande kod:

<html>
<head>
<title>ADMIN CandyStore</title>
<meta name=”layout” content=”main” />
</head>
<body>
<h1 style=”margin-left:20px;”>Admin Godisaffärren</h1>
<div class=”dialog” style=”margin-left:20px;width:60%;”>
<ul>
<g:each var=”c” in=”${grailsApplication.controllerClasses}”>
<li class=”controller”><a href=”${c.logicalPropertyName}”>${c.fullName}</a></li>
</g:each>
</ul>
</div>
</body>
</html>

Admin.gsp listar alla vår kontrollers och skapar länkar som vi kan klicka på. Så där ja, då har vi all adminfunktionalitet vi behöver. Det som återstår nu är bara att skydda admindelarna. Jag tänkte göra en väldigt enkel autentisering, vi börjar med att skapa en admin-kontroller och en loginsida. Skapa kontrollern med följande kommando:

grails create-controller admin

Öppna AdminController.groovy och lägg till följande kod:

class AdminController {

def index = {
render view: ”login”
}
def authenticate = {
if(params.user == ”admin” && params.pwd == ”admin”){
session.loggedIn = ”admin”
redirect(uri: ”/admin.gsp”)
}else{
flash.message = ”fel användarnamn eller lösenord”
render view: ”login”
}
}
}

Vi ser till att den defaulta händelsen renderar vyn login. Vi skapar en ny händelse, authenticate, som kollar om användarnamnet användaren har fyllt i är ”admin” och lösenordet detsamma. Är det så sätter vi sessionsattributet loggedIn och redirectar till admin.gsp sidan. Har man skrivit in fel användarnamn eller lösenord så sätter vi ett litet meddelande och renderar login sidan på nytt. Skapa filen candystore\grails-app\views\admin\login.gsp och lägg till följande kod:

<html>
<head>
<title>LOGIN</title>
</head>
<body>
<g:if test=”${flash.message}”>
<div style=”padding: 5px; margin: 10px; border: thin solid red; color: red; font-weight: bold”>
${flash.message}
</div>
</g:if>
<div style=”margin-left:20px;width:60%;”>
<g:form action=”authenticate” controller=”admin”>
<table>
<tr>
<td>Användarnamn:</td>
<td><g:textField name=”user”/></td>
</tr>
<tr>
<td>Lösenord:</td>
<td><input type=”password” name=”pwd” /></td>
</tr>
<tr>
<td colspan=”2″><g:submitButton name=”login” value=”Login” /></td>
</tr>
</table>
</g:form>
</div>
</body>
</html>

En väldigt enkel sidan med ett formulär där användaren får mata in användarnamn och lösenord. Nu skall vi lösenordsskydda alla admindelar. Detta tänkte jag fixa till med ett filter. För att skapa filter i Grails behöver man bara skapa en fil i grails-app\conf mappen som heter *Filters. Vi skapar följande fil, candystore\grails-app\conf\CandyStoreFilters.groovy och defenierar två stycken filter:

class CandyStoreFilters {

def filters = {
candy(controller:”candy”, action:”*”){
before = {
if(!session.loggedIn){
redirect(uri:”/admin/login.gsp”)
return false
}
}
}
candyB(controller:”candyB*”, action:”*”){
before = {
if(!session.loggedIn){
redirect(uri:”/admin/login.gsp”)
return false
}
}
}
}
}

Först skapar vi ett filter som vi kallar för candy, den koden kommer alltid köras innan någon händelse i CandyController utförs. Koden kollar om attributet loggedIn finns i sessionen (om du kommer ihåg så sätter vi det attributet i AdminController.authenticate om användaren har skrivit in korrekt användarnamn och lösenord), om så inte är fallet så redirectas man vidare till loginsidan. Att vi avslutar med return false garanterar att händelsen i kontrollen inte kommer att utföras. Sen så skapar vi ytterligare ett filter, candyB, som alltid kommer att utföras innan någon händelse i någon kontroller som börjar med CandyB* utförs. Wildcardet * gör så att detta filter tar hand om CandyBagController och CandyBagItemController. Koden är densamma som i candy filtret. Om vi nu startar vår applikation och går till sidan:

http://localhost:8080/candystore/admin.gsp

och klickar på någon utav följande kontrollerlänkar, CandyController, CandyBagController eller CandyBagItemController så kommer vi hamna på loginsidan. Om vi där loggar in med admin/admin så blir vi autentiserade och får access till de skyddade resurserna. Nu återstår bara att även skydda admin.gsp sidan, detta tänkte jag lösa med en ny tag, csArea51. Dessutom så listar admin.gsp alla våra kontrollers som finns i vår applikation, detta fixar vi till med en egen tag också, som filtrerar bort de kontroller som vi inte är intresserade av. Öppna filen CandyStoreTagLib.groovy och lägg till följande kod:

def csArea51 = {
if(!session.loggedIn){
def ctx = servletContext.getServletContextName()
response.sendRedirect(”${ctx}/admin/login.gsp”)
}
}

def csAdminControllers = {
def filter = {ctrl ->
switch(ctrl.fullName){
case ”AdminController”:
return false
case ”CandyStoreController”:
return false
default:
return true
}
}

def ctrls = grailsApplication.controllerClasses.findAll(filter)

out << ”<ul>”
ctrls.each(){
out << ”<li><a href=’${it.logicalPropertyName}’>${it.fullName}</a></li>”
}
out << ”</ul>”
}

csArea51-taggen gör precis samma sak som våra filter, kollar om loggedIn attributet finns i sessionen, om inte så skickas man till loginsidan. csAdminControllers-taggen filtrerar listan med alla kontrollers och renderar sedan ut en punktlista med länkar.

Öppna admin.gsp och gör följande ändringar:

<g:csArea51/>
<html>
<head>
<title>ADMIN CandyStore</title>
<meta name=”layout” content=”main” />
</head>
<body>
<h1 style=”margin-left:20px;”>Admin Godisaffärren</h1>
<div class=”dialog” style=”margin-left:20px;width:60%;”>
<g:csAdminControllers />
</div>
</body>
</html>

Om du nu testkör applikationen och försöker accessa http://localhost:8080/candystore/admin.gsp (du måste starta om servern) så kommer du hamna på loginsidan. Logga in med admin/admin så kommer du till adminsidan och där ser du att listan med kontrollers endast innehåller de som vi är intresserade av.

Så där ja, det var hela vår applikation. Vi har skapat en enkel liten webbshopp med komplett MVC, persistens i databas, filter och egna taggar utan att konfigurera en endaste xml-fil.

Jag hoppas att denna artikel har fått igång ditt intresse för Grails!

Resurser:
http://grails.org
http://grails.org/doc/RC1/

Kommentarer

  • [...] Java, Systemutveckling | Taggar: grails |   För ett tag sedan så skrev jag en liten intro till Grails, och då använde jag version 1.0RC1, nu har äntligen Grails 1.0 släppts. Mer kan läsas här och [...]

Skriv kommentar