Ajax i wyszukiwanie w grails

Kolejny rozdział książki Definitive guide to grails zajmuje się ajaxem w grails oraz wspomina o serwisach i pluginie searchable. Wobec tego można tą notkę połączyć z wpisem o autouzupełnianiu w grails.

Grails domyślnie wspiera prototype i Scriptaculous, ale poprzez system pluginów łatwo można dodać kolejne biblioteki ajaxowe.

Import biblioteki ajaxowej odbywa się poprzez dodanie w sekcji head strony gsp

<g:javascript library="prototype" />

Tagi ajaxowe posiadają atrybut before i after, i wtedy akcje w tym atrybucie są wywołane bezpośrednio przed i bezpośrednio po akcji ajaxowej (nawet jeśli pojawi się błąd w js).

  • tworzy kotwicę bez przeładowni strony.
  • rozszerzenie zwykłego forma na formy ajaxowe.
  • pole tekstowe, które wysyła swoją wartość do serwera po zmianie tej wartości.

W można przechwytywać zdarzenia (handle events):

  • onSuccess
  • onFailure
  • onLoaded
  • onComplete
  • onERROR_CODE

Serwisy są użyteczne przy scentralizowaniu logiki biznesowej, która jest dzielona przez różne warstwy. Domyślnie są one transakcyjne (statyczna zmienna – transactional).

W pliku grails-app/conf/spring/resources.groovy można ustawić cachewoanie poprzez Ehcache np

beans = {
  albumArtCache(org.springframework.cache.ehcache.EhCacheFactoryBean) {
    timeToLive = 300
  }
}

Do wyszukiwania warto używać pluginu searchable. Aby dodać opcję wyszukiwania należy w klasie domenowej dodać pole

static searchable = true

to jest wyszukiwanie dla wszystkich pól w danej domenie, aby ograniczyć do wybranych należy ustawić to np:

static searchable = [only: ['name']]

Ten plugin rozszerza klasy domenowe o kilka metod.

Autocomplete w grails

Dziś trochę praktyki w poznawaniu grails a mianowicie jak zrobić autouzupełnianie w formularzach grails. Jako, że funkcja automatycznego uzupełniania jest bardzo przydatna i do tego lubię ją najbardziej z wszelkich ajaxowych „udogodnień”, to przedstawię jak ją zaimplementować w grails.

Będę korzystał z pluginu grails-ui. Kilka screencastów pokazujących użycie tego plugina jest dostępnych na tej stronie.

Po zainstalowaniu pluginu, należy otworzyć stronę gsp na której chcemy dodać autouzupełnianie i w sekcji head wpisujemy:

<gui:resources components="autoComplete"/>

oraz tworzymy formatkę, w której będzie autocomplete

 <g:form action="searchUser">
    <gui:autoComplete id="searchUserAC" 
                      controller="user"
                      action="usersAutoComplete"
                      resultName='users'
                      />
    <g:submitButton name='searchUser' value="SubmittAction"/>
    </g:form>

Autouzupełnianie będzie odnosiło się do kontrolera User i wywoływało akcję userAutoComplete.

A akcja userAutoComplete wygląda następująco:

import grails.converters.JSON

//some code

    def usersAutoComplete = {
        def users = User.findAllByLoginLike("${params.query}%")
        users = users.collect{
            [id:it.id, name:login]
        }
        
        def jsonUser = [
            users: users
        ]
        render jsonUser as JSON
    }

Wyświetlanie podpowiedzi odbywa się przy pomocy JSONa, gdzie wybrany element jest parą id(nie wyświetlane na formatce) i login(pokazywane). Oczywiście można dodać różne parametry do autouzupełniania jak np opóźnienie, minimalną długość wpisanego ciągu.

Internacjonalizacja w grails

Kolejny krótki rozdział książki Definitive guide to grails. Tym razem autorzy poruszają kwestię lokalizacji i jest to bardzo proste, jak wszystko do tej pory w grails.

