Kręcę się koło tworzenia aplikacji grailsowej, którą mógłbym obsłużyć kilka tematów - najważniejszy z nich, to narzędzie pomocnicze do nauki dla moich dzieciaków (jak mają coś wkuwać, np. chemia czy angielski, to nie są za bardzo zmotywowane do pracy z książką, a przy kompie to jakoś tak wszystko łatwiej im wchodzi), po drugie jako sposób na pogłębienie wiedzy praktycznej z Grails i ostatni, to po prostu stworzenie aplikacji referencyjnej, którą mógłbym wykorzystać do moich kolejnych wystąpień o Grails (jest jeszcze jeden, a właściwie dwa powody, ale o nich na razie sza).
Pierwszy krok podczas tworzenia aplikacji grailsowej to stworzenie projektu poleceniem grails create-app. Określamy nazwę projektu i po chwili mamy właściwą strukturę katalogową (dobry moment, aby umieścić ją od razu w systemie kontroli wersji - właśnie otworzyłem konto na github, aby nie być gołosłownym i stworzyłem projekt nauczyciel).
Warto przyjrzeć się wynikowi polecenia grails create-app, aby zajrzeć za kulisy Grails.
jlaskowski@work /cygdrive/c/projs/sandboxWarto nadmienić, że polecenie grails to tak naprawdę skrypt napisany w Gant, czyli nakładce Groovy dla Ant. Wiedza jaką mamy odnośnie Ant jest wystarczająca, aby zrozumieć działanie Gant. Dodając do tego znajomość Groovy i mamy komplet. Każde polecenie grails ma swój skrypt. Grails przeszukuje właściwe katalogi w poszukiwaniu skryptu i w przypadku create-app będzie to $GRAILS_HOME/scripts/CreateApp_.groovy. Można do niego zajrzeć, aby poznać, jak niewiele trzeba było, aby stworzyć Grails (można powiedzieć, że wszystko było, tylko należało dopasować do siebie części - Spring, Hibernate, Groovy, teraz Ant i kilka innych klocków). Pierwsza linia tego skryptu to
$ grails create-app nauczyciel
Welcome to Grails 1.1 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: c:/apps/grails
Base Directory: C:\projs\sandbox
Running script c:\apps\grails\scripts\CreateApp_.groovy
Environment set to development
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\src
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\src\java
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\src\groovy
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\grails-app
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\grails-app\controllers
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\grails-app\services
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\grails-app\domain
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\grails-app\taglib
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\grails-app\utils
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\grails-app\views
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\grails-app\views\layouts
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\grails-app\i18n
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\grails-app\conf
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\test
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\test\unit
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\test\integration
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\scripts
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\web-app
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\web-app\js
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\web-app\css
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\web-app\images
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\web-app\META-INF
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\lib
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\grails-app\conf\spring
[mkdir] Created dir: C:\projs\sandbox\nauczyciel\grails-app\conf\hibernate
[propertyfile] Creating new property file: C:\projs\sandbox\nauczyciel\application.properties
[copy] Copying 1 resource to C:\projs\sandbox\nauczyciel
[unjar] Expanding: C:\projs\sandbox\nauczyciel\grails-shared-files.jar into C:\projs\sandbox\nauczyciel
[delete] Deleting: C:\projs\sandbox\nauczyciel\grails-shared-files.jar
[copy] Copying 1 resource to C:\projs\sandbox\nauczyciel
[unjar] Expanding: C:\projs\sandbox\nauczyciel\grails-app-files.jar into C:\projs\sandbox\nauczyciel
[delete] Deleting: C:\projs\sandbox\nauczyciel\grails-app-files.jar
[move] Moving 1 file to C:\projs\sandbox\nauczyciel
[move] Moving 1 file to C:\projs\sandbox\nauczyciel
[move] Moving 1 file to C:\projs\sandbox\nauczyciel
Installing plug-in hibernate-1.1
[mkdir] Created dir: C:\Documents and Settings\jlaskowski\.grails\1.1\projects\nauczyciel\plugins\hibernate-1.1
[unzip] Expanding: C:\Documents and Settings\jlaskowski\.grails\1.1\plugins\grails-hibernate-1.1.zip
into C:\Documents and Settings\jlaskowski\.grails\1.1\projects\nauczyciel\plugins\hibernate-1.1
Executing hibernate-1.1 plugin post-install script ...
Plugin hibernate-1.1 installed
Created Grails Application at C:\projs\sandbox/nauczyciel
includeTargets << grailsScript("_GrailsCreateProject")która (jak łatwo się zorientować) dołącza zawartość kolejnego skryptu _GrailsCreateProject.groovy i ustawia zadanie createApp jako domyślne. Kiedy zaczniemy przeglądać skrypt dalej dojdziemy do zadania createStructure (przez initProject) , który znajduje się w _GrailsInit.groovy i przedstawia się następująco:
target(createStructure: "Creates the application directory structure") {Właśnie to zadanie najbardziej uzmysławia, że to co się dzieje pod spodem create-app, to nic innego jak stary, dobry Ant przesłonięty Groovy DSL. Zero magii (która towarzyszyła mi na początku poznawania Grails).
ant.sequential {
mkdir(dir: "${basedir}/src")
mkdir(dir: "${basedir}/src/java")
mkdir(dir: "${basedir}/src/groovy")
mkdir(dir: "${basedir}/grails-app")
mkdir(dir: "${basedir}/grails-app/controllers")
mkdir(dir: "${basedir}/grails-app/services")
mkdir(dir: "${basedir}/grails-app/domain")
mkdir(dir: "${basedir}/grails-app/taglib")
mkdir(dir: "${basedir}/grails-app/utils")
mkdir(dir: "${basedir}/grails-app/views")
mkdir(dir: "${basedir}/grails-app/views/layouts")
mkdir(dir: "${basedir}/grails-app/i18n")
mkdir(dir: "${basedir}/grails-app/conf")
mkdir(dir: "${basedir}/test")
mkdir(dir: "${basedir}/test/unit")
mkdir(dir: "${basedir}/test/integration")
mkdir(dir: "${basedir}/scripts")
mkdir(dir: "${basedir}/web-app")
mkdir(dir: "${basedir}/web-app/js")
mkdir(dir: "${basedir}/web-app/css")
mkdir(dir: "${basedir}/web-app/images")
mkdir(dir: "${basedir}/web-app/META-INF")
mkdir(dir: "${basedir}/lib")
mkdir(dir: "${basedir}/grails-app/conf/spring")
mkdir(dir: "${basedir}/grails-app/conf/hibernate")
}
}
Następnie pora na stworzenie klasy dziedzinowej poleceniem grails create-domain-class pl.jaceklaskowski.nauczyciel.Pierwiastek. Zazwyczaj polecenia do tworzenia klas w Grails nie zawierają opcjonalnego pakietu. Postanowiłem zmienić ten zwyczaj (u siebie przynajmniej). Zalet nadawania pakietów do naszych klas nie ma co przedstawiać - ochrona przez potencjalnym konfliktem z tą samą klasą z innego projektu. Do klasy dodaję trzy atrybuty - symbol, nazwa i opis. Warto w tym momencie skorzystać z jakiegoś IDE - ja korzystam z NetBeans 6.7 (wersja z dzisiaj).
package pl.jaceklaskowski.nauczycielMamy dziedzinę (model). Pozostaje stworzyć dla niej kontroler. Możemy tak - grails create-controller pl.jaceklaskowski.nauczyciel.Pierwiastek, albo podpieramy się narzędziami w IDE (na węźle Controllers wybieramy New > Grails Controller...).
class Pierwiastek {
String symbol
String nazwa
String opis
}
package pl.jaceklaskowski.nauczycielTeraz mamy kilka możliwości - albo kasujemy domknięcie def index i definiujemy def scaffold = Pierwiastek, co włączy dynamiczne rusztowanie (ang. scaffolding), albo tworzymy widok, który będzie odpowiadał domknięciu index (w przeciwnym przypadku skończymy z 404 jak w Wystąpienia publiczne jako utrwalenie wiedzy z Grails w tle). Najlepiej byłoby połączyć oba rozwiązania i wiem, że się da, ale raz, że nie pamiętam dokładnie jak, a dwa, że nie będę teraz aż tak kombinował. Wybieram stworzenie widoku dla akcji index. I ponownie mamy kilka opcji - albo grails generate-views pl.jaceklaskowski.nauczyciel.Pierwiastek, albo podparcie się IDE do stworzenia domyślnej, pustej funkcjonalnie strony grails-app/views/pierwiastek/index.gsp. Wybieram pierwsze podejście (jeszcze z niego nie korzystałem publicznie). W katalogu grails-app/views/pierwiastek powinny pojawić się 4 pliki, które odpowiadają każdej z akcji CRUD:
class PierwiastekController {
def index = { }
}
$ ls grails-app/views/pierwiastek/Ale moment! Nie ma index.gsp?! Zatem nie pozostaje mi nic innego jak wrócić do stworzenia jej ręcznie. Z pomocą NetBeans otrzymuję następującą stronę grails-app/views/pierwiastek/index.gsp:
create.gsp edit.gsp list.gsp show.gsp
<%@ page contentType="text/html;charset=UTF-8" %>Uruchomienie aplikacji poleceniem grails run-app i chwila na zastanowienie, co dalej.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Lista pierwiastków</title>
</head>
<body>
<h1>Na razie niewiele, ale początek już mamy...</h1>
</body>
</html>
Aktualna wersja aplikacji znajduje się w repozytorium git jako projekt nauczyciel. Zainteresowanych udziałem w tym mini-projekcie zapraszam do kontaktu. Najgorzej będzie ze stylami (rozmieszczę div'y, ale pewnie nic poza tym), więc jeśli masz zmysł plastyczny i potrafisz stworzyć właściwe CSSy koniecznie musimy obgadać temat współpracy.
Może się przyda się przyda http://www.mor.ph/ ;)
OdpowiedzUsuń"You can deploy Rails, Java, Grails, and PHP applications to Morph AppSpace."
Pozdrawiam,
Krzysztof Kowalczyk
Już od jakiegoś czasu zastanawiam się nad umieszczaniem klas domenowych i kontrolerów w pakietach, do tej pory tego nie robiłem: po co przecież mój projekt jest mały, nie dojdzie do konfliktów nazw. Ale chyba za Twoim przykładem zacznę robić porządek w klasach :)
OdpowiedzUsuńA tak przy okazji: ciekawe jak zachowają się Grails'y w sytuacji dwóch klas domenowych o tej samej nazwie, ale w różnych pakietach? Czy aby GORM nie zgłupieje? Jak to zostanie odwzorowane w bazie?
Muszę sprawdzić :)
Uprzejmie donoszę, że sprawdziłem. GORM zgłupiał, ale tylko troszkę. Utworzył jedną tabelę i wszystko wrzuca do niej. Utworzyłem dwie klasy Test w różnych pakietach, uruchomiłem aplikację - w bazie pojawiła się jedna tabela. Dodałem do kontrolera utworzenie instancji i zapisanie dla obu typów - dwa rekordy w jednej tabeli.
OdpowiedzUsuńA tu jeszcze parę słów na ten temat:
OdpowiedzUsuńhttp://tech.mrozewski.pl/2009/03/13/grails-i-klasy-domenowe-o-tych-samych-nazwach/
I masz moją odpowiedź tam ;-)
OdpowiedzUsuńDlaczego warto umieszczać kasy domenowe w pakietach:
OdpowiedzUsuńhttp://marceloverdijk.blogspot.com/2009/03/grails-tip-of-day-always-use-packages.html
Idealnie wpasowało się w naszą dyskusję :)