Kontrolery w grails

Duża porcja wiedzy o kontrolerach na podstawie książki Definitive guide to grails.

Kontrolery w grails są odpowiedzialne za przechwytywanie requstów aplikacji i decydowanie co z nimi można zrobić:

  • wykonać inna akcję kontrolera (nie koniecznie tego samego)
  • wyświetlić widok
  • wyświetlić informację bezpośrednio w responsie

Klasy z kontrolerami znajdują się w katalogu controllers. Każda klasa musi się kończyć na Controller (konwencja). Kontrolery nie potrzebują rozszerzać jakiejś klas bądź implementować specyficznych interfejsów.

Akcje reprezentowane są jako pola do których dopisane jest domknięcie. W URLach dostęp jest przez konwencję, np /sample/action, gdzie sample to nazwa kontrolera (bez słowa Controller) a action to zdefiniowana akcja. W kontrolerze może być zdefiniowanych wiele akcji.

Jeśli nie jest podana żadna akcja, to jest wykonywana akcja domyślna:

  • jeśli kontroler posiada jedną akcję, to ona jest domyślna
  • jeśli kontroler posiada akcję index to ona jest domyślna
  • jeśli posiada właściwość defaultAction, to akcja przypisana do tego pola jest domyślna

Do każdego kontrolera jest wstrzyknięta właściwość log, która jest instancją org.apache.commons.logging.Log

Oprócz log do kontrolera są wstrzyknięte inne pola, jak między innymi HttpServletRequest, HttpServletResponse itd:

  • actionName – nazwa wykonywanej akcji
  • actionUri – URI do akcji
  • controllerName – nazwa kontrolera
  • controllerUri – URI kontrolera
  • flash – do pracy w zasiegu flash, równoznaczne z params tylko inny zasięg
  • log
  • params – mapa parametrów
  • request – HttpServletRequest
  • response – HttpServletResponse
  • session – HttpSession
  • servletContext – ServletContext

Dostęp do tych obiektów javie (za pomocą servletów)

request.getAttribute("myAttr");
request.setAttribute("myAttr", "myValue");

A w kontrolerach grails

request.myAttr
request.myAttr = "myValue"

Zasięgi (scopes):

  • request – obiekty utrzymują się tylko dla bieżącego żądania
  • flash – obiekty są dostępne dla obecnego i następnego żądania
  • session – są przetrzymywane do momentu aż sesja użytkownika zostanie unieważniona (ręcznie lub automatycznie)
  • servletContext – przetrzymywane przez cały czas życia aplikacji. W tym zasięgu obiekty nie są synchronizowane i należy dokonywać synchronizacji ręcznie, np
def index = {
  synchronized(servletContext) {
    def myValue = servletContext.myAttr
    servletContext.myAttr = "changed"
    render myValue
  }
}

Pola z formularzy są przekazywane do kontrolera i dostęp jest poprzez params

def userName = params.userName
log.info("User Name: ${userName}")

Poprzez metodę render można renderować tekst. Można dodać contentType:

render text:'<album>Revolver</album>', contentType:'text/xml'

Najczęściej używa się metody render do renderowia widoków i szablonów GSP.

Przekierowanie ządań odbywa się poprzez metodę redirect:

def first = {
  redirect(action: "second")
}

Metoda ta składa się z mapy, gdzie mogą występować różne klucze:

  • action – nazwa akcji do której następuje przekierowania
  • controller – nazwa kontrolera do którego następuje przekierowanie
  • id – id parametru do przekazania
  • params – mapa parametrów
  • uri
  • url

Domyślny widok dla kontrolera jest pobierany na podstawie akcji i nazwy kontrolera.
Można tworzyć widoki nie standardowe poprzez metodę render.

class SongController {
  def show = {
    render(view:"display",model:[ song: Song.get(params.id) ])
  }
}

widok znajduje się w grails-app/views/song/display.gsp, i zostaje przekazany model Song.
Widok może się znajdować w innym katalogu

render(view:"/common/song", model:[song: Song.get(params.id) ])

wskazuje na grails-app/views/common/song.gsp.
Renderowanie szablonów jest podobne

render(template:"/common/song", model:[song: Song.get(params.id) ] )

przez konwencję nazwy szablonów rozpoczynają się podkreśleniem(_). Szablon to mały wycinek kodu, który widoki mogą dołączać.

W konstruktorze można przekazać mapę parametrów, aby nie robić przypisania każdego parametru oddzielnie:

def album = new Album(params)

Dla istniejących obiektów można w zbliżony sposób przekazać parametry:

def album = Album.get(params.id)
album.properties = params

Walidacja przychodzących danych jest dwustopniowa. Najpierw jest sprawdzana zgodność typów. Jeśli pojawi się błąd obiektu nie można zapisać (read-only). Jeśli to przejdzie sprawdzane są walidatory.
Mechanizm walidacji opiera się na springowym pakiecie org. springframework.validation.

Z punktu widzenia kontrolera jeśli obiekt domenowy jest w nieprawidłowym stanie, należy w logice obsłużyć ten stan, np:

if(album.save()) {
  redirect(action: "show", id:album.id)
}
else {
  render(view: "edit", model:[album:album])
}

Można iterować po błędach obiektu:

object.errors.allErrors.each { println it.code }

Sprawdzić czy istnieją jakieś błędy:

object.hasErrors()

Rendedrowanie błędów w widoku to:

<g:renderErrors bean="${object}" />

Nazwy parametrów, przekazanych w params, są pobierane na podstawie atrybutu name w html, np

<input type="text" name="title" />

Jeśli, należy przekazać parametry do więcej niż jednego obiektu to należy w name dodać jakiś klucz (przestrzeń nazw).

<input type="text" name="album.title" />
<input type="text" name="artist.name" />

I pobranie odpowiednich parametrów to:

def album = new Album( params["album"] )
def artist = new Artist( params["artist"] )

Można zawężać przekazane parametry przy wykorzystaniu metody blindData:

bindData(album, params, [include:"title"])

Można tak zawężać do wielu obiektów, gdzie ostatnim argumentem jest klucz:

bindData(album, params, [include:"title"], "album")

CommandObject, jest to klasa, która ma właściwości klasy domenowej ale nie jest trwała (persistent).
Obiekty żądania definiuje się w katalogu contollers lub w tym samym pliku co kontroler.

Można ograniczyć wywołanie metody Http.POST(Http.GET itd) do konkretnych akcji:

class SomeController {
// action1 may be invoked via a POST
// action2 has no restrictions
// action3 may be invoked via a POST or DELETE
def allowedMethods = [action1:'POST', action3:['POST', 'DELETE']]
def action1 = { ... }
def action2 = { ... }
def action3 = { ... }
}

Jeśli metoda zostanie wywołana w niedozwolonym trybie zostanie rzucony błąd 405 – „method not allowed”

Formatka załączania plików:

<g:uploadForm action="upload">
<input type="file" name="myFile" />
<input type="submit" value="Upload! " />
</g:uploadForm>

obsługa tego:

def upload = {
  def file = request.getFile('myFile')
  // do something with the file
}

Grails automatycznie potrafi rozpoznać byte[] i odpowiednio połączyć z odpowiednią zmienną.

grails-app/views/layouts/main.gsp jest dostępne na każdej stronie.