Pliki z messagami lokalizacyjnymi znajdują się w grails-app/i18n/ (w netbeansie grails-app/MessageBundles/). To są zwykłe pliki properties z parą klucz-wartość.

Pobieranie wartości w kodzie odbywa się przy udziale klasy java.util.ResourceBundle.
Użycie tej klasy w kodzie java i groovy wygląda następująco:

// JavaMessages.java
import java.util.ResourceBundle;
public class JavaMessages {
  public static void main(String[] args) {
    ResourceBundle bundle = ResourceBundle.getBundle("messages");
    String appName = bundle.getString("app.name");
    System.out.println("application name is " + appName);
  }
}
// GroovyMessages.groovy
def messages = ResourceBundle.getBundle('messages')
def appName = messages.getString('app.name')
println "application name is ${appName}"

Strony gsp posiadają tag odpowiedzialny za lokalizację

<g:message code="gtunes.welcome"/>

gdzie code jest kluczem z pliku properties. Domyślnie grails korzysta z pliku properties, który reprezentuje lokalizację clienta (client’s locale)

Informację o języku można dodać do adresu a nie używać parametrów. Tutaj można znaleźć trochę o mapowaniu url. http://localhost:8080/gTunes/?lang=es można zastąpić http://localhost:8080/
gTunes/en/
poprzez mapowanie w pliku UrlMappings.groovy

static mappings = {
  "/store/$lang"(controller:'store')
}

Grails zapewnia również parametryzowanie messagów, przy pomocy klasy java.text.MessageFormat
Aby przekazać sparametryzowaną wartość w pliku properties należy w klamrach umieścić kolejny numer parametru dla danej wartości:

grail.message=To jest {0} wiadomość

Użycie tego w javie może wyglądać tak:

// JavaMessages.java
import java.util.ResourceBundle;
import java.text.MessageFormat;
public class JavaMessages {
  public static void main(String[] args) {
    ResourceBundle bundle = ResourceBundle.getBundle("messages");
    String countMessage = bundle.getString("grail.message");
    String message = MessageFormat.format(countMessage, 97);
    System.out.println("message: " + message);
  }
}

a w groovy

import java.text.MessageFormat
def bundle = ResourceBundle.getBundle('messages')
def countMessage = bundle.getString('grail.message')
def message = MessageFormat.format(countMessage, 97)
println "message: ${message}"

Również w tagu można przekazać parametry poprzez atrybut args=[].

<g:message code="grail.message"
args="[session.user.message?.size() ?: 0]"/>

Sparametryzowanych wiadomości, można użyć podczas wyświetlania komunikatów o walidacji.

Czasem z daną wiadomością należy wykonać coś innego niż pokazanie na stronie gsp. Grails również takie zachowanie wspiera poprzez beana messageSource, który jest instancją

Przyjazne linki i grails

Dziś trochę krótszy rozdział książki Definitive guide to grails ale jednak wbrew pozorom z dużą ilością ciekawych informacji. Nie tylko programiście mają łatwiej z grails ale także seo i pozycjonowanie może mieć pewne korzyści przy mapowaniu url i tworzeniu przyjaznych linków.

Domyślne mapowanie url w grails jest kolejnym przykładem konwencji ponad konfigurację. Można zastosować własne mapowanie, które posiada wiele opcji ale nie wymaga żadnych xml tylko troszkę kodu w groovy.

Domyślne mapowanie składa się z obowiązkowego kontrolera, opcjonalnej akcji (jak jej nie ma to jest wywoływana domyślna) i opcjonalnego id obiektu.
/controller/action/id

Definicja mapowania znajduje się w grails-app/conf/UrlMappings.groovy
Do mapowania można dodawać kolejne opcjonalne elementy, które muszą się pojawić na końcu wzorca. W domyślnym mapowaniu każdy element jest zmienną, która zawiera prefix – $. Można dodać statyczny tekst, który będzie częścią url:

static mappings = {
  "/showAlbum/$controller/$action?/$id?" {
    constraints {
      // apply constraints here
    }
  }
}

