Tym razem przejdziemy do konfiguracji adresów URL w Grails. Rozdział 6. "Mapping URLs" pozwala zrozumieć, w jaki sposób można definiować relację pomiędzy URL a jego kontrolerem i akcją z możliwością przesyłania własnych parametrów.
Pierwsza część URL (po kontekście aplikacji webowej) to nazwa kontrolera, a kolejna (opcjonalna) nazwa jego akcji (domknięcia), np. dla poniższego kontrolera PierwiastekController:
package pl.jaceklaskowski.nauczycieladres http://localhost:8080/nauczyciel/pierwiastek/index to wykonanie jego akcji index. Jeśli nie podano opcjonalnej nazwy akcji, zostanie wykonana akcja domyślna kontrolera. Ostatni, opcjonalny, element URL to identyfikator id.
class PierwiastekController {
def index = {
render 'Wywołano index'
}
}
Definicja składowych URL znajduje się w grails-app/conf/UrlMappings.groovy.
Jeśli korzystamy z opcjonalnych składowych (za pomocą operatora dereferencji w Groovy - ?) w URL to muszą one być na końcu wzorca.
Zmienna to składowa URL poprzedzona $. Mapowanie może zawierać tekst statyczny. Nazwa kontrolera i jego akcji nie muszą występować w URL. Za pomocą bloku static mappings w skrypcie UrlMappings możemy przypisać wykonanie wskazanego kontrolera i jego akcji, np. (Listing 6-3):
class UrlMappings {Powyższa konfiguracja jest równoznaczna z
static mappings = {
"/pierwiastek/$id" {
controller = 'pierwiastek'
action = 'show'
}
}
}
class UrlMappings {Wybór należy do nas, który ze sposobów bardziej do nas przemawia.
static mappings = {
"/pierwiastek/$id"(controller = 'pierwiastek', action = 'show')
}
}
}
W Grails preferuje się adresy typu /pierwiastek/potas zamiast typowych /pierwiastek?nazwa=potas. Przekazanie wartości "potas" w parametrze nazwa to konfiguracja:
class UrlMappings {Dostęp do zmiennej jest możliwy przez zmienną params, bez względu, czy przekazaliśmy parametr przez mapowanie w UrlMappings (jako część URL), czy bezpośrednio jako parametr żądania.
static mappings = {
"/pierwiastek/$nazwa"(controller = 'pierwiastek', action = 'show')
}
}
}
Autorzy proponują korzystać z konwencji "podkreślnik-zamiast-%20", aby adres typu /zwiazek/chlorek wapnia miał postać /zwiazek/chlorek_wapnia zamiast /zwiazek/chlorek%20wapnia. Wystarczy połączyć wcześniejsze mapowanie (z static mappings) z wykonaniem metody replaceAll('_', ' '), aby dostać się do poprawnej wartości parametru, tj.
class PierwiastekController {Poza tym, możliwe jest definiowanie nowych zmiennych żądania, jak gdyby były one w nim przekazane. W ten sposób wykonanie URL rozpoczynającego się /pokazPierwiastek/<nazwa> miało inny skutek niż wykonanie /pokazPierwiastekPelny/<nazwa>. Dla użytkownika końcowego wyróżnikiem jest adres URL, podczas, gdy jego obsługą zajmuje się kontroler, który wywołany zostanie z właściwymi parametrami, tj.
def show = {
def pierwiastek = Pierwiastek.findByNazwa(params.nazwa.replaceAll('_', ' '))
render "Symbol pierwiastka: ${pierwiastek.symbol}, nazwa: ${pierwiastek.nazwa}"
}
}
class UrlMappings {W ten sposób, obsługa akcji wyswietl w kontrolerze PierwiastekController podejmowałaby decyzję o ilości wyświetlanych danych na bazie parametru format (${params.format}).
static mappings = {
"/pokazPierwiastek/$nazwa(controller = 'pierwiastek', action = 'wyswietl') {
format = 'prosty'
}
"/pokazPierwiastekPelny/$nazwa(controller = 'pierwiastek', action = 'wyswietl') {
format = 'pelny'
}
}
}
Możliwe jest związanie adresu z konkretnym widokiem - stroną GSP. Użyteczne w sytuacji, kiedy widok nie potrzebuje modelu oraz żadna z akcji kontrolera nie musi być wcześniej wykonana. Mapowanie jest identyczne do poprzednich, z kontrolerami i ich akcjami, z tą różnicą, że zamiast action definiujemy parametr view, np.:
class UrlMappings {W wyniku zostanie wyświetlona strona grails-app/views/welcome.gsp. Jeśli poza view określimy również parametr controller, wtedy strona GSP będzie tą, która związana jest z danym kontrolerem, np.:
static mappings = {
"/"(view: '/welcome')
}
}
class UrlMappings {Dla /wyszukaj zostanie wyświetlona strona grails-app/views/pierwiastek/wyszukaj.gsp *bez* wcześniejszego wykonania akcji w kontrolerze.
static mappings = {
"/wyszukaj"(view: 'wyszukaj', controller: 'pierwiastek')
}
}
Poza tym, możemy w (pod)sekcji constraints nadawać warunki jakie muszą spełniać parametry żądania, aby doszło do wykonania kontrolera, albo wyświetlenia strony, np.:
class UrlMappings {Mechanizm jest podobny do warunków w klasach dziedzinowych. I podobnie jak w klasach dziedzinowych, wszystkie warunki muszą zajść, aby wykonane zostało mapowanie.
static mappings = {
"/pierwiastek/$masaAtomowa(controller = 'pierwiastek', action = 'wyswietl') {
constraints {
masaAtomowa matches: /[0-9]{2}/
}
}
}
}
UWAGA: Podobieństwo między warunkami w klasach dziedzinowych, a tymi w mapowaniu URL jest niezwykle subtelne - w klasach dziedzinowych mamy do czynienia ze statyczną zmienną, której przypisujemy domknięcie (jako jej wartość), a w mapowaniu URL wykonujemy metodę constraints przekazując jej parametr będący domknięciem. Uwaga na brak znaku równości. Cuda języka Groovy.
Mapowanie URL pozwala na związanie go z danym adresem URL z użyciem symbolów maski (wieloznacznik) - * (gwiazdka) lub ** (dwie gwiazdki), co oznacza cokolwiek spełniającego warunek bez konieczności przekazywania ciągu spełniającego maskę jako parametr żądania, np.: (Listing 6-14)
class UrlMappings {co oznacza, że kontroler image jest związany z adresami typu /images/*.jpg, ale już nie z /images/podkatalog/*.jpg, który jest spełniony przy zastosowaniu podwójnej gwiazdki (dowolna hierarchia katalogów). Ciąg znaków spełniający maskę nie będzie związany z żadnym parametrem żądania. Jeśli jednak chcielibyśmy przypisać pasujący ciąg znaków do parametru, poprzedzamy symbol maski nazwą parametru, np.: (Listing 6-16)
static mappings = {
"/images/*.jpg"(controller:'image')
}
}
class UrlMappings {Dla /images/photos/president.jpg parametr pathToFile ma wartość photos/president.
static mappings = {
"/images/$pathToFile**.jpg"(controller: 'image')
}
}
Istnieje możliwość związania różnych akcji kontrolera dla różnych typów żądań HTTP - GET, POST, PUT i DELETE. Zamiast sprawdzać w kontrolerze typ żądania HTTP za pomocą request.method możemy wskazać w action, która akcja kontrolera obsługuje dany typ, np.: (Listing 6-18):
class UrlMappings {Wartością action jest mapa - typ żądania HTTP i akcja kontrolera.
static mappings = {
"/artist/$artistName" {
controller = 'artist'
action = [GET: 'show',
PUT: 'update',
POST: 'save',
DELETE: 'delete']
}
}
}
Możemy również wiązać kody odpowiedzi HTTP, np. 404 czy 500 z danym widokiem bądź akcją kontrolera, np.: (Listing 6-20)
class UrlMappings {Domyślnie, Grails obsługuje kod 500 (Internal Error), który wyświetla widok /error (strona grails-app/views/error.gsp), która pozwala na namierzenie błędu w razie...błędu. Zakłada się, że jest użyteczne podczas tworzenia aplikacji i jej debugowaniu. W naszych stronach możemy korzystać z parametru exception (przykładem może być właśnie grails-app/views/error.gsp).
static mappings = {
"404"(controller:'store')
}
}
Do tej pory przedstawiony został mechanizm obsługi żądań do własnych URLi. Autorzy przedstawiają sposób tworzenia tych URLi za pomocą znaczników GSP - g:link. Wystarczy dostarczyć niezbędne parametry w atrybucie params wraz z odpowiednio zdefiniowanymi atrybutami action oraz controller, a g:link stworzy właściwy URL, np.: (Listing 6-22)
class UrlMappings {Przy takim UrlMappings wystarczy skonstruować g:link w następujący sposób: (Listing 6-23)
static mappings = {
"/showArtist/$artistName"(controller:'artist', action:'show')
}
}
<g:link action='show'Plik UrlMappings można dzielić na mniejsze pliki. Wystarczy, aby miały taką samą strukturę - static mappings, znajdowały się w katalogu grails-app/conf i ich nazwa kończyła się na UrlMappings.
controller='artist'
params="[artistName:${artist.name.replaceAll(' ', '_')}]">
${artist.name}
</g:link>
Testowanie własnych mapowań jest możliwe z klasą grails.test.GrailsUrlMappingsTestCase, która rozszerza z kolei klasę groovy.util.GroovyTestCase. Metoda assertForwardUrlMapping sprawdza, czy żądanie do zadanego URL jest obsługiwane przez właściwą akcję kontrolera, np.: (Listing 6-26)
class ArtistUrlMappingTests extends grails.test.GrailsUrlMappingsTestCase {Jeśli mapowanie definiuje własne parametry żądania, np. wcześniej korzystaliśmy z artistName, wtedy sprawdzenie jest możliwe przez przekazanie opcjonalnego domknięcia do metody assertForwardUrlMapping z parametrami i ich żądanymi wartościami, np.
void testShowArtist() {
assertForwardUrlMapping('/showArtist/Jeff_Beck',
controller:'artist', action:'display')
}
}
class ArtistUrlMappingTests extends grails.test.GrailsUrlMappingsTestCase {Sprawdzenie, czy mapowanie dla g:link da oczekiwany rezultat odbywa się za pomocą metody assertReverseUrlMapping, która działa i ma parametry analogiczne do assertForwardUrlMapping.
void testShowArtist() {
assertForwardUrlMapping('/showArtist/Jeff_Beck',
controller:'artist', action:'display') {
artistName = 'Jeff_Beck'
}
}
}
Sprawdzenie obu przypadków to wykonanie metody assertUrlMapping.
Klasa GrailsUrlMappingsTestCase wczytuje wszystkie mapowania aplikacji. Jeśli chcemy zawęzić zbiór testowanych mapowań, wystarczy zdefiniować w klasie testującej statyczną zmienną mappings, która przyjmuje pojedynczą nazwę klasy mapującej, albo ich listę, np.: (Listing 6-30):
class ArtistUrlMappingsTests extends grails.test.GrailsUrlMappingsTestCase {Więcej w dokumentacji Grails w Testing URL Mappings.
static mappings = [UrlMappings, ArtistUrlMappings]
//...
}
A czy zamiast 'pierwiastek' można użyć PierwiastekController, jak poniżej:
OdpowiedzUsuńclass UrlMappings {
static mappings = {
"/pierwiastek/$id" {
controller = PierwiastekController
action = PierwiastekController.show
}
}
}
?
Nie. Konwencją jest nazywanie kontrolerów przez ich skróconą nazwę (to może mieć znaczenie w "przestrzeni springowej", ponieważ większość bytów w Grails to tak na prawdę ziarna springowe), a ich klasy kończą się na Controller. Jako uzupełnienie - katalogiem kontrolerów jest grails-app/controllers.
OdpowiedzUsuń