- pro vytvoření mockup controlleru použijte normálně Controller controller = new Controller(). Problémem je však v tom, že pokud jednou vytvořený controller přiřadíte parametry či zavoláte akci, zůstanou parametry i odpovědi z akce v controlleru. A to i přesto, že vytvoříte novou instanci znova pomocí new Controller(). Tento problém jsem eliminoval tak, že jsem controller vlastnoručně vyčistil od "bordelu".
/**
Problém by šel líp vyřešit pomocí setUp() a tearDown(), ale protože testuji celý proces, tak jsem tyto metody nepoužil.
* vyčistí controller pro další použití
* musí se použít pokaždé, když chceme
* vytvořit nový request
* @param controller
*/
private void clear(def controller) {
controller.response?.reset()
controller.params?.clear()
controller.modelAndView?.clear()
}
- pokud chcete otestovat vrácený model z controlleru, lze použít konstrukci controller.modelAndView.model.nazevModelu. Avšak konstrukce
[voucherInstance: voucherInstance, diagnosis: diagnosis, insuranceCompanyList: insuranceCompanyList]
nevrací žádný model (nevím, co přesně to vrací...). Stejně je to s konstrukcírender(template: "detail", model: [voucherInstance: voucherInstance], status: 200) .
Zde to vrací vygenerovaný HTML snippet. Model vrací tato konstrukce:render view: "edit", model: [voucherInstance: voucherInstance, diagnosis: diagnosis, insuranceCompanyList: insuranceCompanyList]
- používám IntelliJ jako IDE pro vývoj v Grails. Při testování jsem narazil na problém při debugování v controlleru, který používá metodu s closure.
// akce v controlleru
Uvnitř closure, který předávám v metodě edit(), se zpracování nezastaví, i když tam hodím breakpoint.
def edit() {
checkVoucherById(params.id) {voucherInstance ->
// tady nemůžu vůbec stopnout zpracování
render view: "edit", model: [voucherInstance: voucherInstance]
}
}
// tohle je metoda, která bere jako parametr closure
private checkVoucherById(id, closure) {
Voucher voucherInstance = null
try {
id = id as long
voucherInstance = voucherService.get(id)
closure(voucherInstance)
} catch (Throwable t) {
// nějaká jiná akce
}
} - při testování controlleru lze přidat parametry do requestu podobně jako je to při skutečném provozu. V dokumentaci je popsán tento způsob:
def controller = new AuthenticationController()
To však lze provést i pomocí Map parametru.
controller.params.login = "marcpalmer"
controller.params.password = "secret"
controller.params.passwordConfirm = "secret"
controller.signup()def controller = new AuthenticationController()
controller.request.parameters = [login: "marcpalmer", password: "secret", passwordConfirm: "secret"]
controller.signup()
-
Odlišný počet parametrů při použití controller.request.parameters a
controller.params. Mám-li jako parametr něco takového:
[
a použiji controller.request.parameters jako pro přiřazení parametrů do controlleru, v controlleru pak mám o 1 parametr víc. Tím je mapa vytvořená z visitList[0], což je stejné chování jako controller za běžného provozu. Použiji-li však pro přiřazení parametrů konstrukci controller.params jako v manuálu, tak mapa chybí.
"visitListSize": 1.toString(), "visitList[0].date_day": 1.toString(), "visitList[0].date_month": 1.toString(),
"visitList[0].date": Date.parse("dd-MM-yyyy", "01-01-2012").toString(), "visitList[0].date_year": 2012.toString()
]
Blog určený spíše pro osobní použití. Zaznamenávám sem problémy, se kterými se potýkám v práci a které mi trvaly trošku déle, než jsem je vyřešil. Tak abych příště dělal rychleji :) Nyní také o Austrálii.
středa 30. května 2012
Postřehy z integračního testování v Grails
neděle 27. května 2012
Grails - jak jsou naprogramovány mapping v doménových třídách
Closure (česky uzávěry) umožňují uvnitř nich volat metody. Příklad:
class SomeClass {
void callClosure() {
def dog = {
woof() // volání metody void woof() z closure
}
dog() // zavolá closure
}
void woof() {
println "Woof!"
}
}
Každý closure má obsažené 3 různé atributy: Owner, Delegate a this. this odkazuje na instanci třídy, kde je closure definován. Owner obsahuje odkaz na instanci, kde je obsažen closure. Ve většině případů je tedy this stejný jako owner. Výjimkou je třeba tento případ (z knihy Programming Groovy):
examiningClosure() {Výstup:
println "In First Closure:"
println "class is " + getClass().name
println "this is " + this + ", super:" + this.getClass().superclass.name // this = owner
println "owner is " + owner + ", super:" + owner.getClass().superclass.name
println "delegate is " + delegate +
", super:" + delegate.getClass().superclass.name
examiningClosure() { // closure uvnitř closure
println "In Closure within the First Closure:"
println "class is " + getClass().name
println "this is " + this + ", super:" + this.getClass().superclass.name // this != owner
println "owner is " + owner + ", super:" + owner.getClass().superclass.name
println "delegate is " + delegate +
", super:" + delegate.getClass().superclass.name
}
}
In First Closure:
class is ThisOwnerDelegate$_run_closure1
this is ThisOwnerDelegate@55e6cb2a, super:groovy.lang.Script
owner is ThisOwnerDelegate@55e6cb2a, super:groovy.lang.Script
delegate is ThisOwnerDelegate@55e6cb2a, super:groovy.lang.Script
In Closure within the First Closure:
class is ThisOwnerDelegate$_run_closure1_closure2
this is ThisOwnerDelegate@55e6cb2a, super:groovy.lang.Script
owner is ThisOwnerDelegate$_run_closure1@15c330aa, super:groovy.lang.Closure
delegate is ThisOwnerDelegate$_run_closure1@15c330aa, super:groovy.lang.Closure
Atribut Delegate v closure je ten, který právě umožňuje dělat kouzla jako v static mapping = {}. Closure totiž umožňuje nastavit atribut Delegate na jiný objekt pomocí setDelegate(Object delegate) a pak volat jejich metody. V praxi to znamená toto:
Doménová třída s defaultním řazením entit podle dne:
class Attendance {V doménové třídě však žádná metoda sort defaultně neexistuje. Avšak třída je platná a funkční. Pokud použijete debugger a zastavíte zpracování v closure mappings, uvidíte následující:
Date day
static mapping = {
sort("day")
}
}
this = class wellness.AttendanceKdyž si otevřete třídu HibernateMappingBuilder, nacházejí se tam metody, které se používají v mapping, jako je výše zmíněný sort, cache, table, version apod. To je celé tajemství.
owner = class wellness.Attendance
delegate = org.codehaus.groovy.grails.orm.hibernate.cfg.HibernateMappingBuilder@3321875 // ha, to je on
P.S. Další otázkou je, kde je nastaven setDelegate() v doménových třídách? To jsem se ještě nedočetl (ale myslím, že je to udělané pomocí MOP). O tom napíšu, až budu vědět na stopro nebo si to někde přečtu.
Grails - URL mapování pro celý package
Nakonec jsem objevil článek, který popisuje triky s URL mapováním v Grailsu. Trošku jsem to změnil a řešení je na světě:
for (controller in AppCtx.grailsApplication.controllerClasses) { // AppCtx - viz článekOdteď všechny controllery, které jsou v balíku "admin", budou mít URL namapovány jako "/admin/${controller}/${action}", a zbytek klasicky jako "/${controller}/${action}.
def cName = controller.logicalPropertyName
def packageName = controller.packageName
if (packageName.contains(".admin") || packageName.contains(".springsecurity")) {
"/admin/${cName}/$action?/$id?"(controller: cName) {
constraints {
}
}
} else {
"/${cName}/$action?/$id?"(controller: cName) {
constraints {
// apply constraints here
}
}
}
}
středa 16. května 2012
getTotalCount() metoda
A k tomu slouží právě metoda getTotalCount(). Metoda createCriteria().list(max: params.max, offset: params.offset) totiž vrací instanci třídy PagedResultList, který toto umožňuje. Jsem si toho ani nevšim a už jsem se chtěl zeptat na StackOverflow, ale byl jsem včas upozorněn a v dokumentaci je to hned na začátku popisu metody createCritera(). RTFM.
úterý 15. května 2012
Metoda inject() v Groovy
Příklad:
Chci v jqGrid použít dropwdown menu pro filtrování položek. Abych udělal toto dropdown menu, potřebuji dostat řetězec vypadající asi takto:
:Vše;NEW:Nová;SENT:Odeslaná;CLOSED:Uzavřenákde mám vždy dvojici hodnotu option value a text, který se zobrazuje. Obě tyto hodnoty můžu získat z enumerační třídy.
enum Status {Jeden způsob, jakým mohu získat předchozí řetězec, je tento:
NEW("Nová"),
SENT("Odeslaná"),
CLOSED("Uzavřená")
String czech
Status(String czech) {
this.czech = czech
}
}
var statusEnumsStr = "${protetika.Invoice.Status.values().collect { it.name() + ":" + it.czech}.join(";")}";Metoda values() vrací Collection<Status> se všemi enum hodnoty, který pak přeměním na řetězec složený z jména hodnoty (metoda name()) a českého názvu hodnoty, a "posbírám" tyto řetězce (metoda collect()) do nové kolekce. Hodnoty z této kolekce pak spojím do jednoho řetězce pomocí ";". Teď už mám řetězec:
statusEnumsStr = ":Vše;"+ statusEnumsStr;
NEW:Nová;SENT:Odeslaná;CLOSED:UzavřenáZbytek je jasné.
Teď to samé udělám pomocí metody inject():
var statusEnumsStr = "${protetika.Invoice.Status.values().inject(":Vše") {initVal, colVal -> initVal + ";" + colVal.name() + ":" + colVal.czech }}";Metoda inject bere 2 parametry: inicializační parametr a closure. Funguje tak, že pro první prvek v kolekci provede to, co je v closure, a výsledek se pak stane inicializační hodnotou pro další prvek. Lepší znázornění:
- naše kolekce [name: "NEW", czech:"Nová"; name: "SENT", czech: "Odeslaná"; name: "CLOSED", czech: "Uzavřená"]
- inicializační hodnota je ":Vše" a první prvek v kolekci je name: "NEW", czech:"Nová"
- V closure se provede ":Vše" + ";" + "NEW" + ":" + "Nová" a tato hodnota se vrací jako další inicializační hodnota
- Pro druhý prvek kolekce name: "SENT", czech: "Odeslaná" je pak k dispozici inic. hodnota :Vše;NEW:Nová;
Kroky se pak opakují.
sobota 12. května 2012
Vyřazení duplikátních výsledků z dtb
NejakaEntita.createCriteria().list() {
resultTransformer Criteria.DISTINCT_ROOT_ENTITY // this is it
}
Good to know. Význam té konstrukce jsem zatím nezkoumal, na to snad bude čas někdy jindy. Hlavní je, že funguje.
čtvrtek 10. května 2012
Moje první MySQL procedura
create procedure generaterc()Poznámka pod čarou:
begin
declare _rc varchar(255); --tady jsem měl chybu
declare _id int;
set _id = 1;
while _id < ((select count(*) from patient) + 1) do
begin
set _rc = cast(FLOOR(1000000000 + (RAND() * 8999999999)) AS char);
if not exists(select * from patient where patient.rc = _rc) then
update patient set rc=_rc where id=_id;
set _id=_id+1;
end if;
end;
end while;
end
Proměnné, které chceme pak dávat jako hodnotu do sloupců databáze, by měly mít stejný datový typ jako ten sloupec. Měl jsem v tabulce sloupec rc, který byl typu varchar(255). Proměnnou jsem však deklaroval jako declare _rc char; a to způsobilo nekompatibilní dat. typ. http://stackoverflow.com/questions/10524573/mysql-procedure-random-number-generating
Zjištění:
V databázi s 82 618 řádky funkce vůbec nedoběhne do konce, a to jsem ji nechal běžet přes 30 min. Nakonec jsem ty čísla náhodně negeneroval :)
středa 9. května 2012
PDF renderování a čeština
V projektu používáme plugin Grails Rendering pro generování PDF souboru. Používání bylo jednoduché, jenže zase české znaky se nezobrazovaly. Bylo potřeba přidat správný font.
@font-face {Takhle se vše zobrazovalo správně. Problém byl ale v tom, že odkaz na font byl funkční jen pro Windows. OK, tak prostě se ten soubor s fontem zkopíroval do web-app/font složky (složka s HTML a CSS a obrázky, která se nekompiluje). Komplikací poté ale bylo, že nešlo na ten soubor odkazovat:
src: url(file:c:/windows/fonts/arialuni.ttf);
-fs-pdf-font-embed: embed;
-fs-pdf-font-encoding: Identity-H;
}
@font-face {
src: url(file:${resource(dir:'font', file: 'ARIALUNI.TTF')});
-fs-pdf-font-embed: embed;
-fs-pdf-font-encoding: Identity-H;
}
A hned NullPointerException. Naštěstí jsem pak našel na StackOverlow, jak to někdo také řešil. A také jsem přehlídl důležitou větu v manuálu (RTFM): All links to resources (e.g. images, css) must be accessible by the application . This is due to the linked resources being accessed by application and not a browser. Takže řešením bylo nějak takhle (na soubor se muselo odkazovat jako na soubor z PC, ne přes URL):
@font-face {
src: url(file:${czechFontPath});
-fs-pdf-font-embed: embed;
-fs-pdf-font-encoding: Identity-H;
}
úterý 1. května 2012
DBF soubor s českými znaky
České čárky a háčky,to je občas velké zlo v kódování. A ještě větší zlo je,když toto kódování nevíte a ani neumíte zjistit. V současném projektu je 1 z úkolů migrace databáze. Jenže databáze starého systému,který je starý a k tomu ještě desktopový, že jsem tu databázi viděl poprvé (slyšel podruhé). Jednalo se o databázi foxpro a hromada souborů s koncovkou .dbf.
Myslel jsem, že vše proběhne bez problému. Jako první jsem stáhl z netu nějaké programy pro zobrazování,ale bohužel žádná neuměla české znaky přečíst (např. č). Pak jsem koukal i přímo po java knihovnách. Nejlepší byl tento: http://www.hxtt.com/orderdbf.html, akorát stál prachy,takže nic pro studenty. Trial verze umožňovala zobrazit jen 1000 prvních řádků... Ale plusem byl ten,že jsem konečně zjistil kódování dbf souboru (Cp1250).
Po delším hledání jsem narazil na program od českých autorů: http://www.pspad.com/cz/dbfview.htm. Ten funguje skvěle,doporučuji. Migrace samotné provedeme konverzí na csv a pak načtení csv do databáze.