W tym przypadku url mapuje /showAlbum/album/list. Bez /showAlbum to będzie niepoprawny adres.

Z url można usunąć nazwę kontrolera i akcji mapując je na specyficzną nazwę:

static mappings = {
  "/showAlbum/$id" {
    controller = 'album'
    action = 'show'
  }
}

Teraz pokazywanie danego albumu będzie posiadało url: /showAlbum/jakieśId

Grails dostarcza także inną składnię tego samego mapowania

static mappings = {
  "/showAlbum/$id"(controller:'album', action:'show')
}

Grails zapewnia obsługę standardowych parametrów http, np url /showArtist?artistName=Rush może zostać obsłużony jeśli mapowanie będzie zdefiniowane

static mappings = {
"/showArtist"(controller:'artist', action:'show')
}

Nie trzeba nigdzie tutaj dodawać dodatkowych parametrów.

W taki sposób można dodać wiele parametrów do url ale przy większej ilości staje się on coraz bardziej brzydki i nieczytelny. Grails zapewnia, że zamiast takiego mapowania url z parametrami /showArtist?artistName=Rush można wartość dodać jako część url /showArtist/Rush, wystarczy tylko zmapować ten parametr:

static mappings = {
  "/showArtist/$artistName"(controller:'artist', action:'show')
}

Taki parametr można przekazać w kontrolerze za pomocą params.parametrName

def artist = Artist.findByName(params.artistName)

Można dodawać dodatkowe parametry, które nie będą pokazane w url:

static mappings = {
  "/showArtist/$artistName"(controller:'artist', action:'show') {
    format = 'simple'
  }
}

Tutaj istnieje dodatkowy parametr format o wartości simple.

W url można zmapować wzorzec do określonego widoku.

static mappings = {
  "/"(view:'/welcome')
}

Tutaj jest mapowany root aplikacji (/) do widoku grails-app/views/welcome.gsp

Także można zmapować do widoku w konkretnym kontrolerze

static mappings = {
  "/find"(view:'query', controller:'search')
}

Tutaj url /find jest zmapowany do widoku query w kontrolerze search. W takim przypadku nie jest wywoływana żadna akcja kontrolera, tylko zostaje określona lokalizacja odpowiedniej strony gsp.

W mapowaniu url można używać ograniczeń podobnych a właściwe prawie takich samych (drobna różnica o czym za chwilę) jak ograniczenia w klasach domenowych.

static mappings = {
  "/grailsblogs/$year/$month/$day/$entry_name?" {
    controller = 'blog'
    action = 'display'
    constraints {
      year matches: /[0-9]{4}/
      month matches: /[0-9]{2}/
      day matches: /[0-9]{2}/
    }
  }
}

Parametr year musi być czterocyfrowym numerem, month i day dwu. Ograniczenia muszą się pojawić w takiej kolejności jak przekazane parametry.

Różnica między ograniczeniem w klasie domenowej a w mapowaniu url jest taka, że w klasie domenowej jest to pole którego przypisaną wartością jest domknięcie a w mapowaniu jest to metoda, która jako parametr przekazuje domknięcie. Czyli w constraincie w mapowaniu url nie jest potrzebny znak równości (właściwie przypisania =) a przy klasie domenowej on musi być.

W mapowaniu url w grails dozwolone są wildcardy, ich symbolem jest gwiazdka(*).

static mappings = {
  "/images/*.jpg"(controller:'image')
}

To mapowanie odnosi się do dowolnego pliku w katalogu image, który kończy się na jpg. Jeśli jest potrzeba aby odnosiło się do dowolnej ilości podkatalogów należy użyć podwójnej gwiazdki (**)

static mappings = {
  "/images/**.jpg"(controller:'image')
}

Można mapować do akcji HTTP

static mappings = {
  "/artist/$artistName" {
    controller = 'artist'
    action = [GET: 'show',
                PUT: 'update',
                POST: 'save',
                DELETE: 'delete']
  }
}

A także do kodów odpowiedzi HTTP

static mappings = {
  "404"(controller:'store')
}

Tag również potrafi obsługiwać mapowanie url. O ile nic nie zostanie zmienione w pliku UrlMappings.groovy to jego użycie w formie:

<g:link action='show'
  controller='artist'
  id="${artist.id}">${artist.name}</g:link>

utworzy link /artist/show/jakieśId
A jeśli chcemy aby ten tag obsługiwał przyjazne linki, które zdefiniujemy w pliku np w ten sposób:

static mappings = {
"/showArtist/$artistName"(controller:'artist', action:'show')
}

to wywołanie tagu nie wiele się różni od poprzedniego wywołania:

<g:link action='show'
  controller='artist'
  params="[artistName:${artist.name.replaceAll(' ', '_')}">${artist.name}
</g:link>

i tutaj do mapy parametrów jest przekazywany parametr artistName, na którego wartości została dokonana operacja zamiany stringów.

Można tworzyć własne klasy mapujące, które znajdują się w grails-app/conf/ i kończą się UrlMappings (konwencja). Ich struktura jest taka sama jak domyślnego pliku mapowania.

Chyba to nie będzie niespodzianką ale grails pozwala testować mapowanie url. Podstawową klasą jest grails.test.GrailsUrlMappingsTestCase a testy są integracyjne a nie jednostkowe jak dotychczas.

Widok w grails

Jako, że grails jest frameworkiem mvc i opisałem model(klasy domenowe ), controller(kontrolery ), to najwyższa pora na view. Wpis oparty na książce Definitive guide to grails.

Dlaczego warto używać gsp:

  • Aby wykorzystać zalety groovy, technologia widoku musi mieć wiedzę o groovy
  • Lepszy język wyrażeń (GPath, przeciążone operatory)
  • GString, wyrażenia regularne, operacje na listach i mapach

Atrybuty zasięgu takie jak w kontrolerach.
out – Writer w responsie

Gsp jest podobne do jsp i wiele obiektów z jsp jest dostępnych w gsp. GSP jest bardziej zwinny (agile) niż jsp.

Dyrektywy <%@ %>

<%@ page contentType="text/xml; charset=UTF-8" %>

Także importy w dyrektywach są rzadziej używane z racji tego groovy domyślnie importuje wiele pakietów.

Skryptlety <% %>, dla zgodności z jsp, należy używać tagów gsp.

Wyrażenia <%= %>
<%= "print me!" %> równoznaczne z <% out << "print me!" %>

Strony gsp można traktować jako jeden wielki GString.

<c:out value="${album.title}" />

i to samo w gsp

${album.title}

Strony gsp posiadają swoje własne wbudowane tagi, których nie trzeba importować. Przestrzenią nazw dla nich jest g. Można importować tagi jsp.

Wbudowane tagi gsp.
Tagi gsp są domknięciami.
Ustawienie wartości tagiem g:set

<g:set var="albumTitle" value="${album.title}" />

var to zmienna a value przypisana wartość. Można dodać scope dla zakresu, np

<g:set scope="session" var="user" value="${user}" />

Tagi z logiką<g:if>, <g:elseif> i <g:else> odpowiadają za sprawdzenie warunków. Tagi <g:if> i <g:elseif> posiadają atrybut test, który może być w języku wyrażeń (expression language) i w nim jest warunek.

<g:if test="${album?.year < 1980 && album?.genre == 'Rock'}">
  Classic rock
</g:if>
<g:elseif test="${album?.year >= 1980 && album?.genre == 'Rock'}">
 Modern Rock
</g:elseif>
<g:else>
  Other
</g:else>

Konstrukcja album?.year zostanie wywołana wtedy gdy album jest różny od null. Null w stronach gsp jest wyświetlany jako pusty string.

Tagi iterujące

<g:each in="${album.songs?}">
  <span class="tag">${it.title}</span>
</g:each>

Jeśli zostanie użyty ? w wyrażeniu to jeśli będzie null nie będzie iterowane. Można nadać nazwę zmiennej aby nie używać domyślnej nazwy – it. Dokonuje się tego poprzez var.

<g:while> jak pętla while

<g:set var="i" expr="${album.songs?.size()}" />
<g:while test="${i > 0}">
<g:set var="i" expr="${i-1}" />
</g:while>

odpowiednik tego w groovy to

while(i > 0) i=i-1

Dodatkowo tagi iterujące wspierają operacje na listach takie jak collect, findAll zapewniają obsługę GPath.

<ol>
<g:collect in="${albums}" expr="${it.title}">
	<li>${it}</li>
</g:collect></ol>

jest równoznaczne z

<ol>
<g:each in="${albums.title}" >
	<li>${it}</li>
</g:each></ol>

przykład z findAll

<g:findAll in="${albums}" expr="${it.songs?.title.contains('Love')}">
	<li>
${it.title}</li>
</g:findAll>

Dynamiczne tagi

Można ich używać tak samo jak tagów wbudowanych dodatkowo mogą być wywołane jako metody w skryptletach lub wyrażeniach GString.

<!-- With a regular tag -->
<a href="<g:createLink action="list" />">A dynamic link</a>
<!-- As a method call -->
<a href="${createLink(action:'list')}">A dynamic link</a>

Ciało taga może być przekazane jako ostatni argument w metodzie

Tagi linkujące:
<g:link>  linkuje do jakiś akcji, wspiera atrybuty
-controller – nazwa kontrolera
-action – nazwa akcji
-id – id doklejane do końca URI
-params – parametry przekazywane jako mapa.

Co najmniej jeden z atrybutów controller/action jest wymagany. Jeśli podany jest tylko controller bez action zostanie wywołana domyślna akcja danego kontrolera. Jeśli jest action bez kontrolera to zostanie wywołana akcja w obecnie wykonywanym kontrolerze. wspiera również wszystkie atrybuty htmlowego tagu anchor.

<g:link controller="album" action="list">List Albums</g:link>
<g:link action="show" id="1">Show album with id 1</g:link>

przykład z przekazaniem parametrów

<g:link controller="album"
action="list"
params="&#91;max:10,order:'title'&#93;">pokazuje pierwsze 10 sortowane po tytule</g:link>

inne tagi linkujące to <g:createLink> podobne działanie jak tylko, że tworzony jest link a nie kotwica.

<g:createLinkTo> można linkować do zasobów w kontekście aplikacji. Najczęściej linkowanie do obrazków, arkuszy styli.

	<link rel="stylesheet" href="${createLinkTo(dir:'css',file:'main.css')}"></link>

Formularze

<g:form action="register" name="registerForm">
</g:form>

<g:textField> równoznaczny z <input type=”text”>.
checkbox

<g:checkBox name="aBooleanValue" value="${true}" />

Radio są grupowane i muszą posiadać taką samą nazwę aby były połączone.


<g:radio name="myGroup" value="1" checked="${someValue == 1}" /> Radio 1

<g:radio name="myGroup" value="2" checked="${someValue == 2}" /> Radio 2

select

<g:select name="genre" from="${&#91;'Rock', 'Blues', 'Jazz'&#93;}"
value="${album.genre}" />

posiada 2 ciekawe atrybuty optionKey i optionValue co pokazuje przykład

<g:select name="album.id" from="${Album.list()}"
optionKey="id" optionValue="title"/>

jest równoznaczne z

<select name="album.id">
<option value="1">Undertow</option>
...
</select>

Podobne tagi to <g:currencySelect>, <g:localeSelect> i <g:timeZoneSelect>

daty

<g:datePicker name="myDate" value="${new Date()}" precision="day" />

gdzie precision określa precyzję daty. Bez tego atrybutu pokazywane jest ze szczegółowością co do sekundy. dataPicker jest instancją java.util.Date.

<g:hasErrors> czy są błędy atrybuty, które mogą się pojawić to bean, field, model.
<g:eachError> iterowanie po błędach.

Domyślne komunikaty dla błędów znajdują się w grails-app/i18n/message.properties

Można wyświetlać błędy jako listę.

<g:renderErrors bean="${album}" as="list" />

Stronicowanie odbywa się za pomocą tagu , domyślna ilość to 10 rekordów na stronę, obowiązkowym polem jest total, opis atrybutów tego tagu.

Szablony gsp (gsp templates) są specjalnym plikiem gsp zawierającym tylko kawałek strony, który może być wykorzystany na wielu stronach. W szablonach mogą pojawić się wszystkie rzeczy jak w zwykłych stronach gsp. Specyficzną cechą szablonów jest ich nazwa, która zaczyna się podkreśleniem(_). Przykładowa ścieżka to grails-app/views/album/_albumList.gsp

Aby dołączyć szablon do strony należy użyć tagu render z atrybutem template, podczas jego użycia znika z niego podkreślenie w nazwie

<g:render template="/album/albumList"/>

Aby przekazać dane do szablonu należy użyć atrybuty model, który jest mapą

<g:render template="/album/albumList"
model="&#91;albums: top5Albums&#93;"/>

Mapa jest zdefiniowana w akcji danego kontrolera

[top5Albums: Album.list(max:5, sort:"dateCreated", order:"desc")]

Tworzenia własnych tagów gsp.
Można podzielić na 3 kategorie:

  • proste tagi (simple tags) – tagi posiadają atrybuty bez ciała
  • tagi logiczne (logical tags) – ciało jest wykonywane pod pewnym warunkiem
  • tagi iteracyjne (iterative tags) – ciało jest wykonywane conajmniej jeden raz

Tagi definiuje się w klasie, która kończy się TagLib i znajduje się w grails-app/taglib. Aby utworzyć taką klasę należy użyć polecenia grails create-taglib. Własne tagi (custom tags) pełnią role helperów dla widoku.
Tag jest domknięciem, które posiada 2 parametry: atrybuty tagu jako mapa i ciało jako domknięcie.

def repeat = { attrs, body ->
  attrs.times?.toInteger().times { n ->
    body(n)
  }
}

a użycie tego to

<g:repeat times="3">
  Hello number ${it}
</g:repeat>

Domyślną przestrzenią nazw dla własnych tagów jest g, można ustawić własną poprzez

static namespace = 'someNamespace'

Własne tagi można testować. Podstawowa klasa to grails.test.TagLibUnitTestCase

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.

Klasy domenowe w grails

Kolejny rozdział książki Definitive guide to grails dotyczy klas domenowych. A właściwie to przez większość rozdziału są opisane zachowanie ormowe a właściwe GORMowe. Jak ktoś miał styczność z hibernate to wszystko powinno być proste.

Klasy domenowe zawierają w sobie domyślnie pola id i version.
Ograniczenia dla pól definiuje się poprzez statyczny constraints, którego wartością jest domknięcie

static constraints = { }

zdefiniowane ograniczenia można znaleźć tutaj.

Podczas metody save(), dokonywana jest walidacja i jeśli ona nie przejdzie to jest zwracany odpowiedni komunikat. Klasy domenowe mają pole errors, które jest instancją org.springframework.validation.Errors

Wszystkie klasy domenowe posiadają metodę clearError(), która czyści komunikaty błędów pozostałe po wcześniejszej walidacji oraz validate(), dzięki której można dokonać walidacji „ręcznie”.
Można tworzyć własne walidatory np:

class User {
  static constraints = {
    password(unique:true, length:5..15, validator:{val, obj ->
      if(val?.equalsIgnoreCase(obj.firstName)) {
         return false
      }
    })
  }
}

gdzie obok standardowej walidacji jest walidacja użytkownika. W domknięciu jest sprawdzane czy wartość jest różna od innej wartości tego obiektu. Zamiast wartości można zwrócić błąd.
Ogólny schemat walidacji niestandardowej to:

static constraints = {
  field (validator: {closure})
}

Domyślnie wszystkie właściwości klasy są trwałe (persistant) i są zapisywane do bazy danych. Grails pozwala tworzyć właściwości przejściowe(transient), które nie są zapisywane do bazy poprzez static transients.

class Company {
String name
Integer numberOfEmployees
BigDecimal salary
static transients = ['salary']
}

Pole salary nie jest zapisywane w bazie (brak takiej kolumny).

Jeśli w klasie nie ma pola a jest metoda getPole, to grails traktuje to jakby było dane pole i jest ono persistant. W taki sam sposób można zrobić je transient jak pole.

Można mapować pola do bazy danych. Domyślnie nazwa kolumny jest „taka” sama(mogą być różnice podkreślenia, duże/małe litery). Można utworzyć własne mapowania przez

static mapping = {closure}

np

class Person {
 String firstName
 String lastName
 Integer age
 static mapping = {
   id column:'person_id'
   firstName column:'person_first_name'
   lastName column:'person_last_name'
   age column:'person_age'
   version false
 }
}

W danej tabeli nie będzie pola version. Domyślnie nazwa tabeli jest taka jak nazwa klasy i też można ją zmienić.

static mapping = {
table 'tableName
}

Relacje:
jeden-do-jednego dwukierunkowa(bez właściciela)

class Car {
Engine engine
}
class Engine {
Car car
}

jeden-do-jeden dwukierunkowa (z właścicielem). Klucz mapy jest jako właściwość.

class Car {
Engine engine
}
class Engine {
static belongsTo = [car:Car]
}

jeden-do-jeden jednokierunkowa

class Engine {
static belongsTo = Car
}

belongsTo zapewnia usuwanie kaskadowe(cascade delete). Jeśli zostanie usunięty Car to jednocześnie zostanie usunięty Engine.
jeden-do-wielu

class Artist {
String name
static hasMany = [albums:Album]
}
class Album {
String title
static belongsTo = [artist:Artist]
}

Relacje jeden-do-wielu mogą być jednokierunkowe jak i dwukierunkowe. Dla jednokierunkowych podaje się tylko klasę do której obiekt należy, przy dwukierunkowej tworzy się mapę.
hasMany musi być mapą, gdzie klucz jest właściwością dla danej klasy (java.util.Set). Jeśli potrzeba innego typu to należy go zadeklarować jawnie (Czasem będzie wymagana implementacja interfejsu Comparable – dla danych list).

class Album {
  static hasMany = [songs:Song]

  SortedSet songs
}

Jedna klasa może być w wielu relacjach jeden-do-wielu:

static hasMany = [albums:Album, instruments:Instrument]

Dziedziczenie
Są dwie możliwości mapowania klasy i jej podklas w tabeli.
Pierwsza i domyślna jedna tabela dla wszystkich klas (table per hierarchy). Tabela zawiera wszystkie pola z klasy głównej i podklas, dodatkowo pole class które określa jaka to klasa (krytyczne dla grails).
Druga osobna tabela dla każdej klasy (table per subclass). W klasie nadrzędnej należy ustawić:

static mapping = {
  tablePerHierarchy false
}

Można umieszczać 2 klasy w jednej tabeli (embeddeding object), użyteczne przy relacjach jeden-do-jeden, gdzie obiekty są w różnych tabelach i mają oddzielne id. Zagnieżdżenie powoduje, że wartości są w jednej tabeli.

class Car {
  String make
  String model
  Engine engine
  static embedded = ['engine']
}

W grails są testy jednostkowe i integracyjne.
W testach integracyjnych można testować metody dynamiczne, większy wycinek systemu, trwają one dłużej (między innymi kosz utworzenia środowiska). Testy jednostkowe są krótsze i testują konkretne zachowania „statyczne”, jako drobny wycinek. Przy tworzeniu klasy domenowej automatycznie jest tworzona klasa testu jednostkowego dla danej klasy.
Poleceniem test-app zostaną uruchomione wszystkie testy jednostkowe i integracyjne. Jeśli po tym poleceniu zostanie podana nazwa klasy (bez słowa test np tak jak nazwa klasy domenowej) to zostaną wykonane testy dla tej klasy. Podstawową klasą testową jest grails.test.GrailsUnitTestCase

%d blogerów lubi to: