E N C Y K L O P E D I E
Z O N E R
P R E S S
RUBY
kompendium znalostí pro začátečníky i profesionály
© Foto: Jiří Heller
H a l
F u l t o n
„Tato kniha, mimo jiné, skvěle vysvětluje metaprogramování, jeden z nejzajímavějších aspektů Ruby. Prvním vydáním této knihy bylo inspirováno mnoho raných myšlenek k Rails. Díky této knize se dostáváte na horskou dráhu mezi „Jak to můžu použít“ a „To je tak skvělé!“. Jakmile na tuto dráhu jednou nastoupíte, není už žádné cesty zpět.“ David Heinemeier Hansson, Tvůrce Ruby on Rails, Společník 37signals „Druhé vydání této knihy je vzrušující událostí pro všechny programátory v Ruby – a pro milovníky skvělé technické literatury obecně. Hal Fulton přináší poutavý a jasný styl s důrazem na dokonalé a úzkostlivě přesné vysvětlení Ruby. Zřetelně cítíte přítomnost učitele, který má obrovské množství znalostí, a jenž vám chce opravdu pomoci, abyste je měli také.“ David Alan Black, Autor Ruby for Rails „Je to vynikající zdroj informací o tom, jak Ruby funguje. I já, jako člověk, který s Ruby pracuje po několik let, jsem zde našel plno nových triků a technik. Tuto knihu je možné nejenom číst od začátku do konce, ale také ji používat jako referenční příručku, ke které můžete kdykoliv sáhnout a naučit se něco nového.“ Chet Hendrickson, Průkopník agilního softwaru „Často používám první vydání této knihy o Ruby, abych zjistil podrobnosti o tomto programovacím jazyku, protože pokrývá mnoho témat, která nemohu najít nikde jinde. Nové vydání je ještě komplexnější a bude ještě hodnotnější.“ Ron Jeffries, Autor a řečník „Ruby je báječný jazyk, ale někdy prostě potřebujete jen něco udělat. Halova kniha vám poskytne řešení a poučí vás, proč je tohle řešení správné Ruby.“ Martin Fowler, Chief Scientist, ThoughtWorks, Autor „Patterns of Enterprise Application Architecture“
Ruby kompendium znalostí pro začátečníky i profesionály
Hal Fulton
THE RUBY WAY, SECOND EDITION Hal Fulton Authorized translation from English language edition, entitled RUBY WAY, SECOND EDITION, THE: SOLUTION AND TECHNIQUES IN RUBY PROGRAMMING, 2nd Edition, 0672328844 by FULTON, HAL, published by Pearson Education, Inc, publishing as Addison Wesley Professional, Copyright © 2007. All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education, INC. CZECH language edition by ZONER SOFTWARE S.R.O., Copyright © 2009. Autorizovaný překlad anglického vydání nazvaného RUBY WAY, SECOND EDITION, THE: SOLUTION AND TECHNIQUES IN RUBY PROGRAMMING, druhé vydání, 0672328844, autor FULTON, HAL, vydal Pearson Education, Inc, ve vydavatelství Addison Wesley Professional, Copyright © 2007. Všechna práva vyhrazena. Žádná část této publikace nesmí být reprodukována nebo předávána žádnou formou nebo způsobem, elektronicky ani mechanicky, včetně fotokopií, natáčení ani žádnými jinými systémy pro ukládání bez výslovného svolení Pearson Education, INC. České vydání ZONER SOFTWARE S.R.O., Copyright © 2009.
Ruby – kompendium znalostí pro začátečníky i profesionály Autor: Hal Fulton Copyright © ZONER software, s.r.o. Vydání první v roce 2009. Všechna práva vyhrazena. Zoner Press Katalogové číslo: ZR724 ZONER software, s.r.o. Nové sady 18, 602 00 Brno Překlad: Jiří Koutný Odpovědný redaktor: Miroslav Kučera Odborná korektura: Miroslav Kučera a Dalibor Šrámek Šéfredaktor: Ing. Pavel Kristián DTP: Miroslav Kučera Zdrojové soubory ke knize: http://www.zonerpress.cz/download/ruby-kompendium.zip
Informace, které jsou v této knize zveřejněny, mohou byt chráněny jako patent. Jména produktů byla uvedena bez záruky jejich volného použití. Při tvorbě textů a vyobrazení bylo sice postupováno s maximální péčí, ale přesto nelze zcela vyloučit možnost výskytu chyb. Vydavatelé a autoři nepřebírají právní odpovědnost ani žádnou jinou záruku za použití chybných údajů a z toho vyplývajících důsledků. Všechna práva vyhrazena. Žádná část této publikace nesmí být reprodukována ani distribuována žádným způsobem ani prostředkem, ani reprodukována v databázi či na jiném záznamovém prostředku či v jiném systému bez výslovného svolení vydavatele, s výjimkou zveřejnění krátkých částí textu pro potřeby recenzí. Veškeré dotazy týkající se distribuce směřujte na: Zoner Press ZONER software, s.r.o. Nové sady 18, 602 00 Brno tel.: 532 190 883, fax: 543 257 245 e-mail: knihy@zoner.cz http://www.zonerpress.cz
ISBN 978-80-7413-018-2
Mým rodičům, bez kterých bych tu nebyl
6
Obsah Předmluva
23
Předmluva k druhému vydání
23
Předmluva k prvnímu vydání
23
Poděkování
25
Poděkování pro druhé vydání
25
Poděkování pro první vydání
26
O autorovi
27
Úvod
28
O druhém vydání
28
Jak pracovat s touto knihou
31
Zdrojové kódy ke stažení
32
Poznámka redakce k českému vydání
33
Sdělte nám svůj názor
33
Co je "cesta Ruby"?
34
Kapitola 1
Ruby ve zkratce
1.1 – Úvod do objektové orientace
39 40
1.1.1 – Co je objekt?
40
1.1.2 – Dědičnost
41
1.1.3 – Polymorfismus
43
1.1.4 – Ještě několik pojmů
44
1.2 – Základní syntaxe a sémantika Ruby
45
1.2.1 – Klíčová slova a identifikátory
46
1.2.2 – Komentáře a vestavěná dokumentace
46
1.2.3 – Konstanty, proměnné a typy
47
1.2.4 – Operátory a priorita
49
1.2.5 – Ukázkový program
50
1.2.6 – Cykly a větvení
53
1.2.7 – Výjimky
57
1.3 – OOP v Ruby
59
1.3.1 – Objekty
60
1.3.2 – Zabudované třídy
60
1.3.3 – Moduly a mixiny
62
1.3.4 – Vytváření tříd
62
7 1.3.5 – Metody a atributy
67
1.4 – Dynamické aspekty Ruby
68
1.4.1 – Programování v době běhu programu
69
1.4.2 – Reflexe
70
1.4.3 – Chybějící metody
72
1.4.4 – Svoz odpadků (garbage collection)
72
1.5 – Trénink intuice: co si zapamatovat
73
1.5.1 – Záležitosti syntaxe
73
1.5.2 – Perspektiva při programování
75
1.5.3 – Příkaz case v Ruby
78
1.5.4 – Rubyismy a idiomy
81
1.5.5 – Výrazy a další rozmanité záležitosti
86
1.6 – Hantýrka Ruby a slang
88
1.7 – Závěr
91
Kapitola 2
Práce s řetězci
93
2.1 – Reprezentace běžných řetězců
94
2.2 – Reprezentace řetězců s alternativní notací
94
2.3 – Víceřádkové řetězce (here-documents)
95
2.4 – Zjištění délky řetězce
97
2.5 – Zpracování řetězce po řádcích
97
2.6 – Zpracování řetězce po bajtech
97
2.7 – Provádění specializovaného porovnávání řetězců
98
2.8 – Rozdělení řetězce na znaky
100
2.9 – Formátování řetězce
101
2.10 – Řetězce jako IO objekty
102
2.11 – Malá a velká písmena
102
2.12 – Zpřístupňování a přiřazování podřetězců
103
2.13 – Nahrazování v řetězcích
105
2.14 – Prohledávání řetězce
106
2.15 – Konverze mezi znaky a ASCII kódy
107
2.16 – Implicitní a explicitní konverze
108
2.17 – Připojení položky do řetězce
110
2.18 – Odstranění přebytečných znaků pro nový řádek a dalších znaků
110
2.19 – Odstranění prázdných znaků z řetězce
111
2.20 – Opakování řetězců
112
8 2.21 – Vkládání výrazů do řetězců
112
2.22 – Odložené vyhodnocení při vkládání výrazu
113
2.23 – Analýza dat oddělených čárkou
113
2.24 – Konverze řetězců na čísla (dekadická a jiná)
114
2.25 – Kódování a dekódování textu rot13
116
2.26 – Šifrování řetězců
117
2.27 – Komprimace řetězců
117
2.28 – Počítání znaků v řetězci
118
2.29 – Obrácení řetězce
119
2.30 – Odstraňování duplicitních znaků
119
2.31 – Odstraňování znaků
119
2.32 – Tisk speciálních znaků
120
2.33 – Generování po sobě jdoucích řetězců
120
2.34 – Výpočet 32bitového CRC
121
2.35 – Výpočet haše řetězce pomocí MD5
121
2.36 – Výpočet Levenshteinovy vzdálenosti mezi dvěma řetězci
122
2.37 – Kódování a dekódování řetězců base64
124
2.38 – Kódování a dekódování řetězce (uuencode/uudecode)
125
2.39 – Konverze znaků Tab na mezery a naopak
125
2.40 – Zalomení řádků textu
126
2.41 – Závěr
127
Kapitola 3
Práce s regulárními výrazy
129
3.1 – Syntaxe regulárních výrazů
129
3.2 – Kompilování regulárních výrazů
131
3.3 – Ošetření speciálních znaků
132
3.4 – Používání kotev (anchors)
133
3.5 – Používání kvantifikátorů
134
3.6 – Pozitivní a negativní vyhlížení
136
3.7 – Přístup ke zpětným referencím
137
3.8 – Používání tříd znaků
140
3.9 – Rozšířené regulární výrazy
141
3.10 – Zachytávání znaku pro nový řádek pomocí tečky
142
3.11 – Použití modifikátoru na část regulárního výrazu
143
3.12 – Použití vložených podvýrazů
143
3.13 – Ruby a Oniguruma
144
9 3.13.1 – Testování přítomnosti Onigurumy
144
3.13.2 – Přidání Onigurumy
145
3.13.3 – Několik nových funkcionalit enginu Oniguruma
146
3.13.4 – Pozitivní a negativní zpětné vyhlížení
146
3.13.5 – Více o kvantifikátorech
147
3.13.6 – Pojmenované výsledky porovnání
148
3.13.7 – Rekurze v regulárních výrazech
149
3.14 – Několik ukázkových regulárních výrazů
150
3.14.1 – Porovnání IP adresy
150
3.14.2 – Porovnání páru atribut=hodnota
151
3.14.3 – Porovnání římských číslic
152
3.14.4 – Porovnání číselných konstant
153
3.14.5 – Porovnání řetězce datum/čas
153
3.14.6 – Detekce duplicitních slov v textu
154
3.14.7 – Porovnání slov psaných velkými písmeny
154
3.14.8 – Porovnání čísel verzí
155
3.14.9 – Několik dalších vzorů
155
3.15 – Závěr
Kapitola 4
156
Internacionalizace v Ruby
157
4.1– Pozadí a terminologie
158
4.2 – Kódování ve světě po éře ASCII
161
4.2.1 – Knihovna jcode a $KCODE
162
4.2.2 – Běžné operace s řetězci a regulárními výrazy
163
4.2.3 – Detekce kódování znaků
167
4.2.4 – Normalizace řetězců Unicode
167
4.2.5 – Problémy s řazením řetězců
169
4.2.6 – Konverze mezi kódováními
172
4.3 – Používání katalogů zpráv
174
4.3.1 – Pozadí a terminologie
175
4.3.2 – Začínáme s katalogy zpráv
175
4.3.3 – Lokalizace jednoduché aplikace
176
4.3.4 – Další poznámky
180
4.4 – Závěr
181
10 Kapitola 5
Vykonávání číselných výpočtů
5.1 – Reprezentace čísel v Ruby
183 183
5.2 – Základní operace s čísly
184
5.3 – Zaokrouhlování hodnot s pohyblivou řádovou čárkou
185
5.4 – Porovnání čísel s pohyblivou řádovou čárkou
187
5.5 – Formátování hodnot pro výstup
189
5.6 – Formátování čísel s čárkou
189
5.7 – Práce s velmi velkými celými čísly
190
5.8 – Použití BigDecimal
190
5.9 – Práce s racionálními čísly
192
5.10 – Práce s maticemi
193
5.11 – Práce s komplexními čísly
197
5.12 – Používání knihovny mathn
198
5.13 – Hledání faktorizace, GCD a LCM
199
5.14 – Práce s prvočísly
200
5.15 – Implicitní a explicitní číselné konverze
201
5.16 – Vynucení číselné hodnoty
202
5.17 – Provádění bitových operací na číslech
203
5.18 – Provádění konverze základů
205
5.19 – Hledání odmocnin
205
5.20 – Určení pořadí bajtů architektury
206
5.21 – Numerický výpočet určitého integrálu
207
5.22 – Trigonometrie ve stupních, radiánech a gradiánech
208
5.23 – Pokročilejší trigonometrie
209
5.24 – Výpočet logaritmu s libovolným základem
209
5.25 – Výpočet průměru, mediánu, a modusu ze souboru dat
210
5.26 – Rozptyl a směrodatná odchylka
211
5.27 – Výpočet korelačního koeficientu
212
5.28 – Generování náhodných čísel
213
5.29 – Knihovna memoize pro cachování
214
5.30 – Závěr
215
Kapitola 6
Symboly a rozsahy
6.1 – Symboly
217 217
6.1.1 – Symboly jako výčty
219
6.1.2 – Symboly jako metahodnoty
219
11 6.1.3 – Symboly, proměnné a metody
220
6.1.4 – Konverze na/z symboly
221
6.2 – Rozsahy
222
6.2.1 – Otevřené a uzavřené rozsahy
222
6.2.2 – Hledání koncových bodů
223
6.2.3 – Iterace přes rozsah
223
6.2.4 – Testování příslušnosti k rozsahu
224
6.2.5 – Konverze na pole
224
6.2.6 – Opačné rozsahy
225
6.2.7 – Flip-flop operátor
225
6.2.8 – Uživatelské rozsahy
228
6.3 – Závěr
Kapitola 7
231
Práce s datem a časem
233
7.1 – Určení aktuálního času
234
7.2 – Práce se specifickými časy
234
7.3 – Určení dne v týdnu
235
7.4 – Určení data Velikonoc
236
7.5 – Hledání n-tého dne v měsíci
237
7.6 – Konverze mezi sekundami a většími jednotkami
238
7.7 – Konverze na a z epochy
238
7.8 – Práce s přestupnými sekundami? Raději ne!
239
7.9 – Nalezení čísla dne v roce
239
7.10 – Ověřování data a času
240
7.11 – Hledání týdne v roce
240
7.12 – Detekce přestupných roků
241
7.13 – Získávání časových pásem
242
7.14 – Práce s hodinami a minutami
242
7.15 – Porovnávání datových a časových hodnot
243
7.16 – Přidávání intervalů k datovým a časovým hodnotám
243
7.17 – Výpočet rozdílu dvou datových/časových hodnot
244
7.18 – Práce se specifickými daty
244
7.19 – Konverze mezi třídami Time, Date a DateTime
245
7.20 – Získávání hodnot data a času z řetězce
246
7.21 – Formátování a tisk hodnot data a času
247
7.22 – Konverze časových pásem
248
12 7.23 – Určení počtu dnů v měsíci
248
7.24 – Rozdělení měsíce na jednotlivé týdny
249
7.25 – Závěr
250
Kapitola 8
Pole, haš a ostatní výčty
8.1 – Práce s poli
251 251
8.1.1 – Vytvoření a inicializace pole
252
8.1.2 – Zpřístupnění a přiřazení prvků pole
252
8.1.3 – Nalezení velikosti pole
254
8.1.4 – Porovnávání polí
254
8.1.5 – Řazení polí
256
8.1.6 – Výběr z pole na základě kritéria
259
8.1.7 – Použití specializovaných indexovacích funkcí
260
8.1.8 – Implementace řídkých matic
262
8.1.9 – Pole jako matematické množiny
263
8.1.10 – Náhodné uspořádání prvků pole
266
8.1.11 – Používání vícerozměrných polí
267
8.1.12 – Hledání prvků pole, které nejsou prvky jiného
268
8.1.13 – Transformace nebo mapování polí
269
8.1.14 – Odstranění hodnot nil z pole
269
8.1.15 – Odstranění specifických prvků z pole
269
8.1.16 – Zřetězení a připojení do polí
271
8.1.17 – Použití pole jako zásobníku nebo fronty
272
8.1.18 – Iterace skrz pole
272
8.1.19 – Oddělovače prvků pole při tvorbě řetězce
273
8.1.20 – Převrácení pole
273
8.1.21 – Odstranění duplicitních prvků z pole
274
8.1.22 – Prokládání polí
274
8.1.23 – Zjišťování počtu výskytů hodnot v poli
274
8.1.24 – Převrácení pole do podoby haše
275
8.1.25 – Synchronizované řazení několika polí
275
8.1.26 – Nastavení výchozí hodnoty pro nové prvky pole
276
8.2 – Práce s hašem
277
8.2.1 – Vytvoření nového haše
277
8.2.2 – Nastavení výchozí hodnoty pro haš
278
8.2.3 – Zpřístupnění a přidávání dvojic klíč-hodnota
279
13 8.2.4 – Odstranění dvojic klíč-hodnota
280
8.2.5 – Iterace skrz haš
281
8.2.6 – Invertování haše
281
8.2.7 – Detekce klíčů a hodnot v haši
281
8.2.8 – Extrahování haše do polí
282
8.2.9 – Výběr dvojice klíč-hodnota na základě kritéria
282
8.2.10 – Řazení haše
283
8.2.11 – Slučování dvou hašů
283
8.2.12 – Vytvoření haše z pole
284
8.2.13 – Hledání rozdílu nebo průniku klíčů haše
284
8.2.14 – Použití haše jako řídké matice
284
8.2.15 – Implementace haše s duplicitními klíči
285
8.3 – Výčty obecně
288
8.3.1 – Metoda inject
289
8.3.2 – Používání kvantifikátorů
290
8.3.3 – Metoda partition
290
8.3.4 – Iterování přes skupiny
291
8.3.5 – Konverze na pole nebo množiny
292
8.3.6 – Používání enumerátorů
293
8.3.7 – Používání generátorů
294
8.4 – Závěr
Kapitola 9
295
Pokročilejší datové struktury
9.1 – Práce s množinami
297 297
9.1.1 – Jednoduché množinové operace
298
9.1.2 – Pokročilejší množinové operace
299
9.2 – Práce se zásobníky a frontami
301
9.2.1 – Implementace přísnějšího zásobníku
302
9.2.2 – Detekce nevyrovnaného použití párových znaků
303
9.2.3 – Zásobníky a rekurze
304
9.2.4 – Implementace přísnější fronty 9.3 – Práce se stromy
305 306
9.3.1 – Implementace binárního stromu
307
9.3.2 – Řazení s použitím binárního stromu
309
9.3.3 – Používání binárního stromu jako vyhledávací tabulky
311
9.3.4 – Konverze stromu na řetězec nebo pole
311
14 9.4 – Práce s grafy
312
9.4.1 – Implementace grafu jako matice sousednosti
313
9.4.2 – Určení, zdali je graf souvislý
316
9.4.3 – Určení, zdali má graf Eulerovu kružnici
317
9.4.4 – Určení, zdali má graf Eulerovu cestu
318
9.4.5 – Nástroje Ruby pro práci s grafy
319
9.5 – Závěr
Kapitola 10
319
I/O a uložení dat
10.1 – Práce se soubory a adresáři
321 322
10.1.1 – Otevírání a zavírání souborů
322
10.1.2 – Aktualizace souboru
323
10.1.3 – Připojení k souboru
324
10.1.4 – Náhodný přístup k souborům
324
10.1.5 – Práce s binárními soubory
325
10.1.6 – Zamykání souborů
327
10.1.7 – Vykonávání jednoduchých I/O
327
10.1.8 – Bufferované a nebufferované I/O
328
10.1.9 – Manipulace s vlastnictvím a přístupovými právy souboru
329
10.1.10 – Získávání a nastavování informací o časových údajích
331
10.1.11 – Kontrola existence souboru a velikosti
332
10.1.12 – Speciální vlastnosti souborů
333
10.1.13 – Práce s rourami (pipes)
335
10.1.14 – Vykonávání speciálních I/O operací
337
10.1.15 – Používání neblokujících I/O
337
10.1.16 – Použití metody readpartial
338
10.1.17 – Manipulace s cestami
339
10.1.18 – Používání třídy Pathname
340
10.1.19 – Manipulace se soubory na úrovni příkazů
341
10.1.20 – Čtení znaků z klávesnice
342
10.1.21 – Načtení celého souboru do paměti
343
10.1.22 – Iterace skrz soubor po řádcích
343
10.1.23 – Iterace skrz soubor po bajtech
344
10.1.24 – Práce s řetězcem jako se souborem
344
10.1.25 – Čtení dat vestavěných v programu
345
10.1.26 – Čtení zdroje programu
345
15 10.1.27 – Práce s dočasnými soubory
346
10.1.28 – Změna a nastavení aktuálního adresáře
346
10.1.29 – Změna aktuálního kořene
347
10.1.30 – Iterace skrz položky adresáře
347
10.1.31 – Získání seznamu položek adresáře
347
10.1.32 – Vytvoření sledu adresářů
348
10.1.33 – Rekurzivní odstranění adresáře
348
10.1.34 – Hledání souborů a adresářů
348
10.2 – Vysokoúrovňový přístup k datům
349
10.2.1 – Jednoduchý marshaling
349
10.2.2 – Složitější marshaling
351
10.2.3 – Omezené rekurzivní kopírování pomocí modulu Marshal
352
10.2.4 – Lepší perzistence objektů s PStore
352
10.2.5 – Práce s daty ve formátu CSV
354
10.2.6 – Marshaling s YAML
356
10.2.7 – Prevalence objektů s Madeleine
357
10.2.8 – Použití knihovny DBM
358
10.3 – Použití knihovny KirbyBase
359
10.4 – Spojení s externími databázemi
362
10.4.1 – Rozhraní k SQLite
362
10.4.2 – Rozhraní k MySQL
363
10.4.3 – Rozhraní k PostgreSQL
366
10.4.4 – Rozhraní k LDAP
368
10.4.5 – Rozhraní k Oracle
370
10.4.6 – Používání modulu DBI
371
10.4.7 – ORM (Object-Relational Mapper)
372
10.5 – Závěr
Kapitola 11
374
OOP a dynamické rysy Ruby
11.1 – Každodenní OOP úlohy
375 376
11.1.1 – Používání vícenásobných konstruktorů
376
11.1.2 – Vytváření atributů instance
377
11.1.3 – Používání pokročilých konstruktorů
378
11.1.4 – Vytváření vlastností a metod na úrovni třídy
380
11.1.5 – Dědění z nadtřídy
383
11.1.6 – Testování třídy objektů
385
16 11.1.7 – Testování rovnosti objektů
387
11.1.8 – Správa přístupu k metodám
389
11.1.9 – Kopírování objektu
391
11.1.10 – Používání initialize_copy
392
11.1.11 – Jak porozumět metodě allocate
393
11.1.12 – Práce s moduly
394
11.1.13 – Transformace a konverze objektů
397
11.1.14 – Vytváření čistě datových tříd (Structs)
400
11.1.15 – Zmrazení objektů
401
11.2 – Pokročilejší techniky
402
11.2.1 – Posílání explicitních zpráv objektu
403
11.2.2 – Specializování individuálních objektů
404
11.2.3 – Vnořování tříd a modulů
408
11.2.4 – Vytváření parametrických tříd
408
11.2.5 – Využití pokračování (continuation) pro implementaci generátoru
411
11.2.6 – Ukládání kódu jako objektů
414
11.2.7 – Jak funguje přidání modulu
415
11.2.8 – Detekce výchozích parametrů
417
11.2.9 – Delegování nebo přesměrování
417
11.2.10 – Automatické definování přístupových metod k atributům na úrovni třídy 11.2.11 – Pokročilejší programovací disciplíny 11.3 – Práce s dynamickými rysy
420 421 423
11.3.1 – Dynamické vyhodnocení kódu
423
11.3.2 – Používání const_get
425
11.3.3 – Dynamická instanciace třídy podle jména
425
11.3.4 – Získávání a nastavování proměnných instance
426
11.3.5 – Používání define_method
427
11.3.6 – Používání const_missing
430
11.3.7 – Odstraňování definicí
431
11.3.8 – Získávání seznamů definovaných entit
434
11.3.9 – Prozkoumání zásobníku
435
11.3.10 – Monitorování vykonávání programu
436
11.13.11 – Procházení prostorem objektů
438
11.3.12 – Správa volání neexistujících metod
438
11.3.13 – Sledování změn definice třídy nebo objektů
439
11.3.14 – Definování finalizerů pro objekty
443
11.4 – Závěr
444
17 Kapitola 12
Grafická rozhraní pro Ruby
12.1 – Ruby/Tk
445 446
12.1.1 – Přehled
446
12.1.2 – Jednoduchá aplikace s okny
447
12.1.3 – Práce s tlačítky
449
12.1.4 – Práce s textovými poli
452
12.1.5 – Práce s ostatními widgety
456
12.1.6 – Několik poznámek
459
12.2 – Ruby/GTK2
459
12.2.1 – Přehled
460
12.2.2 – Jednoduchá aplikace s okny
461
12.2.3 – Práce s tlačítky
462
12.2.4 – Práce s textovými poli
463
12.2.5 – Práce s ostatními widgety
466
12.2.6 – Další poznámky
470
12.3 – FXRuby (FOX)
472
12.3.1 – Přehled
473
12.3.2 – Jednoduchá aplikace s okny
473
12.3.3 – Práce s tlačítky
475
12.3.4 – Práce s textovými poli
477
12.3.5 – Práce s dalšími widgety
478
12.3.6 – Další poznámky
487
12.4 – QtRuby
487
12.4.1 – Přehled
487
12.4.2 – Jednoduchá aplikace s okny
488
12.4.3 – Práce s tlačítky
488
12.4.4 – Práce s textovými poli
490
12.4.5 – Práce s ostatními widgety
492
12.4.6 – Další poznámky
497
12.5 – Další GUI toolkity 12.5.1 – Ruby a X
497 498
12.5.2 – Ruby a wxWidgets
498
12.5.3 – Apollo (Ruby a Delphi)
498
12.5.4 – Ruby a Windows API
499
12.6 – Závěr
499
18 Kapitola 13
Vlákna v Ruby
13.1 – Vytváření a manipulace s vlákny
501 502
13.1.1 – Vytvoření vláken
502
13.1.2 – Přístup k lokálním proměnným vlákna
503
13.1.3 – Dotazování a změna stavu vlákna
505
13.1.4 – Čekání na dokončení (a zachycení návratové hodnoty)
508
13.1.5 – Práce s výjimkami
509
13.1.6 – Použití skupiny vláken
511
13.2 – Synchronizace vláken
512
13.2.1 – Jednoduchá synchronizace s kritickými sekcemi
513
13.2.2 – Synchronizace přístupu ke zdrojům (mutex.rb)
514
13.2.3 – Použití tříd předdefinovaných synchronizovaných front
518
13.2.2 – Použití podmínkových proměnných
520
13.2.5 – Použití dalších synchronizačních technik
521
13.2.6 – Časový limit operace
524
13.2.7 – Čekání na událost
525
13.2.8 – Pokračování ve zpracování během I/O
526
13.2.9 – Implementace paralelních iterátorů
527
13.2.10 – Paralelní rekurzivní mazání 13.3 – Závěr
Kapitola 14
529 530
Skriptování a správa systému
14.1 – Spuštění externího programu
531 531
14.1.1 – Použití system a exec
532
14.1.2 – Substituce výstupu příkazu
533
14.1.3 – Manipulace s procesy
534
14.1.4 – Manipulace se standardním vstupem/výstupem
536
14.2 – Volby a argumenty příkazového řádku
537
14.2.1 – Analýza voleb příkazového řádku
537
14.2.2 – Práce s ARGF
539
14.2.3 – Práce s ARGV
539
14.3 – Knihovna Shell
540
14.3.1 – Použití Shell pro přesměrování I/O
540
14.3.2 – Několik poznámek k shell.rb
542
14.4 – Přístup k proměnným prostředí 14.4.1 – Získávání a nastavování proměnných prostředí
543 543
19 14.4.2 – Ukládání proměnných prostředí jako pole nebo haš
544
14.4.3 – Import proměnných prostředí jako globálních proměnných
545
14.5 – Skriptování v Microsoft Windows
545
14.5.1 – Používání Win32API
546
14.5.2 – Použití Win32OLE
547
14.5.3 – Používání ActiveScriptRuby
550
14.6 – Instalátor na jedno kliknutí pro Windows
551
14.7 – Knihovny, o kterých potřebujete vědět
552
14.8 – Práce se soubory, adresáři a stromy
553
14.8.1 – Několik slov k textovým filtrům
553
14.8.2 – Kopírování adresářovéhostromu (včetně symbolických odkazů)
554
14.8.3 – Mazání souborů podle data nebo jiného kritéria
555
14.8.4 – Zjištění volného místa na disku 14.9 – Různé skriptovací úlohy
556 557
14.9.1 – Ruby řešení v jediném souboru
557
14.9.2 – Roura do interpretu Ruby
558
14.9.3 – Získávání a nastavování návratových kódů
559
14.9.4 – Testování, jestli program běží interaktivně
560
14.9.5 – Určení platformy operačního systému
560
14.9.6 – Použití modulu Etc
561
14.10 – Závěr
Kapitola 15
562
Ruby a datové formáty
15.1 – Analýza XML pomocí REXML
563 564
15.1.1 – Analýza stromu
565
15.1.2 – Analýza proudu
566
15.1.3 – XPath a další
567
15.2 – Práce s RSS a Atom
568
15.2.1 – Standardní knihovna rss
568
15.2.2 – Knihovna feedtools
571
15.3 – Manipulace s obrázky pomocí RMagick
572
15.3.1 – Běžné grafické úkoly
573
15.3.2 – Speciální efekty a transformace
576
15.3.3 – Kreslicí API
579
15.4 – Tvorba PDF dokumentů pomocí PDF::Writer 15.4.1 – Základní koncepty a techniky
582 582
20 15.4.2 – Vzorový dokument 15.5 – Závěr
Kapitola 16
585 592
Testování a odstraňování chyb
593
16.1 – Testování pomocí Test::Unit
594
16.2 – Nástroje ZenTest
598
16.3 – Ruby debugger
601
16.4 – Používání irb jako debuggeru
605
16.5 – Měření vytížení kódu
606
16.6 – Měření výkonu
607
16.7 – Uživatelsky příjemná reprezentace objektů
611
16.8 – Závěr
612
Kapitola 17
Balíčkování a distribuce kódu
17.1 – Používání RDoc
613 613
17.1.1 – Jednoduché značky
615
17.1.2 – Pokročilejší formátování
617
17.2 – Instalace a balíčkování
618
17.2.1 – Knihovna setup.rb
619
17.2.2 – RubyGems
620
17.3 – RubyForge a RAA
622
17.4 – Závěr
623
Kapitola 18
Síťové programování
18.1 – Síťové servery 18.1.1 – Jednoduchý server: aktuální čas
625 626 627
18.1.2 – Implementace serveru s vlákny
628
18.1.3 – Případová studie: peer-to-peer šachový server
629
18.2 – Síťoví klienti 18.2.1 – Získávání opravdu náhodných čísel z webu
638 638
18.2.2 – Spojení s oficiálním časovým serverem
641
18.2.3 – Interakce s POP serverem
642
18.2.4 – Posílání e-mailu prostřednictvím SMTP
644
18.2.5 – Interakce s IMAP serverem
647
18.2.6 – Kódování a dekódování příloh
648
18.2.7 – Případová studie: brána mezi e-mailovou konferencí a diskusní skupinou
651
21 18.2.8 – Získávání webové stránky z URL
656
18.2.9 – Používání knihovny Open-URI
657
18.3 – Závěr
Kapitola 19
657
Ruby a webové aplikace
19.1 – CGI programování v Ruby
659 659
19.1.1 – Představení knihovny cgi.rb
661
19.1.2 – Zobrazení a zpracování formulářů
662
19.1.3 – Práce s cookies
663
19.1.4 – Práce s relacemi uživatele
664
19.2 – Používání FastCGI
665
19.3 – Ruby on Rails
667
19.3.1 – Principy a technologie
667
19.3.2 – Testování a ladění Rails aplikací
669
19.3.3 – Rozšíření jádra
669
19.3.4 – Související nástroje a knihovny
670
19.4 – Vývoj webových aplikací s Nitro
671
19.4.1 – Vytvoření základní Nitro aplikace
671
19.4.2 – Nitro a vzor MVC
673
19.4.3 – Nitro a Og
677
19.4.4 – Běžné úkoly při vývoji webových aplikací v Nitro
678
19.4.5 – Několik důležitých detailů
681
19.5 – Úvod do Wee
683
19.5.1 – Jednoduchý příklad
684
19.5.2 – Asociování stavu s URL
685
19.6 – Vývoj webových aplikací s IOWA
686
19.6.1 – Základní koncepty IOWA
686
19.6.2 – Šablony v IOWA
689
19.6.3 – Předávání řízení mezi komponentami
690
19.7 – Ruby a webový server
691
19.7.1 – Používání mod_ruby
692
19.7.2 – Používání erb
693
19.7.3 – Používání serveru WEBrick
695
19.7.4 – Používání serveru Mongrel
697
19.8 – Závěr
699
22 Kapitola 20
Distribuované Ruby
20.1 – Přehled: použití drb
701 702
20.2 – Případová studie: simulace burzovního telegrafu
704
20.3 – Rinda: Ruby Tuplespace
708
20.4 – Service Discovery s distribuovaným Ruby
712
20.5 – Závěr
713
Kapitola 21
Vývojové nástroje pro Ruby
715
21.1 – Použití RubyGems
715
21.2 – Použití Rake
717
21.3 – Použití irb
721
21.4 – Utilita ri
726
21.5 – Podpora editorů
727
21.6 – Integrovaná vývojová prostředí
728
21.7 – Závěr
729
Kapitola 22
Komunita Ruby
731
22.1 – Zdroje na webu
731
22.2 – Diskusní skupiny a e-mailové konference
732
22.3 – Blogy a online magazíny
732
22.4 – Ruby Change Requests (požadavky na změnu)
733
22.5 – IRC kanály
733
22.6 – Konference o Ruby
734
22.8 – Závěr
734
Rejstřík
735
23
Předmluva Předmluva k druhému vydání Lidé – zejména filozofové – ve starověké Číně si mysleli, že za světem a za každou existencí je ukryto něco tajemného. Něco, co nemůže být nikdy vyřčeno, vysvětleno, a ani popsáno slovy. Číňané to nazývali Tao, Japonci zase Do. Pokud to přeložíte do angličtiny, znamená to Way (v češtině "Cesta" nebo "Způsob"). Je to Do v džudo, kendo, karatedo a aikido. Nejsou to jen bojová umění, ale zahrnují jak filozofii, tak i způsob života. Také programovací jazyk Ruby má svou filozofii a způsob myšlení. Naučí lidi myslet jinak. Pomáhá programátorům, aby měli při své práci více zábavy. Není to tím, že Ruby pochází z Japonska, ale tím, že programování je důležitou částí lidského bytí (dobře, přinejmenším některých lidských bytostí), přičemž Ruby bylo navrženo k tomu, aby lidem pomáhalo mít lepší život. Tao je těžké popsat. Ačkoliv ho cítím, nikdy jsem se nepokusil jej vysvětlit pomocí slov. Je to pro mě příliš obtížné, dokonce i v japonštině, v mém rodném jazyce. Ale chlapík jménem Hal Fulton to zkusil a jeho první pokus, který spočíval v prvním vydání této knihy, byl dost dobrý. Tento jeho druhý pokus popsat Tao jazyka Ruby je díky pomoci mnoha lidí z komunity ještě lepší. Jak se Ruby stává stále více populárním (částečně i kvůli Ruby on Rails), je důležité porozumět tajemství programátorské produktivity. Věřím, že tato kniha vám pomůže se stát zdatným programátorem. Veselé hackování. Yukihiro "Matz" Matsumoto Srpen 2006, Japonsko
Předmluva k prvnímu vydání Krátce poté, co jsem se počátkem 80. let poprvé setkal s počítači, začal jsem se zajímat o programovací jazyky. Od té doby jsem na ně byl zatížený. Myslím si, že důvodem pro tento zájem je skutečnost, že programovací jazyky jsou způsobem, jak vyjádřit lidské myšlenky. Jsou v zásadě orientovány na člověka. Navzdory tomuto faktu měly programovací jazyky sklon být orientovány strojově. Mnoho jazyků bylo navrženo pro pohodlí samotných počítačů. Ale vzhledem k tomu, že se počítače staly výkonnějšími a méně nákladnými, se tato situace postupně změnila. Podívejme se například na strukturované programování. Stroje se nestarají o to, zdali je program strukturován správně – pouze ho vykonají bit po bitu. Strukturované programování není pro stroje, ale pro lidi. Totéž platí pro objektově orientované programování. Přišel čas pro návrh jazyka zaměřeného na člověka.
24 V roce 1993 jsem mluvil s kolegou o skriptovacích jazycích, o jejich síle a budoucnosti. Skriptování jsem cítil jako cestu, kterou by se programování mělo v budoucnosti ubírat – orientací na člověka. Ale s existujícími jazyky jako Perl a Python jsem nebyl spokojen. Chtěl jsem jazyk, který by byl silnější než Perl a více objektově orientovaný než Python. Protože jsem nemohl najít ideální jazyk, rozhodl jsem se vytvořit svůj vlastní. Ruby sice není nejjednodušším jazykem, ale lidská duše ve svém přirozeném stavu také není jednoduchá. Miluje jednoduchost a složitost zároveň. Nemůže ovládat příliš mnoho komplexních věcí, ale ani příliš mnoho věcí jednoduchých. Je to věc rovnováhy. Při navrhování jazyka orientovaného na člověka, Ruby, jsem následoval princip nejmenšího překvapení (Principle of Least Surprise). Všechno, co mě překvapilo, jsem považoval za méně správné. Následkem toho mám při programování v Ruby dobrý pocit – dokonce druh radosti. A již od prvního vydání Ruby v roce 1995 se mnou mnoho programátoru po celém světě o radosti z programování v Ruby souhlasí. Zde bych rád vyjádřil mé velké uznání lidem v komunitě Ruby. Oni jsou srdcem úspěchu Ruby. Jsem také vděčný autorovi této knihy, kterým je Hal E. Fulton. Tato kniha vysvětluje filozofií stojící za Ruby destilovanou z mého mozku a z komunity Ruby. Rád bych věděl, jak je možné, že Hal může číst mou mysl a odhalit tak tajemství Ruby. Nikdy jsem se s ním nesetkal tváří v tvář, ale doufám, že k tomu brzy dojde. Věřím, že tato kniha a Ruby vám pomohou v tom, aby vaše programování bylo zábavou. Yukihiro "Matz" Matsumoto Září 2001, Japonsko
25
Poděkování Poděkování pro druhé vydání Zdravý rozum říká, že druhé vydání bude vyžadovat dvakrát méně práce, než vyžadovalo vydání první. Zdravý rozum nemá pravdu. Třebaže velká část této knihy vychází z prvního vydání, musela být část přepracována a doladěna. Každá jednotlivá věta v této knize musela projít skrz filtr, který se ptal: "Co bylo pravdou v roce 2001, je pravdou i v roce 2006?" A to je samozřejmě jen začátek. Stručně řečeno – na toto druhé vydání jsem vynaložil nespočet stovek hodin práce; téměř tolik, kolik jsem vynaložil na první vydání. A to jsem "pouze autor". Tato kniha mohla vzniknout pouze díky kolektivní práci mnoha lidí. Na straně vydavatele dlužím poděkováním těmto lidem za jejich tvrdou práci a nekonečnou trpělivost: Debra Williams Cauley, Songlin Qui a Mandie Frank. Další díky patří Geneil Breezeové za její neúnavné úpravy mé poněkud nedokonalé angličtiny. Chci zde poděkovat i všem spolupracovníkům, se kterými jsem se nikdy nesetkal, protože jejich práce probíhala v pozadí. Technická redakce byla v tomto složení: Shashank Date a Francis Hwang. Odvedli skvělou práci a já si toho cením. Chyby, které se nakonec dostaly do tisku, samozřejmě padají na mou hlavu. Mé další poděkování patří lidem, kteří dodávali vysvětlivky, psali vzorový kód a odpovídali na mé četné otázky. Mezi ně patří samotný Matz (Yukihiro Matsumoto), Dave Thomas, Christian Neukirchen, Chad Fowler, Curt Hibbs, Daniel Berger, Armin Roehrl, Stefan Schmiedl, Jim Weirich, Ryan Davis, Jenny W., Jim, Freeze, Lyle Johnson, Martin DeMello, Matt Lawrence, Ron Jeffries, Tim Hunter, Chet Hendrickson, Nathaniel Talbott a Bil Kleb. Zvláštní dík patří největším přispěvatelům. Andrew Johnson podstatně zvýšil mou znalost regulárních výrazů. Paul Battley poskytl významné příspěvky do kapitoly o internacionalizaci. Masao Mutoh vypomohl se stejnou kapitolou a dále přispěl materiály o GTK. Austin Ziegler mě zasvětil do tajemství vytváření PDF souborů. Caleb Tennis přidal materiály o Qt. Eric Hodel přispěl materiálem o Rinda a Ring. James Britt vypomohl s kapitolou o webovém vývoji. Znovu musím poděkovat a pochválit Matze – nejenom za jeho pomoc, ale v prvé řadě za vytvoření Ruby. Domo arigato gozaimasu! ("Mockrát děkuji!" – pozn. red.) Opět děkuji svým rodičům, kteří mě bez přestání povzbuzovali a těšili se na tuto knihu. Možná ještě z nich udělám programátory. A ještě jednou musím poděkovat všem lidem z komunity Ruby za jejich neúnavnou práci, produktivitu a za udržování ducha komunity. Obzvláště děkuji čtenářům této knihy (v obou vydáních) a doufám, že ji shledáte poučnou, užitečnou a možná dokonce i zábavnou.
26
Poděkování pro první vydání Napsání knihy je týmové úsilí. Jedná se o skutečnost, kterou jsem nemohl plně docenit až do té doby, dokud jsem sám nějakou knihu nenapsal. Doporučuji to zažít, ačkoliv je to ponižující. Potvrzuji tedy, že bez podpory mnoha lidí by tato kniha nemohla existovat. Mé poděkování a ocenění musí nejprve směřovat k Matzovi (Yukihiro Matsumoto), který vytvořil jazyk Ruby. Domo arigato gozaimasu! Další dík patří Conradu Scheikerovi za pomoc při vytváření celkové struktury knihy. Tento člověk mi navíc prokázal velkou službu, když mě v roce 1999 uvedl do jazyka Ruby. Na vytváření obsahu této knihy se samozřejmě podílelo několik dalších lidí. Zde musím především zmínit Guye Hursta, který napsal nejenom značné části úvodních kapitol, ale také dva dodatky ke knize. Jeho pomoc musím označit za neocenitelnou. Poděkování pochopitelně patří i ostatním přispěvatelům, které zde uvádím bez nějakého speciálního pořadí: Kevin Smith odvedl skvělou práci na sekci věnované GTK v kapitole 6, čímž mě zachránil před horečným osvojováním si potřebných znalostí. Patrick Logan ve stejné kapitole osvětlil tajemství GUI FOX. Nesmím zapomenout na Chada Fowlera, který se v kapitole 9 věnoval XML, a jenž také přispěl nějakým materiálem do CGI sekce. Velký dík také patří těm, kteří asistovali při provádění korektur, a již hodnotili nebo přispěli v dalších rozmanitých oblastech: Don Muchow, Mike Stok, Miho Ogishima a další. Také děkuji Davidu Eppsteinovi, profesoru matematiky, za zodpovězení mých otázek o teorii grafů. Jednou z významných věcí na Ruby je podpora komunity. V e-mailových konferencích a diskusních skupinách se našlo mnoho lidí, kteří zodpověděli mé otázky a poskytli mi potřebnou pomoc. Opět bez nějakého speciálního pořadí se jednalo o tyto lidi: Dave Thomas, Andy Hunt, Hee-Sob Park, Mike Wilson, Avi Bryant, Yasushi Shoji ("Yashi"), Shugo Maeda, Jim Weirich, "Arton" a Masaki Suketa. Pokud jsem na někoho zapomněl, omlouvám se. A samozřejmě – tato kniha by nikdy nevyšla bez ohromné podpory vydavatele. Na produkci této knihy v zákulisí pracovalo mnoho lidí – v první řadě musím poděkovat Williamu Brownovi, který pracoval blízko mě a byl stálým zdrojem podpory, a Scottu Meyerovi, jenž se důkladně probíral všemi podklady a dával jednotlivé materiály dohromady. Ostatní bohužel nemohu ani jmenovat, protože jsem o nich nikdy neslyšel. Nicméně oni sami vědí, o koho jde. Také musím poděkovat svým rodičům, kteří z povzdálí tento projekt sledovali, po celou dobu mě povzbuzovali a dokonce se kvůli mně obtěžovali naučit kus počítačové vědy. Jeden můj přítel, který pracuje jako spisovatel, mi jednou řekl: "Když napíšeš knihu a nikdo si ji nepřečte, pak jsi ve skutečnosti žádnou knihu nenapsal." Takže chci poděkovat i svým čtenářům. Tato kniha je určena vám. Doufám, že pro vás má nějakou hodnotu.
27
O autorovi Hal Fulton má dva tituly v počítačové vědě na University of Mississippi. Předtím, než se kvůli řadě kontraktů (zejména pro IBM) přestěhoval do Austinu v Texasu, čtyři roky vyučoval počítačovou vědu na univerzitní úrovni. Více než 15 let pracoval s různými variantami Unixu, včetně AIX, Solaris a Linux. K Ruby se úplně poprvé dostal v roce 1999. V roce 2001 začal pracovat na prvním vydání této knihy, která byla tehdy celkově druhou knihou o Ruby v anglickém jazyce. Účastnil se celkem šesti konferencí o Ruby, přičemž na čtyřech z nich měl svou vlastní prezentaci (včetně první European Ruby Conference, která se konala v Karlsruhe v Německu). V současné době pracuje pro Broadwing Communications v Austinu v Texasu, kde pracuje na velkém datovém skladišti a souvisejících telekomunikačních aplikacích. Každý den pracuje s kódem C++, Oracle a samozřejmě i s Ruby. Hal Fulton je aktivním členem e-mailové konference (a IRC kanálu) zaměřené na Ruby. Má rozpracovaných několik projektů týkajících se Ruby. Je členem ACM a IEEE Computer Society. V osobním životě má rád hudbu, čtení, umění a fotografování. Je členem Mars Society a je vesmírný nadšenec, který by se rád dostal do vesmíru předtím, než zemře. Žije v Austinu v Texasu.
28
Úvod Cesta, kterou lze pojmenovat, není tou pravou Cestou. – Lao C', Kniha o Tao a ctnosti (Tao-te-ťing) Originální anglický název této knihy je "The Ruby Way". Tento název si říká o vysvětlení. Mým cílem bylo napsat tuto knihu pokud možno ve shodě se samotnou filozofií Ruby. To bylo také cílem všech ostatních spolupracovníků. Ačkoliv s nimi sdílím ocenění za úspěch, odpovědnost za jakékoliv chyby, které se do této knihy dostaly, leží výhradně na mně. Samozřejmě není v mých silách, abych vám precizním způsobem řekl úplně všechno o Ruby. To je v první řadě úkol pro samotného tvůrce jazyka, Matze, ale myslím si, že dokonce i on by měl potíž vyjádřit vše slovy. Stručně řečeno: "Ruby – kompendium znalostí pro začátečníky i profesionály" je pouze kniha, ale cesta Ruby (Ruby Way) je věcí tvůrce jazyka a komunity jako celku. Ačkoliv tohle lze jen velmi těžko popsat pomocí slov, v tomto úvodu se pokusím zachytit alespoň něco z toho nevyslovitelného o Ruby. Moudrý zájemce o Ruby to nicméně nebude brát až tak úplně vážně. Vezměte, prosím, na vědomí, že toto je druhé vydání. Ačkoliv mnoho věcí zůstalo stejných, mnoho dalších věcí se změnilo. Většina tohoto úvodu je stejná jako v předchozím vydání, nicméně nahlédněte do následující části "O druhém vydání", ve které jsou shrnuty změny a nový materiál.
O druhém vydání Všechno se mění a Ruby není výjimkou. Když v srpnu 2006 píšu tento úvod, první vydání této knihy je téměř pět let staré. A to je určitě vhodný čas na aktualizaci. V tomto vydání je mnoho změn a mnoho nových materiálů. Stará kapitola 4 o jednoduchých datových úlohách je nyní rozdělena do šesti kapitol, dvě z nich (Symboly a rozsahy a Internacionalizace v Ruby) jsou úplně nové. Ve zbývajících čtyřech jsou přidány nové příklady a komentáře. Kapitola o regulárních výrazech byla rapidně rozšířena – nyní pokrývá nejenom běžné regulární výrazy, ale také novější engine Oniguruma. Obsah kapitol 8 a 9 byl v prvním vydání této knihy původně obsažen v jediné kapitole. Ta byla rozdělena v okamžiku, kdy došlo k přidání dalších materiálů, protože se příliš rozrostla. Současné kapitoly 18, 19 a 20 obdobným způsobem vyrostly z kapitoly 9. Abychom získali místo navíc pro tyto materiály, museli jsme bohužel z nového vydání odstranit všechny přílohy. Zbývající nové kapitoly jsou následující:
Kapitola 15 – Ruby a datové formáty. Zahrnuje XML, RSS, obrázkové soubory, tvorbu PDF souborů a další věci.
Kapitola 16 – Testování a odstraňování chyb. Zabývá se unit testy, profilováním, laděním a dalšími podobnými tématy.
29
Kapitola 17 – Balíčkování a distribuce kódu. Tato kapitola zahrnuje použití setup.rb, vytvoření RubyGems a další záležitosti.
Kapitola 21 – Vývojové nástroje pro Ruby. Nabízí pohled na podporu Ruby v editoru a IDE, utilitu ri a RubyGems z pohledu uživatele.
Kapitola 22 – Komunita Ruby. Tato kapitola shrnuje důležité webové stránky, e-mailové konference, diskusní skupiny, konference, IRC kanály a další.
V širším smyslu můžeme říci, že každá kapitola v této knize je nová, protože jsem upravil a aktualizoval každou z nich; udělal jsem stovky menších a tucty větších změn. Smazal jsem zastaralé nebo méně důležité věci a změnil materiály tak, aby odpovídaly všem aktuálním změnám v Ruby. Do každé kapitoly jsem samozřejmě přidal nové příklady a komentáře. Pravděpodobně vás bude zajímat, co všechno bylo přidáno do starých kapitol. Jednou z nejdůležitějších změn je popis enginu Oniguruma, o kterém jsem se již zmínil dříve. Dále musím zmínit popis matematických knihoven a tříd jako BigDecimal, mathn a matrix. Nesmím zapomenout ani na nové třídy, jako například Set a DateTime. K velkým změnám v obsahu došlo především v následujících kapitolach:
Kapitoly 10 – I/O a uložení dat. Přidány materiály o readpartial a neblokujícím I/O. Nechybí materiály o třídě StringIO. Také jsem přidal materiály o CSV, YAML a KirbyBase. Do databázové části této kapitoly byly přidány informace o Oracle, SQLite, DBI a diskuse o ORM (Object-Relational Mappers).
Kapitola 11 – OOP a dynamické rysy Ruby. Zahrnuje poslední dodatky k Ruby, například initialize_copy, const_get, const_missing a define_method. Do této kapitoly jsem také přidal informace o technice delegování.
Kapitola 12 – Grafická prostředí pro Ruby. Téměř všechno bylo upraveno (zejména sekce o GTK a Fox). Část věnovaná QtRuby je úplně nová.
Kapitola 14 – Skriptování a správa systému. Nově popisuje instalátor Ruby pro Windows a několik podobných balíčků. Došlo také k několika změnám v ukázkových kódech.
Kapitola 18 – Síťové programování. Nově obsahuje část věnovanou e-mailovým přílohám. Interakce se serverem IMAP je rovněž novinkou. Obsahuje popis knihovny OpenURI.
Kapitola 19 – Ruby a webové aplikace. Nyní stručně pokrývá Ruby on Rails, Nitro, Wee, IOWA a další webové nástroje. Také pokrývá knihovny WEBrick a Mongrel.
Kapitola 20 – Distribuované Ruby. Obsahuje nové materiály popisující knihovnu Rinda, což je implementace konceptu tuplespace v Ruby. Také pokrývá těsně související Ring.
Jsou všechny tyto nové informace nezbytné? Ujišťuji vás, že ano. Kniha The Ruby Way byla v pořadí druhou knihu o Ruby, která vyšla v anglickém jazyce. (Jako první vyšla vynikající kniha Programming Ruby od Davea Thomase a Andyho Hunta.) Záměrně jsem tuto knihu napsal tak, aby první knihu o Ruby spíše doplňovala, než překrývala. Podle ohlasů to vypadá, že se mi to povedlo.
30 Když jsem začal psát první vydání knihy o Ruby, neprobíhaly žádné mezinárodní konference o Ruby. Neexistovalo RubyForge, ruby-doc.org nebo rubygarden.org. Stručně řečeno – kromě domovského webu Ruby bylo na internetu velmi málo informací o tomto programovacím jazyku. Archiv aplikací Ruby obsahoval pouze několik stovek položek. V té době bylo k dispozici pouze několik málo publikací (ať už online nebo offline), které dávaly najevo, že ví o existenci Ruby. Vždy, když vyšel nějaký článek o Ruby, byl to pro nás důvod, abychom si ho všimli a diskutovali o něm na e-mailové konferenci. Také neexistovaly nástroje a knihovny Ruby, které dnes považujete za zcela běžné. Neexistoval žádný RDoc. Nebyl žádný REXML pro analýzu XML. A matematická knihovna byla – v porovnání se současností – podstatně chudší. Ačkoliv existovala jistá podpora databází, ODBC podporováno nebylo. Tk bylo nejpoužívanějším GUI toolkitem a nejběžnější způsob vývoje webových aplikací spočíval na nízkoúrovňové CGI knihovně. Pro platformu Windows nebyl k dispozici žádný instalátor na jedno kliknutí ("one-click" installer). Uživatelé této platformy obvykle používali Cygwin nebo kompilátor založený na mingw. Systém RubyGems neexistoval ani v základní formě. Hledání a instalace knihoven a aplikací byl zcela manuální proces, který se neobešel bez zadávání příkazů tar a make. Nikdo neslyšel o Ruby on Rails, a pokud si dobře pamatuji, nikdo tehdy nepoužíval termín kachní typování (duck typing). Pro Ruby nebyl k dispozici ani YAML, ani Rake. V té době jsme používali Ruby ve verzi 1.6.4 a mysleli si, jak je úžasná. Ale verzi 1.8.5, kterou používáme dnes, je ještě úžasnější. Ačkoliv došlo k několika změnám v syntaxi, nejedná se o nic důležitého, o čem bychom se měli rozepisovat. Většinou se jedná o okrajové záležitosti, které nyní dávají více smyslu než v minulosti. Došlo ke změně sémantiky některých základních metod. Opět se většinou jedná o drobné změny. Například Dir#chdir dříve nepřijímal blok, v současně době to již umí. Některé základní metody byly zrušeny nebo přejmenovány. Metoda class přišla o svůj alias type. Metoda intern je nyní známa jako metoda to_sym. Array#indices je nyní Array#values_at atd. Přibylo také několik nových základní metod, například Enumerable#inject, Enumerable#zip a IO#readpartial. Stará knihovna futils se nyní jmenuje fileutils a má svůj vlastní modul FileUtils se jmenným prostorem namísto přidávání metod do třídy File. Pochopitelně došlo i k jiným změnám, o kterých v této sekci nic nepíši. Nicméně je důležité uvědomit si, že všechny tyto změny byly provedeny s velkou pozorností a opatrností. Ruby je pořád Ruby. Mnoho krásy Ruby je odvozeno z faktu, že všechny změny byly provedeny pozvolna, s rozmyslem a moudrostí Matze a ostatních vývojářů. A jak to vypadá dnes? Dnes máme k dispozici více knih o Ruby a více publikovaných článků, než potřebujeme. Web přetéká různými tutoriály, ukázkovými zdrojovými kódy a dokumentacemi. Objevily se nové nástroje a knihovny. Z těchto různých nástrojů se zdají být nejvíce používané webové frameworky, nástroje pro blogování, nástroje pro tvorbu značek a ORM (object-relational mappers). A samozřejmě je
31 zde k dispozici velké množství dalších – například nástroje a knihovny pro databáze, GUI, náročné výpočty, webové služby, práci s obrázky, správu zdrojů atd. Podpora jazyka Ruby v editorech je rozšířenější a promyšlenější. IDE jsou velmi užitečné. Je rovněž nesporné, že komunita Ruby se rozrostla a změnila. Ruby dnes rozhodně není podřadný jazyk – používá ho NASA, NOAA, Motorola a mnoho dalších velkých firem a institucí. Je používán nejenom pro práce s grafikou či databázemi, ale také pro různé výpočty, vývoj webových aplikací a další věci. Stručně řečeno – Ruby jde společně s hlavním proudem. Aktualizaci této knihy dělám s láskou a věřím, že pro vás bude užitečná.
Jak pracovat s touto knihou Předpokládám, že Ruby se nebudete učit z této knihy, protože není určena pro úplné začátečníky. Tímto chci říci, že pokud je pro vás Ruby naprostou novinkou, možná bude vhodné začít s nějakou jinou knihou. Nicméně programátoři jsou houževnatá parta, takže pokud máte nějaké zkušenosti s programováním, lze využít tuto knihu pro získání potřebných znalostí o Ruby. V takovém případě vám samozřejmě doporučuji začít kapitolou 1, která obsahuje stručný úvod do Ruby, věci týkající se syntaxe a samozřejmě i několik ukázkových příkladů. Tato kniha je převážně určena k zodpovězení otázek typu "Jak mohu udělat...?", takže předpokládám, že budete přeskakovat mezi jednotlivými tématy. Ačkoliv bych byl rozhodně poctěn, kdybyste si přečetli každou stránku od začátku až do konce, nevyžaduji to. Spíše očekávám, že budete brouzdat obsahem a hledat techniky, které potřebujete nebo věci, jež jsou pro vás něčím zajímavé. Od té doby, kdy vyšlo první vydání této knihy, jsem mluvil s mnoha lidmi, a jak se ukázalo, hodně z nich trpělivě četlo tuto knihu stránku po stránce. A co více – několik lidí mi sdělilo, že knihu použili pro učení, takže se ukazuje, že možnosti využití této knihy jsou opravdu velké. Některé věci v této knize mohou vypadat docela jednoduše a možná se budete sami sebe ptát, proč o nich vlastně píšu. Je to kvůli tomu, že různí lidé mají různé schopnosti a zkušenosti. To, co je samozřejmostí pro jednoho uživatele, nemusí být pochopitelné pro jiného. Tuto knihu lze označit za kompromis, protože ačkoliv mým cílem bylo poskytnout vám komplexní a vyčerpávající informace o Ruby, bylo nezbytné udržet rozsah této knihy na nějaké rozumné úrovni. Při psaní této knihy jsem předpokládal, že věci, které vás zajímají, budete vyhledávat podle požadované funkcionality nebo vašeho záměru a nikoliv podle názvu metody nebo třídy. Například třída String obsahuje několik následujících metod – capitalize, upcase, casecmp, downcase a swapcase. V nějaké referenční příručce by tyto metody byly zařazeny pod odpovídající písmena abecedy, nicméně v této knize je naleznete pěkně pohromadě. V honbě za úplností se občas odkazuji na nějaké jiné knihy, kde naleznete další informace. Díky tomu jsem mohl do knihy zařadit více různorodých příkladů a praktických ukázek. Protože tato kniha je určena programátorům, snažil jsem se do ní dostat co nejvíce okomentovaného kódu. Pokud pominu tento úvod, myslím si, že se mi to podařilo. Ačkoliv jako spisovatel mohu být někdy upovídaný, správný programátor chce vždy vidět kód. (A pokud ne, měl by.)
32 Některé příklady jsou kompletně vymyšlené, za což se musím omluvit. V některých případech může být problém (nebo zbytečně složité) ilustrovat požadovanou techniku či princip v kontextu reálného světa. Nicméně – pokud jsem chtěl demonstrovat nějaký komplexnější problém, snažil jsem se vytvořit řešení, které by bylo více založeno na potřebách reálného světa. Takže pokud v knize naleznete téma popisující slučování řetězců, může se vám zdát část kódu, která obsahuje foo a bar jako nesmyslná. Pokud jsem se ovšem věnoval složitějšímu tématu, jakým je třeba analýza kódu XML, je ukázkový kód mnohem smysluplnější a realističtější. Tato kniha obsahuje dva nebo tři osobní manýry, ke kterým se předem přiznávám. Jedním z nich je snaha vyhnout se "ošklivým" globálním proměnným ve stylu Perlu, jako například $_. Ačkoliv jsou v Ruby přítomny, pracují správně a každodenně jsou používány většinou programátorů v Ruby, téměř vždy se jim můžete vyhnout. A já jsem se rozhodl je vždy vynechat. Dalším osobním manýrem je to, že se vyhýbám samostatným výrazům, pokud nemají nějaký vedlejší efekt. Ruby je orientováno na výrazy, což je dobrá věc, kterou se v této knize snažím využívat. Ale v ukázkách kódu preferuji nepsat výrazy, které nevrací použitelnou hodnotu. Ačkoliv výraz "abc" + "def" může demonstrovat způsob, jakým se slučují řetězce, já místo toho raději napíšu něco jako str = "abc" + "def". Je možné, že toto se vám bude zdát jako zbytečně rozvláčné, nicméně – pokud jste programátorem v jazyce C, který si opravdu všímá toho, zdali jsou funkce platné či nikoliv (nebo pokud jste programátor v jazyce Pascal, jenž přemýšlí v procedurách a funkcích), bude to pro vás mnohem přirozenější. A poslední věc je ta, že nemám rád znak "mřížka" pro označení instančních metod. Mnoho ze skalních uživatelů Ruby si bude myslet, že jsem ukecaný, když řeknu "metoda instance crypt třídy String" místo prostého String#crypt. Nicméně tohle nikoho nepoplete. (Ve skutečnosti už pomalu začínám přecházet k používání znaku "mřížka", protože je zřejmé, že tato notace nevymizí.) Vždy, když to bylo možné, snažil jsem se poskytnout odkazy na další zdroje, protože požadavek na rozumný rozsah této knihy mi nedovolit dát do knihy všechno, co jsem chtěl. Každopádně věřím, že vám tyto odkazy pomohou. V knize se například velmi často odkazuji na RAA (Ruby Application Archive), což je jeden z nejdůležitějších zdrojů na webu o Ruby, o kterém byste měli vědět. Úvodní část většiny programátorských knih obvykle obsahuje naprosto zbytečnou ukázku typografie, které je v knize použita pro výpisy zdrojových kódů (a případně pro další dodatečné informace). V úvodu této knihy nic takového nenaleznete, protože nehodlám urážet vaši inteligenci. Na závěr chci poukázat na skutečnost, že přibližně 10 procent této knihy bylo napsáno jinými lidmi, čímž nemám na mysli pouze technické úpravy a opravu chyb. Ocením, když si přečtete poděkování v této knize (a samozřejmě i v jakékoliv jiné knize). Mnoho čtenářů tuto část knihy bohužel přeskakuje. Běžte si poděkování přečíst teď hned. Prospěje vám to (stejně jako zelenina).
Zdrojové kódy ke stažení Zdrojové kódy k této knize si můžete stáhnout z adresy zonerpress.cz/download/ruby-kompendium.zip (165 KB). Zdrojový archiv ve formátu .zip, obsahuje všechny významné fragmenty kódu. Pro jednotlivé soubory v tomto archivu se používá následující konvence. Výpisy kódu jsou
33 pojmenovány podle čísel jednotlivých kapitol, například soubor list11-1.rb obsahuje kód z výpisu 11.1. Kratší části kódu jsou pak pojmenovány na základě čísla stránky, kde se daný fragment kódu nachází, a nepovinného písmene – například soubory p240a.rb a p240b.rb se odkazují na dva fragmenty kódu ze strany 240. Části kódu, které jsou příliš krátké, nebo jež nemůžou být spuštěny mimo kontext, se obvykle v archivu neobjevují.
Poznámka redakce k českému vydání Zdrojové kódy, které jsme pro vás stáhnuli z adresy www.rubyhacker.com, bohužel nejsou kompletní. Archiv obsahuje výpisy a kratší části kódu pouze do kapitoly 12. Ačkoliv Hal Fulton na své stránce slibuje přidání zdrojových kódů ze zbývajících kapitol do poloviny listopadu 2006, doposud se tak nestalo. Za tuto situaci, kterou není v našich silách ovlivnit, se omlouváme. Jak jsme napsali již výše, soubory s kratšími částmi kódu jsou v archivu pojmenovány podle čísel stránek, na kterých se nachází. Čísla stránek českého vydání nicméně neodpovídají číslům stránek originálního anglického vydání. Z tohoto důvodu jsme do archivu zahrnuli soubor ruby-puvodni-obsah.pdf, který obsahuje obsah z původního anglického vydání, a který můžete použít pro snadnější vyhledání zdrojových souborů s požadovanými fragmenty kódu. Na závěr tohoto textu chci poděkovat panu Daliborovi Šrámkovi, který odvedl ohromné množství práce při odborných korekturách této knihy. Jeho web naleznete na www.insula.cz/dali/.
Sdělte nám svůj názor Jako čtenáři této knihy se stáváte těmi nejdůležitějšími kritiky a komentátory. Vážíme si vašeho názoru a chtěli bychom vědět, co děláme správně, co bychom mohli dělat lépe, ve kterých oblastech bychom měli publikovat, a také vaše další podnětné myšlenky, o které jste ochotni se podělit. Jako odborný redaktor Zoner Press vítám vaše názory. Můžete mi psát – poslat e-mail nebo dopis – a sdělit mi, co se vám v této knize líbilo nebo nelíbilo, stejně tak, co bychom měli udělat, aby naše další knihy byly lepší. Pokud mi napíšete, nezapomeňte, prosím, připojit název knihy, ISBN, jméno autora, vaše jméno, telefon, fax nebo e-mail. Pozorně zhodnotím vaše názory a poskytnu je všem lidem, kteří pracovali na této knize. Prosím, vězte, že nemohu pomoci s technickými problémy, které se týkají obsahu knihy, a že díky velkému množství e-mailů, jež dostávám, nemohu zaručit odpověď na každou zprávu. E-mail: miroslav.kucera@zoner.cz nebo knihy@zoner.cz. Adresa: ZonerPress, ZONER software, s.r.o., Miroslav Kučera, Nové sady 18, 602 00 Brno.
34
Co je "cesta Ruby"? Nechte nás připravit se na zápas s nepopsatelným, a uvidíte, možná se na to nakonec nevykašleme. – Douglas Adams, Holistická detektivní kancelář Dirka Gentlyho Jak si představit cestu Ruby (Ruby Way)? Věřím, že má dva aspekty. První je filozofie návrhu Ruby; druhý je filozofie jeho použití. Je přirozené, že návrh a použití spolu obvykle souvisí, ať už se jedná o software nebo hardware. Jestliže postavím nějaké zařízení a nasadím na něj kliku, je to proto, že očekávám, že ji někdo uchopí. Ruby má jakousi nepojmenovanou kvalitu, která ho dělá tím, čím je. Vidíme tuto kvalitu v návrhu syntaxe a sémantiky jazyka, ale i v programech napsaných pro interpret jazyka. Jenže jakmile nakreslíme tuto dělící čáru, hned se nám rozmaže. Ruby zjevně není pouze nástroj pro tvorbu softwaru, ale sám o sobě je to také kus softwaru. Proč by programy v Ruby měly pracovat podle odlišných pravidel, než pracuje interpret? Koneckonců, Ruby je vysoce dynamický a rozšiřitelný jazyk. Mohou existovat důvody, proč by se tyto dvě úrovně měly tu a tam odlišovat; je to výhodné pro přizpůsobení se problémům skutečného světa. Ale z obecného pohledu mohou být (a měly by být) myšlenkové pochody stejné. Ruby může být implementováno v Ruby, ve vskutku Hofstadterově stylu, i když v době psaní knihy tomu tak není. Ačkoliv často nepřemýšlíme nad původem slova "cesta" (way), toto slovo má dva různé významy, ve kterých se používá. Na jedné straně to znamená metodu, způsob nebo techniku, na straně druhé pak může znamenat cestu nebo stezku. Je jasné, že oba tyto významy spolu vzájemně souvisejí a když říkám "cesta Ruby" (the Ruby Way), míním tím oba významy. To, o čem tady mluvíme, je nejenom proces myšlení, ale také cesta, kterou následujete. Ani největší softwarový guru nemůže tvrdit, že dosáhl dokonalosti. Může pouze tvrdit, že následoval cestu. A ačkoliv zde může být více než jedna cesta, já zde mohu mluvit pouze o jedné. Tradiční moudrost říká, že forma následuje funkci. A tradiční moudrost má obvykle pravdu. Ale Frank Lloyd Wright jednou řekl: "Forma následuje funkci – to je nepochopeno. Forma a funkce by měly být ve shodě, duchovně spojeny." Co tím Wright myslel? Myslím, že tuto pravdu se nelze naučit z knihy, ale pouze ze zkušenosti. Nicméně, chtěl bych poukázat na to, jakým způsobem Wright vyjádřil tuto pravdu v trochu stravitelnější podobě. Byl velký zastánce jednoduchosti, jednou dokonce řekl, že "nejužitečnějšími nástroji architekta jsou guma na rýsovacím prkně a páčidlo na staveništi". Jednou ze ctností Ruby je jednoduchost. Mám k tomuto tématu citovat další myslitele? Podle Antoine de St. Exuperyho: "Dokonalosti je dosaženo nikoliv tehdy, když není co přidat, ale když není co odebrat". Ruby je ovšem složitý jazyk. Jak tedy mohu tvrdit, že je jednoduchý? Pokud lépe porozumíme vesmíru, možná nalezneme "zákon zachování složitosti" – fakt reality, který narušuje naše životy jako entropie, jíž se nemůžeme vyhnout, ale pouze ji přerozdělit. A tohle je klíč. Složitosti se nemůžeme
35 vyhnout, nicméně ji můžeme nechat okolo. Můžeme ji pohřbít mimo dohled. Tohle je starý princip "černé skříňky", která vykonává složité úkoly, nicméně zvenčí je ovládána jednoduchými příkazy. Pokud jste ještě neztratili trpělivost s mými citacemi, pak je vhodné uvést citát Alberta Einsteina "Všechno by mělo být tak jednoduché, jak to jenom jde, ale ne jednodušší". V Ruby z pohledu programátora vidíme jednoduchost (když ne z pohledu těch, kdo spravují samotný interpret). Vidíme také potenciál pro kompromis. Ve skutečném světě se musíte trochu ohýbat. Například každá entita v Ruby by měla být opravdovým objektem, nicméně určité hodnoty (jako například celá čísla) jsou uloženy jako přímé hodnoty. V obchodu, který je studentům počítačové vědy důvěrně znám po desetiletí, jsme vyměnili praktičnost implementace za eleganci návrhu. V důsledku jsme vyměnili jeden druh jednoduchosti za jiný. To, co Larry Wall řekl o Perlu, je pravda: "Když něco řeknete v malém jazyce, zdá se to velké. Když něco řeknete ve velkém jazyce, zdá se to malé". Totéž platí pro angličtinu. Hlavní důvodem, proč biolog Ernst Haeckel mohl pouze třemi slovy sdělit, že "ontogeneze rekapituluje fylogenezi", bylo to, že měl k dispozici tato silná slova se specifickým významem. Povolujeme vnitřní složitost jazyka, protože nám umožňuje odsunout tuto složitost do pozadí v jednotlivých výrocích. Řečeno jinými slovy – nepište 200 řádků kódu, když 10 řádků udělá stejnou práci. Stručnost je dobrá věc. Krátká část programu zabere v mozku programátora méně místa a lze ji snadněji uchopit jako celek. A jako pozitivní vedlejší efekt se do kódu při psaní zanese méně chyb. Samozřejmě je stále potřeba myslet na Einsteinovo varování o jednoduchosti. Pokud máte stručnost ve svém žebříčku priorit hodně vysoko, skončíte s kódem, který bude beznadějně nečitelný. Informační teorie říká, že komprimovaná data mají podobné statistické vlastnosti jako náhodný šum. Pokud se díváte na C, APL nebo na notaci (špatně napsaného) regulárního výrazu – máte právě tento dojem. "Ano, jednoduše, ale ne příliš jednoduše." Tohle je klíč. Stručně, ale nikoliv na úkor čitelnosti. Stručnost a čitelnost jsou dobré. Ale to má společnou příčinu, na kterou často zapomínáme. A sice, že počítače existují pro lidi, nikoliv lidi pro počítače. Dříve to bylo naopak. Počítače stály miliony dolarů a spotřebovaly velké množství energie. Lidé se chovali, jako by počítač byl ztělesněním boha; programátoři se považovali za prosebníky. Čas počítače byl cennější než čas člověka. Když se počítače staly menšími a levnějšími, staly se velmi populárními vysokoúrovňové jazyky. Ačkoliv něco takového bylo zcela zbytečné z hlediska počítačů, bylo to velmi efektivní z pohledu lidského. Ruby je pouze dalším rozvojem těchto myšlenek. Někdo občas použije zkratku VHLL (Very High-Level Language). Ačkoliv tento termín není přesně definován, myslím si, že jeho použití zde je opodstatněné. Počítač je předurčen k tomu, aby byl náš sluha, nikoliv pán. A jak Matz s oblibou často říká: chytrý sluha by měl vykonat složité úkoly prostřednictvím několika krátkých příkazů. To je správná myšlenka pro celou historii počítačové vědy. Na začátku byl strojový kód, přičemž postupně jsme přecházeli k nízkoúrovňovým a poté i k vysokoúrovňovým jazykům. Mluvím zde o posunu od formy zaměřené na stroj k formě zaměřené na člověka. Podle mého názoru je Ruby výborným příkladem programování orientovaného na člověka.
36 Přesuňme se do jiné doby. V roce 1980 vyšla nádherná malá kniha s názvem The Tao of Programming (napsal ji Geoffrey James). Téměř každý řádek této knihy stojí za zmínku, ale já zopakuji pouze jednu věc. Program by měl dodržovat "pravidlo nejmenšího údivu". Co to znamená? Nic víc než to, že program by měl vždy reagovat na uživatele takový způsobem, aby ho co nejméně překvapil. (V případě interpretu jazyka je uživatelem samozřejmě programátor.) Ačkoliv si nejsem zcela jist, zdali lze Geoffreymu Jamesovi připsat autorství tohoto pravidla, v jeho knize jsem se s ním setkal poprvé. V komunitě Ruby se jedná o velmi známé a velmi často citované pravidlo. Obvykle se ovšem nazývá jako princip nejmenšího překvapení (principle of least surprise, POLS). Každopádně, ať už toto pravidlo nazýváte jakkoliv, rozhodně platí a bylo dokonce základní direktivou v průběhu vývoje jazyka Ruby. Je také užitečné pro ty, co vyvíjejí různé knihovny nebo uživatelská rozhraní. Jediným problémem je samozřejmě to, že různí lidé mohou být překvapeni různými věcmi; neexistuje žádná univerzální dohoda, jak by se měl nějaký objekt nebo metoda chovat. Matz říká, že "nejmenší překvapení" by se především mělo vztahovat na něj, protože on je návrhář jazyka. Čím více budete myslet jako on, tím méně vás Ruby překvapí. A ujišťuji vás, že napodobování Matze není špatný nápad pro spoustu z nás. Nezávisle na tom, jak logicky je systém vybudován, vaše intuice potřebuje trénink. Každý programovací jazyk je svět sám pro sebe, se svými vlastními předpoklady. Totéž platí pro lidské jazyky. Když jsem se učil němčinu, dozvěděl jsem se, že všechna podstatná jména se píší s prvním písmenem velkým kromě slova deutsch. Postěžoval jsem si na to svému profesorovi, konec konců, vždyť je to název jazyka, nebo ne? Usmál se a řekl: "Nebojuj s tím." To, co mě naučil, bylo, abych nechal němčinu němčinou. To je dobrá rada pro každého, kdo k Ruby přichází od nějakého jiného jazyka. Nechte Ruby, aby mohlo být Ruby. Nečekejte, že je to Perl, protože není. Nečekejte, že je to LISP nebo Smalltalk, také není. Na druhé straně Ruby obsahuje společné prvky se všemi třemi zmíněnými. Postupujte tedy podle svých očekávání, ale nebojujte s tím, že někdy nebudou splněna. (Pokud Matz neodsouhlasí, že se jedná o potřebnou změnu.) Každý programátor dnes zná princip ortogonality (orthogonality). Předpokládejme, že máme fiktivní dvojici os se se sadou srovnatelných jazykových entit na jedné a s vlastnostmi nebo schopnostmi na druhé. Když řekneme slovo ortogonalita, obvykle tím myslíme, že prostor definovaný těmito osami, je tak "plný", jak jen to lze logicky udělat. Cesta Ruby (Ruby way) se částečně snaží o ortogonalitu. Pole je v některých věcech podobné jako haš, protože operace na každém z nich mohou být podobné. Limitu je ovšem dosaženo, jakmile vstoupíte do oblasti, kde se tyto dvě věci odlišují. Matz říká, že "přirozenost" je hodnocena více než ortogonalita. Ale pro porozumění toho, co je přirozené a co nikoliv, musíme přemýšlet a psát kód. Ruby se snaží být k programátorovi maximálně přátelský. Například jsou k dispozici aliasy pro velké množství metod – jak size, tak i length vrátí počet jednotek pole. Pravopisně odlišné indexes a indices se obě odkazují na stejnou metodu. Ačkoliv někdo tohle považuje za komplikaci nebo rovnou za špatnou vlastnost, mně se to docela líbí. Ruby dále usiluje o konzistenci a pravidelnost. Není na tom nic tajemného. V každém aspektu života toužíme po tom, aby věci byly pravidelné a vzájemně podobné. Obtížnější na tom je nau-
37 čit se, kdy tyto principy porušit. Například Ruby má ve zvyku ke jménům predikátových metod připojovat otazník (?). To je správné a dobré, protože to dělá kód přehlednějším a jmenný prostor snadněji ovladatelným. Ale podstatně diskutabilnější se může zdát obdobné použití vykřičníku (!) pro označení metod, které jsou "destruktivní" nebo "nebezpečné" v tom smyslu, že modifikuji příjemce. Je to diskutabilní z toho důvodu, že tímto způsobem nejsou označeny úplně všechny destruktivní metody. Neměli bychom být konzistentní? Ne, skutečně bychom neměli. Některé z metod svého příjemce mění přirozeně (jako například Array metody replace a concat). Některé metody umožňují přiřazení do vlastnosti třídy, takže bychom neměli přidávat vykřičník k názvům vlastností nebo ke znaménku rovnosti. Některé metody pravděpodobně mění příjemce, jako třeba metoda read – to by zase znamenalo příliš časté použití tohoto značení. Kdyby název každé destruktivní metody končil vykřičníkem, naše programy by brzy vypadaly jako reklamní brožury pro multi-level marketing. Povšimli jste si jistého napětí mezi protichůdnými silami? Tendence porušit někdy každé pravidlo? Dovolte mi tohle zformulovat jako druhý Fultonův zákon: "Každé pravidlo má výjimku, kromě druhého Fultonova zákona". (Ano, jedná se o žertík, ale jen malý.) To, co vidíme v Ruby, není hloupé uplatňování konzistence. Není to ani přísné lpění na jednoduchých pravidlech. A vskutku – částí cesty Ruby je to, že se nejedná o přísný a nepoddajný přístup. V návrhu jazyka, jak jednou řekl Matz, byste měli "následovat své srdce". Dalším aspektem filozofie Ruby je toto – neobávejte se změn za běhu programu a nemějte strach z dynamických věcí. Celý svět kolem vás je dynamický; proč byste měli programovat staticky? Ruby lze označit za jeden z nejvíce dynamických jazyků v historii. Chtěl bych také zmínit následující aspekt – nebuďte otrokem výkonu. Pokud je výkon aplikace nepřijatelný, problém musí být pochopitelně vyřešen, ale za normálních okolností by to neměla být první věc, nad kterou budete přemýšlet. Pokud výkonnost není kritická, preferujte eleganci nad výkonností. Avšak pokud píšete nějakou knihovnu, která může být používána nepředvídatelnými způsoby, výkon může být rozhodující od počátku. Když se podívám na Ruby, všímám si rovnováhy mezi odlišnými cíli návrhu, složité interakce připomínají problém mnoha těles ve fyzice. Umím si docela dobře představit, že to může být formováno jako díla Alexandra Caldera. Je to pravděpodobně tato interakce sama o sobě, harmonie, kterou ztělesňuje filozofie Ruby, spíše než její individuální části. Programátoři vědí, že jejich řemeslo není jen věda a technologie, ale i umění. Váhám, zdali říci, že v informatice existuje nějaký spirituální aspekt, ale jen tak mezi námi, pravděpodobně ano. (Pokud jste nečetli knihu Zen and the Art of Motorcycle Maintenance, kterou napsal Robert Pirsig, doporučuji vám to udělat.) Programovací jazyk Ruby vyvstal z lidského nutkání vytvořit věc, která bude užitečná a krásná. Program napsaný v Ruby by měl pramenit z toho samého Bohem daného zdroje. To je pro mě podstata cesty Ruby.
38
KAPITOLA 3 Práce s regulárními výrazy Zvolil bych vést ho v bludišti po vyšlapaných cestách... – Any Lowell, Patterns Síla regulárních výrazů jako výpočetního nástroje byla často podceňována. Od jejich prvotních teoretických začátků ve čtyřicátých letech si v letech šedesátých našly cestu k počítačovým systémům a odtud k různým nástrojům v operačním systému Unix. V devadesátých letech došlo ke zdomácnění regulárních výrazů (hlavně díky popularitě Perlu), takže už to nebyly těžko srozumitelné pomůcky, se kterými pracovali pouze ti největší zasvěcenci. Krása regulárních výrazů spočívá v tom, že téměř veškerou naši zkušenost můžeme chápat jako vzory. Jakmile máme vzory, které můžeme popsat, dokážeme je nacházet; dokážeme nacházet střípky reality, které odpovídají těmto vzorům, a dokážeme je ovlivnit podle své volby. V době psaní tohoto textu dochází ve vývoji Ruby k mnoha změnám. Stávající engine regulárních výrazů bude nahrazen novým, který se nazývá Oniguruma. Tomuto enginu je věnována pozdější sekce "3.13 – Ruby a Oniguruma" této kapitoly. V kapitole 4 jsou pak uvedena specifika regulárních výrazů v souvislosti s mezinárodním použitím.
3.1 – Syntaxe regulárních výrazů Typický regulární výraz je ohraničen dvojicí lomítek. Regulární výrazy rovněž mohou být zapsány ve formě %r. Tabulka 3.1 ukazuje některé jednoduché příklady: Tabulka 3.1. Základní regulární výrazy. Regex
Vysvětlení
/Ruby/
Odpovídá samostatnému slovu Ruby.
130
Kapitola 3 – Práce s regulárními výrazy
Regex
Vysvětlení
/[Rr]uby/
Odpovídá Ruby nebo ruby.
/^abc/
Odpovídá abc na začátku řádku.
%r(xyz$)
Odpovídá xyz na konci řádku.
%r|[0-9]*|
Odpovídá sekvenci (nula nebo více) čísel.
Dále je možné použít modifikátory, které se skládají z jednoho písmene, jež je umístěno ihned za samotným regulárním výrazem. Tabulka 3.2 ukazuje nejběžnější modifikátory: Tabulka 3.2. Modifikátory v regulárních výrazech. Modifikátor
Význam
i
Ignorovat velikost písmen v regulárním výrazu.
o
Vykonat nahrazení výrazu pouze jednou.
m
Víceřádkový režim (tečka odpovídá novému řádku).
x
Rozšířený regulární výraz (povoluje prázdné znaky a komentáře).
Další modifikátory jsou popsány v kapitole 4. Na konec tohoto úvodu o regulárních výrazech si ještě v tabulce 3.3 vyjmenujme nejběžnější symboly a notace. Tabulka 3.3. Běžné notace používané v regulárních výrazech. Zápis
Význam
^
Začátek řádku nebo řetězce.
$
Konec řádku nebo řetězce.
.
Libovolný znak kromě nového řádku (s výjimkou víceřádkového režimu).
\w
Alfanumerické znaky.
\W
Jiné než alfanumerické znaky.
\s
Prázdný znak (mezera, tabulátor, nový řádek atd.).
\S
Neprázdné znaky.
\d
Číslice (totéž jako [0-9]).
\D
Jiné znaky než číslice.
\A
Začátek řetězce.
\Z
Konec řetězce nebo před začátkem nového řádku.
Ruby – kompendium znalostí pro začátečníky i profesionály Zápis
Význam
\z
Konec řetězce.
\b
Hranice slova (pouze vně []).
\B
Jiné znaky než hranice slova.
\b
Znak backspace (pouze uvnitř []).
[ ]
Kterýkoliv znak množiny.
*
Žádný nebo libovolný počet výskytů předchozího znaku.
*?
Žádný nebo libovolný počet výskytů předchozího znaku (non-greedy).
+
Jeden nebo libovolný počet výskytů předchozího znaku.
+?
Jeden nebo libovolný počet výskytů předchozího znaku (non-greedy).
{m,n}
od m do n výskytů předcházejícího podvýrazu.
{m,n}?
od m do n výskytů předcházejícího podvýrazu (non-greedy).
?
Žádný nebo jeden výskyt předchozího znaku.
|
Alternativy (X|Y znamená X nebo Y).
(?=
)
Pozitivní vyhlížení.
(?!
)
Negativní vyhlížení.
()
131
Skupina výrazů.
(?>
)
Vnořený výraz.
(?:
)
Skupina nezačleněná do výsledku.
(?imx - imx)
Nastavení volby na on/off, platné od tohoto okamžiku.
(?imx – imx:expr)
Nastavení volby na on/off, platné pro tento výraz.
(?#
Komentář.
)
Znalost regulárních výrazů přináší modernímu programátorovi velké množství výhod. Protože vyčerpávající popis tohoto tématu je mimo rámec této knihy, doporučujeme vám se podívat do knihy Mastering Regular Expressions, kterou napsal Jeffrey Friedl. Pro další informace o tématu tohoto oddílu se podívejte na sekce "3.13 – Ruby a Oniguruma".
3.2 – Kompilování regulárních výrazů Regulární výrazy mohou být zkompilovány pomocí metody Regexp.compile (která je ve skutečnosti synonymem pro Regexp.new). První parametr je povinný a může to být řetězec nebo regex.
132
Kapitola 3 – Práce s regulárními výrazy
(Povšimněte si, že pokud je parametrem regulární výraz s nějakým modifikátorem, nebude tento modifikátor platný pro právě zkompilovaný regulární výraz.) pat1 = Regexp.compile("^foo.*")
# /^foo.*/
pat2 = Regexp.compile(/bar$/i)
# /bar/ (i není propagováno)
Druhý parametr, pokud je uveden, je obvykle jednou z následujících konstant nebo bitovým OR více z nich – Regexp::EXTENDED, Regexp::IGNORECASE a Regexp::MULTILINE. Navíc libovolná hodnota parametru, která není nil, bude mít za následek vytvoření regulárního výrazu, jenž nebude citlivý na velikost písmen (case-insensitive). Toto vám však nedoporučujeme praktikovat. options = Regexp::MULTILINE || Regexp::IGNORECASE pat3 = Regexp.compile("^foo", options) pat4 = Regexp.compile(/bar/, Regexp::IGNORECASE)
Třetí parametr, pokud je specifikován, je jazykový parametr, který umožňuje podporu vícebajtových znaků. Akceptuje jakoukoliv ze čtyř následujících řetězcových hodnot: "N" or "n" means None "E" or "e" means EUC "S" or "s" means Shift-JIS "U" or "u" means UTF-8
Literály regulárních výrazů mohou být samozřejmě specifikovány bez volání new nebo compile, stačí je uzavřít mezi lomítka. pat1 = /^foo.*/ pat2 = /bar$/i
Pro více informací nahlédněte do kapitoly 4.
3.3 – Ošetření speciálních znaků Metoda třídy Regexp.escape zajišťuje ošetření všech speciálních znaků, které jsou použity v regulárních výrazech. Jsou to znaky jako hvězdička, otazník a hranaté závorky. str1 = "[*?]" str2 = Regexp.escape(str1)
Metoda Regexp.quote je pouze alias.
# "\[\*\?\]"
Ruby – kompendium znalostí pro začátečníky i profesionály
133
3.4 – Používání kotev (anchors) Kotva je speciální výraz, který odpovídá pozici v řetězci (nikoliv znaku nebo sekvenci znaků). Jak uvidíme později, jedná se o jednoduchý případ předpokladu s nulovou délkou (zero-width assertion). Jinak řečeno – je to vzor, který v případě shody nespotřebovává žádné znaky z řetězce. Nejběžnější kotvy byly již uvedeny na začátku této kapitoly. Nejjednodušší jsou ^ a $, které odpovídají začátku a konci řetězce. string = "abcXdefXghi" /def/ =~ string
# 4
/abc/ =~ string
# 0
/ghi/ =~ string
# 8
/^def/ =~ string
# nil
/def$/ =~ string
# nil
/^abc/ =~ string
# 0
/ghi$/ =~ string
# 8
Nicméně – právě jsem vám řekl malou lež. Tyto kotvy vlastně neodpovídají začátku a konci řetězce, ale řádku. Pouvažujte nad stejnými vzory, které jsou aplikovány na podobný řetězec, jenž ovšem obsahuje nové řádky: string = "abc\ndef\nghi" /def/ =~ string
# 4
/abc/ =~ string
# 0
/ghi/ =~ string
# 8
/^def/ =~ string
# 4
/def$/ =~ string
# 4
/^abc/ =~ string
# 0
/ghi$/ =~ string
# 8
Máme ovšem k dispozici i speciální kotvy \A a \Z, které skutečně odpovídají začátku a konci řetězce. string = "abc\ndef\nghi" /\Adef/ =~ string
# nil
/def\Z/ =~ string
# nil
/\Aabc/ =~ string
# 0
/ghi\Z/ =~ string
# 8
\z je totéž jako \Z, ovšem s tím rozdílem, že \Z ignoruje případný ukončující znak nového řádku, kdežto v případě \z musí být shoda explicitní. string = "abc\ndef\nghi" str2 << "\n"
134
Kapitola 3 – Práce s regulárními výrazy
/ghi\Z/ =~ string
# 8
/\Aabc/ =~ str2
# 8
/ghi\z/ =~ string
# 8
/ghi\z/ =~ str2
# nil
Dále je možné pomocí \b porovnávat hranici slova, nebo pomocí \B místo, které není hranicí slova. Příklady s gsub vám pomohou pochopit, jak to pracuje: str = "this is a test" str.gsub(/\b/,"|")
# "|this| |is| |a| |test|"
str.gsub(/\B/,"-")
# "t-h-i-s i-s a t-e-s-t"
Neexistuje žádný způsob, jak rozlišit počáteční a koncovou hranici slova.
3.5 – Používání kvantifikátorů Velkou částí regulárních výrazů je práce s nepovinnými položkami a opakováním. Položka následující za otazníkem je nepovinná – může být uvedena, nebo může chybět; porovnání je závislé na zbytku regulárního výrazu. (Nemá smysl tohle aplikovat na kotvy, ale pouze na část vzoru (subpattern) s nenulovou délkou.) pattern = /ax?b/ pat2 = /a[xy]?b/ pattern =~ "ab"
# 0
pattern =~ "acb"
# nil
pattern =~ "axb"
# 0
pat2 =~ "ayb"
# 0
pat2 =~ "acb"
# nil
Pro entity je obvyklé, aby se nekonečně opakovaly (což můžeme specifikovat kvantifikátorem +). Například tento vzor odpovídá jakémukoliv celému kladnému číslu: pattern = /[0-9]+/ pattern =~ "1"
# 0
pattern =~ "2345678"
# 0
Další běžný případ je vzor, který nenastane ani jednou, nebo nastane vícekrát. To samozřejmě můžete udělat pomocí + a ?. V následujícím fragmentu kódu porovnáváme řetězec Huzzah následovaný žádným, nebo více vykřičníky: pattern = /Huzzah(!+)?/
# Závorky jsou zde nutné
pattern =~ "Huzzah"
# 0
pattern =~ "Huzzah!!!!"
# 0
Nicméně existuje i lepší způsob. Toto chování popisuje kvantifikátor *.
Ruby – kompendium znalostí pro začátečníky i profesionály pattern = /Huzzah!*/
# * se aplikuje pouze na !
pattern =~ "Huzzah"
# 0
pattern =~ "Huzzah!!!!"
# 0
135
Co když chceme porovnávat číslo sociálního pojištění? K tomu poslouží tento vzor: ssn = "987-65-4320" pattern = /\d\d\d-\d\d-\d\d\d\d/ pattern =~ ssn
# 0
Ale tento způsob není příliš čistý. Pojďme jednoznačně říci, kolik číslic je v každé skupině. Číslo v závorkách je kvantifikátor: pattern = /\d{3}-\d{2}-\d{4}/
Tohle sice není ten nejkratší možný vzor, který lze napsat, nicméně je jednoznačný a docela čitelný. Jako oddělovač může být použita i čárka. Představte si telefonní číslo obyvatele Elbonie, které se skládá z části se třemi až pěti čísly a z části se třemi až sedmi čísly. Tady je odpovídající vzor: elbonian_phone = /\d{3,5}-\d{3,7}/
První a poslední číslice jsou nepovinné (ačkoliv musíme mít jednu, nebo druhou): /x{5}/
# pro
/x{5,7}/
# pro 5-7
/x{,8}/
# pro až 8
/x{3,}/
# pro alespoň 3
Tímto způsobem mohou být samozřejmě přepsány kvantifikátory ?, + a *: /x?/
# totéž jako /x{0,1}/
/x*/
# totéž jako /x{0,}
/x+/
# totéž jako /x{1,}
Terminologie regulárních výrazů je plná barvitých personifikujících termínů jako greedy (chamtivý), reluctant (zdráhavý), lazy (líný) a possessive (majetnický). Rozdíl mezi greedy/non-greedy je jeden z nejdůležitějších. Zamysleme se nad touto částí kódu. Můžete očekávat, že tento regex bude odpovídat "Where the", ovšem místo toho odpovídá nejdelší části řetězce "Where the sea meets the": str = "Where the sea meets the moon-blanch'd land," match = /.*the/.match(str) p match[0]
# Zobrazí celou shodu: # "Where the sea meets the"
Důvodem je, že operátor * je greedy (chamtivý) – při porovnání zkonzumuje tolik řetězce, kolik je možné pro nejdelší možnou shodu. Pomocí otazníku z něj můžeme udělat non-greedy:
136
Kapitola 3 – Práce s regulárními výrazy
str = "Where the sea meets the moon-blanch'd land," match = /.*?the/.match(str) p match[0]
# Zobrazí celou shodu: # "Where the"
Tyto ukázky nám předvádí, že operátor * je standartně chamtivý, greedy (bez připojeného ?). Totéž platí pro kvantifikátory + a {m,n}, a také pro kvantifikátor ?. Nebyl jsem ovšem schopen vymyslet dobré příklady pro situaci s {m,n}? a ??. Pokud nějaké znáte, podělte se. Pro více informací o kvantifikátorech nahlédněte do sekce "3.13 – Ruby a Oniguruma".
3.6 – Pozitivní a negativní vyhlížení Regulární výraz je porovnáván vůči řetězci přímočarým způsobem (se zpětnou kontrolou, backtracking, v případě potřeby). Z tohoto důvodu existuje v řetězci koncept "aktuálního umístění" – v podstatě se jedná o souborový ukazatel nebo kurzor. Termín vyhlížení (lookahead) se odkazuje na konstrukci, která odpovídá části řetězce za aktuální pozicí. Jedná se o vzor s nulovou délkou, protože dokonce i tehdy, když je srovnání úspěšné, nedojde ke zkonzumování žádné části řetězce (to znamená, že aktuální pozice se nemění). V následujícím příkladě bude řetězec "New World" odpovídat pouze v případě, pokud bude následován slovem "Symphony" nebo "Dictionary" (třetí slovo není součástí porovnání): s1 = "New World Dictionary" s2 = "New World Symphony" s3 = "New World Order" reg = /New World(?= Dictionary| Symphony)/ m1 = reg.match(s1) m.to_a[0]
# "New World"
m2 = reg.match(s2) m.to_a[0]
# "New World"
m3 = reg.match(s3)
# nil
A zde je ukázka negativního vyhlížení: reg2 = /New World(?! Symphony)/ m1 = reg.match(s1) m.to_a[0]
# "New World"
m2 = reg.match(s2) m.to_a[0]
# nil
m3 = reg.match(s3)
# "New World"
V tomto příkladu bude řetězec "New World" odpovídat pouze tehdy, pokud nebude následován slovem "Symphony".
Ruby – kompendium znalostí pro začátečníky i profesionály
137
3.7 – Přístup ke zpětným referencím Každá část regulárního výrazu, která je umístěna do závorek, je samostatně přístupnou částí výsledku porovnání. Tyto části jsou očíslovány, takže prostřednictvím těchto čísel se na ně můžete odkazovat. Existuje několik způsobů, jak to provést. Pojďme nejprve prozkoumat tradičnější (tzn. ošklivější) způsoby. Speciální globální proměnné jako $1, $2 atd. mohou být použity jako reference na shody. str = "a123b45c678" if /(a\d+)(b\d+)(c\d+)/ =~ str puts "Matches are: '#$1', '#$2', '#$3'" # Vytiskne: Matches are: 'a123', 'b45', 'c768' end
Uvnitř substituce (jako například sub nebo gsub) nemohou být tyto proměnné použity. str = "a123b45c678" str.sub(/(a\d+)(b\d+)(c\d+)/, "1st=#$1, 2nd=#$2, 3rd=#$3") # "1st=, 2nd=, 3rd="
Možná se ptáte, proč nemůže fungovat? Odpověď je jednoduchá – argumenty pro sub jsou vyhodnoceny ještě předtím, než dojde k zavolání sub. Následující kód je ekvivalentní: str = "a123b45c678" s2 = "1st=#$1, 2nd=#$2, 3rd=#$3" reg = /(a\d+)(b\d+)(c\d+)/ str.sub(reg,s2) # "1st=, 2nd=, 3rd="
Tento kód názorně demonstruje, že hodnoty $1 až $3 nesouvisí s porovnáním, které bylo provedeno uvnitř volání sub. V tomto případě mohou být použity speciální kódy \1, \2 atd. str = "a123b45c678" str.sub(/(a\d+)(b\d+)(c\d+)/, '1st=\1, 2nd=\2, 3rd=\3') # "1st=a123, 2nd=b45, 3rd=c768"
Povšimněte si, že v předchozím fragmentu kódu jsme použili apostrofy. Pokud použijete klasické uvozovky, budou obrácená lomítka interpretována jako osmičkové (octal) únikové sekvence: str = "a123b45c678" str.sub(/(a\d+)(b\d+)(c\d+)/, "1st=\1, 2nd=\2, 3rd=\3") # "1st=\001, 2nd=\002, 3rd=\003"
Způsob, jak tohle obejít, spočívá v použití dvojitých únikových sekvencí: str = "a123b45c678"
138
Kapitola 3 – Práce s regulárními výrazy
str.sub(/(a\d+)(b\d+)(c\d+)/, "1st=\\1, 2nd=\\2, 3rd=\\3") # "1st=a123, 2nd=b45, 3rd=c678"
Dále je možné použít blokovou formu nahrazování, ve které mohou být použity globální proměnné, viz následující fragment kódu: str = "a123b45c678" str.sub(/(a\d+)(b\d+)(c\d+)/) { "1st=#$1, 2nd=#$2, 3rd=#$3" } # "1st=a123, 2nd=b45, 3rd=c678"
Při použití bloku tímto způsobem není možné použít speciální čísla s obrácenými lomítky uvnitř řetězce obklopeného klasickými uvozovkami (nebo dokonce apostrofy). Zde je vhodný okamžik, abych se zmínil o možnosti nezachytávajících (noncapturing) skupin. Někdy můžete chtít na znaky pohlížet jako na skupinu, například z důvodu vytvoření nějakého rafinovaného regulárního výrazu, ale nepotřebujete se odkazovat na odpovídající hodnoty při pozdějším použití. V těchto případech můžete použít nezachytávající skupinu (noncapturing group), která je označena prostřednictvím syntaxe (?:...): str = "a123b45c678" str.sub(/(a\d+)(?:b\d+)(c\d+)/, "1st=\\1, 2nd=\\2, 3rd=\\3") # "1st=a123, 2nd=c678, 3rd="
V tomto fragmentu kódu byla druhá skupina "zapomenuta", takže to, co bylo třetí částí výsledku porovnání, se stalo druhou. Osobně nemám rád notaci \1 stejně jako notaci $1. Ano – je pravda, že někdy jsou tyto notace pohodlné, nicméně vůbec není nutné je používat, protože to můžeme dělat "hezčím", více objektově orientovaným, způsobem. Metoda třídy Regexp.last_match vrací objekt třídy MatchData (stejně jako to dělá metoda match instance). Tento objekt poskytuje metody instance, které umožňují programátorovi zpřístupňovat zpětné reference. S objektem MatchData se manipuluje prostřednictvím notace s hranatými závorkami (jako by se jednalo o pole shod). Speciální prvek 0 obsahuje kompletní text odpovídajícího řetězce. Následně se prvek n odkazuje na n-tou shodu: pat = /(.+[aiu])(.+[aiu])(.+[aiu])(.+[aiu])/i # Čtyři identické skupiny v tomto vzoru refs = pat.match("Fujiyama") # refs je nyní: ["Fujiyama","Fu","ji","ya","ma"] x = refs[1] y = refs[2..3] refs.to_a.each {|x| print "#{x}\n"}
Povšimněte si skutečnosti, že objekt ref není opravdovým polem. Takže – když s ním chceme zacházet jako s polem za použití iterátoru each, musíme ho prostřednictvím to_a (jak je v případu výše ukázáno) převést na pole.
Ruby – kompendium znalostí pro začátečníky i profesionály
139
K tomu, abyste lokalizovali odpovídající podřetězec uvnitř původního řetězce, můžete použít i další techniky. Metody begin a end vrací počáteční a koncový offset shod. (Je důležité si uvědomit, že koncový offset je ve skutečnosti indexem znaku následujícího po posledním znaku shody při porovnání.) str = "alpha beta gamma delta epsilon" #
0.....5....0.....5.....0....5....
#
(pro naše konvence)
pat = /(b[^ ]+ )(g[^ ]+ )(d[^ ]+ )/ # Tří slova, pro každé jedna shoda refs = pat.match(str) # "beta " p1 = refs.begin(1)
# 6
p2 = refs.end(1)
# 11
# "gamma " p3 = refs.begin(2)
# 11
p4 = refs.end(2)
# 17
# "delta " p5 = refs.begin(3)
# 17
p6 = refs.end(3)
# 23
# "beta gamma delta" p7 = refs.begin(0)
# 6
p8 = refs.end(0)
# 23
Metoda offset podobným způsobem vrací pole se dvěma čísly, což je počáteční a koncový offset tohoto srovnání. Pokračování předchozího příkladu: range0 = refs.offset(0) # [6,23] range1 = refs.offset(1) # [6,11] range2 = refs.offset(2) # [11,17] range3 = refs.offset(3) # [17,23]
Části řetězce před a po srovnání podřetězce mohou být získány prostřednictvím metod pre_match a post_match. Pokračování předchozího příkladu: before = refs.pre_match # "alpha " after = refs.post_match # "epsilon"
KAPITOLA 8 Pole, haš a ostatní výčty Všechny části by do sebe měly zapadat bez použití hrubé síly. Mějte na paměti, že díly, které právě skládáte, jste předtím rozebrali. Pokud je nemůžete znovu složit dohromady, musí to mít nějaký důvod. V žádném případě nepoužívejte kladivo. – IBM, návod k obsluze, 1925 Jednoduché proměnné rozhodně nepostačují pro skutečné programování. Každý moderní jazyk podporuje nejenom složitější formy strukturovaných dat, ale také mechanismy pro vytváření nových abstraktních datových typů. Historicky jsou nejstarší a nejrozšířenější datovou strukturou pole. Ve Fortranu se jim říkalo indexové proměnné, a ačkoliv později došlo k určitým změnám, jejich základní myšlenka zůstala stejná ve všech programovacích jazycích. V současné době se extrémně populárním programovacím nástrojem stává haš. Podobně jako v případě pole je i haš indexovanou kolekcí datových položek, ovšem na rozdíl od něj může být indexován libovolným objektem. (V Ruby – jako ve většině ostatních programovacích jazyků – jsou prvky pole přístupné prostřednictvím číselného indexu.) Později v této kapitole se obecněji podíváme na modul Enumerable a na to, jakým způsobem pracuje. Jak pole, tak i haš tento modul zahrnují jako mix-in. Stejně tak mohou modul využívat i všechny třídy, pro které má taková funkcionalita smysl. Ale nepředbíhejme. Začneme s poli.
8.1 – Práce s poli Pole v Ruby jsou indexována celými čísly od nuly, stejně jako v případě polí v jazyce C. Nicméně zde veškerá podobnost končí. Pole v Ruby jsou dynamická. Při jejich vytváření je možné (ale nikoliv nezbytné) specifikovat jejich velikost. Po svém vytvoření mohou růst podle potřeby, bez jakéhokoliv zásahu programátora.
252
Kapitola 8 – Pole, haš a ostatní výčty
Pole v Ruby jsou heterogenní v tom smyslu, že mohou uchovávat různé datové typy, nikoliv pouze jeden. Ve skutečnosti je to tak, že ukládají reference na objekty (nikoliv objekty samotné), s výjimkou případů bezprostřední hodnoty jako u Fixnum. Pole si uchovává svou velikost, takže se nemusíme zdržovat jejím výpočtem nebo ji ukládat v externí proměnné, kterou bychom museli udržovat synchronizovanou s daným polem. V praxi jsou iterátory často definovány tak, abychom zřídkakdy potřebovali znát velikost pole. A konečně – třída Array v Ruby poskytuje polím mnoho užitečných funkcí pro přístup, hledání, zřetězení a další manipulace s poli. Ve zbývajících částech této sekce tuto třídu podrobně prozkoumáme a rozšíříme její vestavěnou funkčnost.
8.1.1 – Vytvoření a inicializace pole Speciální metoda třídy [] je používána pro vytvoření pole. Datové položky, které jsou uvedeny v hranatých závorkách, jsou použity po naplnění pole. Následující řádky kódu nám ukazují tři způsoby volání této metody. (Pole a, b a c budou naplněna stejnými hodnotami). a = Array.[](1,2,3,4) b = Array[1,2,3,4] c = [1,2,3,4]
V Ruby existuje metoda třídy nazvaná new, která může akceptovat žádný, jeden nebo dva parametry. První parametr je počáteční velikost pole (počet prvků). Druhý parametr je počáteční hodnota pro každý prvek: d = Array.new
# Vytvoří prázdné pole
e = Array.new(3)
# [nil, nil, nil]
f = Array.new(3, "blah")
# ["blah", "blah", "blah"]
Pečlivě se podívejte na poslední řádek předcházejícího fragmentu kódu. Obvyklou začátečnickou chybou je myslet si, že objekty v poli jsou odlišné. Ve skutečnosti se jedná o tři reference (odkazy) na stejný objekt. Pokud tedy z nějakého důvodu změníte objekt (místo jeho nahrazení za jiný objekt), změníte všechny prvky pole. Abyste se mohli vyhnout tomuto chování, použijte blok. Potom bude tento blok vyhodnocen pro každý prvek, takže každý prvek bude jiný objekt: f[0].capitalize!
# f je nyní: ["Blah", "Blah", "Blah"]
g = Array.new(3) { "blah" }
# ["blah", "blah", "blah"]
g[0].capitalize!
# g je nyní: ["Blah", "blah", "blah"]
8.1.2 – Zpřístupnění a přiřazení prvků pole Reference na prvky a přiřazování se provádí prostřednictvím metod třídy [] a []= (v tomto pořadí). Každá metoda třídy akceptuje celočíselný parametr, dvojici celých čísel (začátek a délku) nebo rozsah. Záporný index počítá od konce pole (začíná číslem -1).
Ruby – kompendium znalostí pro začátečníky i profesionály
253
Speciální metoda instance at slouží jako jednoduchá reference na prvek. Protože může přijímat pouze jediný celočíselný parametr, je o něco málo rychlejší. a = [1, 2, 3, 4, 5, 6] b = a[0]
# 1
c = a.at(0)
# 1
d = a[-2]
# 5
e = a.at(-2)
# 5
f = a[9]
# nil
g = a.at(9)
# nil
h = a[3,3]
# [4, 5, 6]
i = a[2..4]
# [3, 4, 5]
j = a[2...4]
# [3, 4]
a[1] = 8
# [1, 8, 3, 4, 5, 6]
a[1,3] = [10, 20, 30]
# [1, 10, 20, 30, 5, 6]
a[0..3] = [2, 4, 6, 8]
# [2, 4, 6, 8, 5, 6]
a[-1] = 12
# [2, 4, 6, 8, 5, 12]
V následujícím příkladě si povšimněte, jak reference směřující za konec pole způsobí změnu jeho velikosti. Také si povšimněte, že podpole (subarray) může být nahrazeno větším množstvím prvků, než v něm původně bylo, což také způsobí změnu jeho velikosti. k = [2, 4, 6, 8, 10] k[1..2] = [3, 3, 3]
# [2, 3, 3, 3, 8, 10]
k[7] = 99
# [2, 3, 3, 3, 8, 10, nil, 99]
A nakonec bychom se měli zmínit o tom, že pole, které je přiřazeno jedinému prvku, se ve skutečnosti vloží jako vnořené pole (na rozdíl od přiřazení do rozsahu): m = [1, 3, 5, 7, 9] m[2] = [20, 30]
# [1, 3, [20, 30], 7, 9]
# na druhou stranu... m = [1, 3, 5, 7, 9] m[2..2] = [20, 30]
# [1, 3, 20, 30, 7, 9]
Metoda slice slouží jako alias pro metodu []: x = [0, 2, 4, 6, 8, 10, 12] a = x.slice(2)
# 4
b = x.slice(2,4)
# [4, 6, 8, 10]
c = x.slice(2..4)
# [4, 6, 8]
254
Kapitola 8 – Pole, haš a ostatní výčty
Speciální metody first a last vrací první a poslední prvek pole. Pokud je pole prázdné, bude vráceno nil, viz následující fragment kódu: x = %w[alpha beta gamma delta epsilon] a = x.first
# "alpha"
b = x.last
# "epsilon"
Předvedli jsme vám, že některé techniky pro odkazování na prvky skutečně vrací celé podpole. Existuje pár dalších způsobů, jak hromadně přistupovat k prvkům, takže se na ně teď podíváme. Metoda values_at akceptuje seznam indexů a vrací pole skládající se pouze z těchto prvků. Toto může být použito tam, kde nelze použít rozsah (tzn. v situaci, ve které spolu nesousedí všechny prvky). V předchozí verzi Ruby byla metoda values_at nazvána jako indices, přičemž aliasem byl indexes. Tyto názvy v současné verzi Ruby nefungují. x = [10, 20, 30, 40, 50, 60] y = x.values_at(0, 1, 4)
# [10, 20, 50]
z = x.values_at(0..2,5)
# [10, 20, 30, 60]
8.1.3 – Nalezení velikosti pole Metoda length (nebo její alias size) vrací počet prvků v poli. (Jako vždy je tato hodnota o jedničku větší než index posledního prvku). x = ["a", "b", "c", "d"] a = x.length
# 4
b = x.size
# 4
Metoda nitems je úplně stejná, až na to, že nepočítá prvky nil: y = [1, 2, nil, nil, 3, 4] c = y.size
# 6
d = y.length
# 6
e = y.nitems
# 4
8.1.4 – Porovnávání polí Porovnávání polí je docela choulostivé, takže pokud to potřebujete udělat, dělejte to opatrně. Metoda instance <=> se používá pro porovnání polí. Pracuje stejně jako v jiných kontextech – vrací buď -1 (znamenající "menší než"), 0 (znamenající "rovno"), nebo 1 (znamenající "větší než"). Na této metodě jsou závislé metody == a !=. Pole jsou porovnána pěkně prvek po prvku. První dva prvky, které se nerovnají, určí nerovnost celého procesu porovnání. (To znamená, že přednost je dána prvku, který je nejvíce vlevo, stejně jako je tomu v případě, když porovnáváme dvě velká celá čísla "od oka", postupně po jedné číslici).
Ruby – kompendium znalostí pro začátečníky i profesionály
255
a = [1, 2, 3, 9, 9] b = [1, 2, 4, 1, 1] c = a <=> b
# -1 (znamená a < b)
Pokud se všechny prvky rovnají, pak se rovnají i pole. Pokud je jedno pole delší než druhé, přičemž do délky kratšího pole jsou si rovny, pak je delší pole považováno za větší. d = [1, 2, 3] e = [1, 2, 3, 4] f = [1, 2, 3] if d < e
# false
puts "d is less than e" end if d == f puts "d equals f"
# Vytiskne "d equals f"
end
Protože třída Array není šířena modulem Comparable, nejsou pro třídu definovány běžné operátory <, >, <= a >= . Ale pokud chcete, můžete je jednoduše nadefinovat sami: class Array def <(other) (self <=> other) == -1 end def <=(other) (self < other) or (self == other) end def >(other) (self <=> other) == 1 end def >=(other) (self > other) or (self == other) end) end
Pokud ovšem sami rozšíříte Array o modul Comparable, bude všechno jednodušší: class Array include Comparable end
256
Kapitola 8 – Pole, haš a ostatní výčty
Jakmile máme definované tyto nové operátory, můžeme je použít tak, jak byste očekávali: if a < b print "a < b"
# Vytiskne "a < b"
else print "a >= b" end if d < e puts "d < e"
# Vytiskne "d < e"
end
Je možné, že výsledkem porovnání polí bude porovnání dvou prvků, pro které operátor <=> není definován nebo nemá význam. Následující kód způsobí chybu za běhu (TypeError), protože porovnání 3 <=> "x" je problematické: g = [1, 2, 3] h = [1, 2, "x"] if g < h puts "g < h"
# Chyba! # Žádný výstup
end
Nicméně – v případě, že stále nejste zmateni, rovnost a nerovnost budou v tomto případě stále pracovat. To proto, že dva objekty různého typu jsou přirozeně považovány za nerovné, i když nemůžeme říct, který z nich je větší, nebo menší než druhý. if g != h puts "g != h"
# Bez problémů. # Vytiskne "g != h"
end
A konečně – je možné, že dvě pole, která obsahují neodpovídající si datové typy, budou přeci jen porovnána pomocí operátorů < a >. V takovém příkladě dostaneme výsledek ještě předtím, než narazíme na neporovnatelné prvky: i = [1, 2, 3] j = [1, 2, 3, "x"] if i < j puts "i < j"
# Bez problémů. # Vytiskne "i < j"
end
8.1.5 – Řazení polí Nejjednodušším způsobem, jak seřadit pole, je použít zabudovanou metodu sort: words = %w(the quick brown fox) list = words.sort
# ["brown", "fox", "quick", "the"]
Ruby – kompendium znalostí pro začátečníky i profesionály
257
# Nebo takto: words.sort!
# ["brown", "fox", "quick", "the"]
Tato metoda předpokládá, že všechny prvky v poli jsou porovnatelné s ostatními. Pomíchané pole, jako třeba [1, 2, "three", 4], normálně vrací chybu typu. V případě, jako je tento, můžete použít blokovou formu volání stejné metody. Následující příklad předpokládá, že existuje alespoň metoda to_s pro každý prvek (pro převedení na string): a = [1, 2, "three", "four", 5, 6] b = a.sort {|x,y| x.to_s <=> y.to_s} # b is now [1, 2, 5, 6, "four", "three"]
Je samozřejmé, že takové řazení (v tomto případě závisející na ASCII) nemusí být smysluplné. Pokud máte takové různorodé pole, zeptejte se prvně sami sebe, proč jej vlastně řadíte nebo proč vůbec ukládáte objekty různých typů. Tato technika funguje, protože blok vrací celé číslo (-1, 0 nebo 1) při každém volání. Když je vráceno číslo -1, znamená to, že x je menší než y; dva prvky jsou zaměněny. Takže pro řazení v sestupném pořadí můžeme jednoduše prohodit pořadí porovnání: x = [1, 4, 3, 5, 2] y = x.sort {|a,b| b <=> a}
# [5, 4, 3, 2, 1]
Blok může být také použit pro komplexnější řazení. Předpokládejme, že chceme seřadit seznam knih a filmových titulů tímto způsobem – ignorovat velikost písmen, zcela ignorovat mezery a ignorovat jistý druh vložené interpunkce. Zde prezentujeme jednoduchý příklad. (Věříme, že jak učitelé angličtiny, tak i programátoři budou zmateni z tohoto druhu řazení podle abecedy). titles = ["Starship Troopers", "A Star is Born", "Star Wars", "Star 69", "The Starr Report"] sorted = titles.sort do |x,y| # Smaže členy (a, an, the) a = x.sub(/^(a |an |the )/i, "") b = y.sub(/^(a |an |the )/i, "") # Smaže mezery a interpunkci a.delete!(" .,-?!") b.delete!(" .,-?!") # Převede na velká písmena a.upcase! b.upcase! # Porovná a a b a <=> b
258
Kapitola 8 – Pole, haš a ostatní výčty
end # Výsledek je nyní: # [ "Star 69", "A Star is Born", "The Starr Report" #
"Starship Troopers", "Star Wars"]
Tento příklad není příliš použitelný a určitě by mohl být napsán kompaktněji. Pointa je v tom, že při porovnání dvou operandů může být na nich vykonán libovolně složitý soubor operací. (Nicméně si povšimněte, že originální operandy jsme nechali nedotčeny; pracovali jsme s jejich kopiemi.) Tato metoda může být užitečná v mnoha situacích – například při řazení podle několika klíčů nebo podle klíčů, které jsou vypočítány až při běhu programu. V novějších verzích Ruby obsahuje modul Enumerable metodu sort_by, která je samozřejmě součástí Array. Je velmi důležité ji pochopit. Metoda sort_by používá to, co lidé od Perlu nazývají Schwartzovou transformací (podle Randala Schwartze). Místo abychom řadili podle prvků samotných, aplikujeme na ně nějakou funkci nebo mapování a řadíme podle výsledku. Představte si, že máte seznam souborů, který chcete seřadit podle velikosti. Přímočarý způsob by vypadal nějak takto: files = files.sort {|x,y| File.size(x) <=> File.size(y) }
Nicméně jsou zde dva problémy. Zaprvé – vypadá to trochu ukecaně. Měli bychom být schopni tento fragment kódu trochu zestručnit. Zadruhé – dochází k vícenásobnému přístupu na disk, což je docela drahá operace (ve srovnání s jednoduchými operacemi v paměti). Čím více takových operací, tím hůře. Použitím metody sort_by ovšem vyřešíme oba tyto problémy najednou. Zde je správný způsob, jak to udělat: files = files.sort_by {|x| File.size(x) }
V předchozím fragmentu kódu je každý klíč počítán pouze jednou. Výsledek je následně interně uložen jako dvojice klíč/data. Ačkoliv v případě menších polí může mít tento způsob efektivitu naopak nižší, může zvýšení čitelnosti kódu i tak stát za to. Metoda sort_by! neexistuje. Nicméně si můžete vždy napsat svou vlastní. A co řazení na základě více klíčů? Představte si, že máte pole objektů, která potřebujete seřadit na základě těchto tří atributů – jméno, věk a výška. Skutečnost, že pole jsou vzájemně porovnatelná, znamená, že následující technika bude funkční: list = list.sort_by {|x| [x.name, x.age, x.height] }
Je samozřejmé, že nejste omezení pouze na jednoduché prvky pole, které byly použity ve výše uvedených příkladech. Prvkem pole může být libovolný výraz.
Ruby – kompendium znalostí pro začátečníky i profesionály
259
8.1.6 – Výběr z pole na základě kritéria Někdy chceme lokalizovat prvek (nebo prvky) v poli podobným způsobem, jako se dotazujeme na tabulky v databázi. Existuje několik způsobů, jak to udělat. Všechny níže nastíněné způsoby pochází z modulu Enumerable. Metoda detect nalezne nanejvýš jediný prvek. Akceptuje blok (ve kterém jsou prvky procházeny sekvenčně), přičemž vrací první prvek, pro nějž je výraz v bloku pravdivý. x = [5, 8, 12, 9, 4, 30] # Najde první násobek 6 x.detect {|e| e % 6 == 0 }
# 12
# Najde první násobek 7 x.detect {|e| e % 7 == 0 }
# nil
Objekty v poli mohou být samozřejmě libovolně složité, stejně jako test v bloku. Metoda find je synonymem pro metodu detect. Metoda find_all je varianta, která vrací i více prvků. Metoda select je pak synonymem pro metodu find_all: # Pokračování předchozího příkladu... x.find {|e| e % 2 == 0}
# 8
x.find_all {|e| e % 2 == 0}
# [8, 12, 4, 30]
x.select {|e| e % 2 == 0}
# [8, 12, 4, 30]
Metoda grep volá operátor rovnosti case pro porovnání každého prvku s daným vzorem. V nejjednodušší formě vrací pole obsahující shodující se prvky. Protože je použit operátor rovnosti case (===), vzor nemusí být regulárním výrazem. (Název grep samozřejmě pochází ze světa Unixu a historicky souvisí s příkazem g/re/p). a = %w[January February March April May] a.grep(/ary/)
# ["January, "February"]
b = [1, 20, 5, 7, 13, 33, 15, 28] b.grep(12..24)
# [20, 13, 15]
Existuje bloková forma, která transformuje každý výsledek ještě předtím, než ho uloží do pole. Výsledné pole pak obsahuje návratové hodnoty bloku, nikoliv hodnoty, které byly do bloku předány: # Pokračování předchozího příkladu... # Pojďme uložit délky řetězců a.grep(/ary/) {|m| m.length}
# [7, 8]
# Pojďme umocnit každou hodnotu b.grep(12..24) {|n| n*n}
# {400, 169, 225}
Metoda reject je doplňková (komplementární) k metodě select. Vylučuje každý prvek, který blok vyhodnotí jako true. Je také definována metoda reject!:
KAPITOLA 12 Grafická rozhraní pro Ruby Není nic horšího, než ostré zobrazení neurčitého konceptu. – Ansel Adams Nejsou žádné pochybnosti o tom, že se nacházíme ve věku grafického uživatelského rozhraní (GUI). Je zřejmé, že v budoucnu bude preferovaným způsobem interakce s počítačem nějaká forma grafického rozhraní. Nemyslím si ovšem, že by v dalším desetiletí měl vymizet příkazový řádek – ten má jisté své místo ve světě. Ale dokonce i hackeři ze staré školy (kteří by raději používali příkaz cp –R než rozhraní drag-and-drop) někdy používají GUI, když je to vhodné. S programováním grafiky se neodmyslitelně pojí různé významné problémy. První problém pochopitelně spočívá v návrhu smysluplného a použitelného prostředí programu. V návrhu uživatelského rozhraní nemá obrázek vždy cenu tisíce slov. Tato kniha rozhodně nemůže pojmout celou problematiku tvorby GUI. Naším cílem zde není řešit ergonomii, estetiku či psychologii. Druhým obvyklým problémem je to, že programování grafiky je složitější. Musíme si totiž dělat starosti o velikost, tvary, umístění a chování všech ovládacích prvků, které mohou být zobrazeny na obrazovce, jež mohou být ovládány myší a/nebo klávesnicí. Třetí potíž spočívá v tom, že různé počítačové kultury mají odlišné představy o tom, co je systém oken a jak by měl být implementován. Nesourodost mezi těmito systémy musí být prvně vyzkoušena, aby mohla být následně plně pochopena. Nejeden programátor se pokoušel vytvořit nějaký multiplatformní nástroj, aby nakonec zjistil, že tou nejtěžší částí je přizpůsobení GUI. Tato kapitola vám s těmito problémy příliš pomoci nemůže. Maximum, co pro vás mohu udělat, je poskytnout úvod k několika populárním GUI systémům, a nabídnout několik cenných rad a postřehů. Převážná část této kapitoly je věnována systémům Tk, GTK+, FOX a Qt. Ačkoliv se jedná o nejvíce rozšířené systémy, je docela slušná šance, že se zeptáte: "Proč v této kapitole nebyl popsán (sem vložte název vašeho oblíbeného GUI)?" Důvodů může být několik. Jedním důvodem je omezený prostor, protože tato kniha primárně není o grafickém rozhraní. Dalším důvodem může být to, že váš oblíbený systém nemá natolik vyspělé
446
Kapitola 12 – Grafická rozhraní pro Ruby
Ruby rozhraní, aby se dal použít. A posledním důvodem může být fakt, že ne všechny systémy uživatelského rozhraní jsou si rovny. Tato kapitola se tudíž snaží pokrýt pouze ty, které jsou nejdůležitější a nejvíce vyspělé. O zbytku se zmíníme pouze krátce.
12.1 – Ruby/Tk Kořeny Tk sahají až do roku 1988 (pokud počítáme různé předběžně uvolněné verze). Dlouho bylo považováno za společníka k programovacímu jazyku Tcl, nicméně Tk se začalo používat i s několika dalšími jazyky, včetně Perlu a Ruby. Pokud by Ruby někdy mělo nějaké nativní GUI, pravděpodobně by se jednalo o Tk. V době psaní této knihy je velmi široce používáno, přičemž některé verze Ruby lze stáhnout včetně Tk. Předchozí zmínka o Perlu není úplně bezdůvodná. Tk pro Ruby a Perl je dost podobné na to, aby materiál pro Perl/Tk mohl být z velké části použitelný i pro Ruby/Tk. Doporučujeme si sehnat zajímavou knihu Learning Perl/Tk, ISBN 1565923146 od Nancy Walsh.
12.1.1 – Přehled V roce 2001 bylo Tk pravděpodobně nejvíce používaným GUI pro Ruby. Bylo první, které bylo dostupné a dlouho bylo i součástí standardní instalace Ruby. I když v současnosti už není tak populární jako předtím, je stále široce používáno. Někteří vývojáři říkají, že na Tk je patrný jeho věk – pro ty, co mají rádi čisté, objektově orientované, rozhraní, může být rozhraní Tk trochu zklamání. Ale má výhodu v popularitě, přenositelnosti a stabilitě. Jakákoliv aplikace Ruby/Tk musí použít příkaz require pro načtení rozšíření tk. Poté je aplikační rozhraní sestavováno postupně, počínaje nějakým druhem kontejneru a ovládacích prvků, kterými je tento kontejner naplněn. Nakonec je provedeno volání Tk.mainloop (tato metoda zachytává všechny události – jako pohyb myši a stisky tlačítek – a zpracovává je). require "tk" # Sestavení aplikace... Tk.mainloop
Stejně jako ve většině (nebo rovnou ve všech) okenních systémech jsou i zde ovládací prvky Tk nazývány widgety. Tyto widgety jsou obvykle dohromady seskupeny v kontejnerech. Kontejner na nejvyšší úrovni je nazýván kořen. Ačkoliv tento kořen není nutné specifikovat explicitně, rozhodně je to vhodné. Každá třída widgetu je pojmenována na základě svého názvu ve světě Tk (připojením slova Tk na začátek). Proto widget Frame odpovídá třídě TkFrame. Widgety jsou instanciovány prostřednictvím metody new. První parametr specifikuje kontejner, do kterého je widget umístěn. Pokud je vynechán, předpokládá se kořen.
Ruby – kompendium znalostí pro začátečníky i profesionály
447
Nastavení, které je použito pro instanciaci widgetu, může být specifikováno dvěma způsoby. První způsob (jako v Perlu) spočívá v předání haše s vlastnostmi a hodnotami. (Připomínáme, že se jedná o trik syntaxe Ruby – haš, který je vložen jako poslední nebo jako jediný parametr, může mít závorky vynechány.) my_widget = TkSomewidget.new( "borderwidth" => 2, "height" => 40 , "justify" => "center" )
Další způsob spočívá v předání bloku do konstruktoru, který bude vyhodnocen pomocí instance_eval. Uvnitř tohoto bloku můžeme volat metody pro nastavení vlastností widgetu (prostřed-
nictvím metod, které mají stejné názvy jako vlastnosti). Mějte na paměti, že blok kódu je vyhodnocován v kontextu objektu, nikoliv volajícího. To například znamená to, že proměnné instance volajícího nemohou být odkazovány uvnitř tohoto bloku. my_widget = TkSomewidget.new do borderwidth 2 height 40 justify "center" end
V Tk jsou dostupní celkem tři správci rozložení – všichni slouží pro účely řízení relativní velikosti a umístění widgetu na obrazovce. První (a nejčastěji používaný) je pack. Další dva jsou grid a place: správce grid je sofistikovaný, ale poněkud náchylný k chybám; správce place je ze všech nejjednodušší, protože vyžaduje absolutní hodnoty pro umístění widgetu. Ve všech příkladech této kapitoly budeme používat pouze správce pack.
12.1.2 – Jednoduchá aplikace s okny V této sekci si předvedeme nejjednodušší možnou aplikaci – jednoduchou aplikaci kalendáře, která zobrazí aktuální datum. Abychom se dostali do programovací nálady, začneme explicitním vytvořením root a umístěním widgetu Label dovnitř něj. require "tk" root = TkRoot.new() { title "Today's Date" } str = Time.now.strftime("Today is \n%B %d, %Y") lab = TkLabel.new(root) do text str pack("padx" => 15, "pady" => 10, "side" => "top") end Tk.mainloop
V předchozím kódu vytváříme kořen, nastavujeme řetězec s datem a vytváříme popisek (label). Při vytváření popisku nastavujeme text, který je hodnotou str a voláme pack, který tohle všechno
Kapitola 12 – Grafická rozhraní pro Ruby
448
úhledně uspořádá. Správci pack sdělujeme, aby nastavil výplň 15 pixelů horizontálně a 10 pixelů vertikálně, a žádáme, aby text byl centrován na střed. Obrázek 12.1 ukazuje vzhled aplikace.
Obrázek 12.1. Jednoduchá Tk aplikace. Jak jsme už zmínili výše, vytvoření popisu (label) může vypadat takto: lab = TkLabel.new(root) do text str pack("padx" => 15, "pady" => 10, "side" => "top") end
Použitými jednotkami, které jsou v tomto příkladě specifikovány pro padx a pady, jsou standardně pixely. Pochopitelně, můžeme pracovat i v jiných jednotkách – k hodnotě stačí připojit požadovanou jednotku. Taková hodnota se sice nyní stává řetězcem, ale protože to Ruby/Tk vůbec nevadí, nevadí to ani nám. Dostupné jednotky jsou centimetry (c), milimetry (m), palce (i) a body (p). Podívejte se na následující příklad: pack("padx" => "80m") pack("padx" => "8c") pack("padx" => "3i") pack("padx" => "12p")
Atribut side v tomto případě vůbec nic neovlivňuje, protože jsme ji nastavili na výchozí hodnotu. Pokud změníte velikost okna aplikace, povšimnete si, že text se "přilepí" do horní části oblasti, ve které je umístěn. Jak asi tušíte, další možné hodnoty jsou right, left a bottom. Metoda pack poskytuje několik dalších voleb, které řídí umístění widgetu na obrazovce:
Volba fill specifikuje, zdali widget vyplňuje svůj přidělený obdélník (v horizontálním a/ nebo vertikálním směru). Možné hodnoty jsou x, y, both a none (výchozí je none).
Volba anchor bude ukotvovat widget uvnitř přiděleného obdélníku na základě "kompasové" notace. Výchozí je center; další možné hodnoty jsou n, s, e, w, ne, nw, se a sw.
Volba in zabaluje widget s ohledem na nějaký jiný kontejner, než je rodičovský. Výchozí je samozřejmě rodič.
Volby before a after mohou být použity pro změnu pořadí widgetu. To je občas užitečné, protože widgety nemusí být vytvořeny (na rozdíl od jejich umístění na obrazovce) v nějakém konkrétním pořadí.
Celkově můžeme říci, že Tk je docela flexibilní z hlediska umístění widgetů na obrazovce. Prostudujte si dokumentaci pro další informace.
Ruby – kompendium znalostí pro začátečníky i profesionály
449
12.1.3 – Práce s tlačítky Jedním z nejběžnějších widgetů v jakémkoliv GUI je stisknutelné tlačítko (nebo jednoduše tlačítko). Jak asi očekáváte, v aplikacích Ruby/Tk umožňuje použití tlačítek třída TkButton. V případě složitějších aplikací obvykle vytváříme rámce, které obsahují různé widgety, jež poté umisťujeme na obrazovku. V těchto kontejnerech mohou být umístěny i widgety ve formě tlačítka. Pro tlačítko musíme specifikovat nejméně tři následující vlastnosti:
Text tlačítka.
Příkaz spojený s tlačítkem. Tento příkaz bude proveden, když je tlačítko stisknuto.
Pozice tlačítka uvnitř jeho kontejneru.
Tady je malá ukázka: btn_OK = TkButton.new do text "OK" command (proc { puts "The user says OK." }) pack("side" => "left") end
V tomto fragmentu kódu vytváříme nové tlačítko a přiřazujeme nový objekt do proměnné btn_OK. Do konstruktoru předáváme blok, ačkoliv bychom mohli použít i haš. V tomto případě používáme víceřádkovou formu (kterou osobně preferuji, ačkoliv v praxi můžete do jednoho řádku nacpat tolik kódu, kolik jenom chcete. Zapamatujte si, že blok je prováděn pomocí instance_eval, což znamená, že je vyhodnocen v kontextu objektu (v tomto případě nového objektu TkButton). Text, který je specifikován ve formě parametru v metodě text, bude umístěn na samotné tlačítko. Může to být několik slov nebo dokonce i několik řádků. Použití metody pack už jsme viděli. Není zajímavá, ačkoliv je nepostradatelná, pokud má být widget vůbec viditelný. Zajímavou částí kódu je metoda command, která přijímá objekt Proc a připojuje ho k tlačítku. V této kapitole budeme často používat metodu lambdaproc z modulu Kernel, která zkonvertuje blok na objekt Proc. Akce, kterou zde provádíme, je ovšem dosti hloupá. Když uživatel stiskne tlačítko, bude provedeno negrafické puts. To znamená, že výstup půjde do okna příkazového řádku, ze kterého byl spuštěn program (nebo do pomocného okna konzoly). Nyní vám nabídneme lepší příklad. Výpis 12.1 ukazuje aplikaci termostatu, která bude zvyšovat nebo snižovat zobrazenou hodnotu (čímž nám poskytne iluzi toho, že můžeme ovládat teplotu v místnosti). Vysvětlení následuje, jako vždy, až za výpisem. Výpis 12.1. Simulovaný termostat. require 'tk' # Běžné nastavení... Top = { 'side' => 'top', 'padx'=>5, 'pady'=>5 }
450
Kapitola 12 – Grafická rozhraní pro Ruby
Left = { 'side' => 'left', 'padx'=>5, 'pady'=>5 } Bottom = { 'side' => 'bottom', 'padx'=>5, 'pady'=>5 } temp = 74 # Počáteční teplota... root = TkRoot.new { title "Thermostat" } top = TkFrame.new(root) { background "#606060" } bottom = TkFrame.new(root) tlab = TkLabel.new(top) do text temp.to_s font "{Arial} 54 {bold}" foreground "green" background "#606060" pack Left end TkLabel.new(top) do
# symbol"stupně"
text "o" font "{Arial} 14 {bold}" foreground "green" background "#606060" # Přidat anchor-north do hash pack Left.update({ 'anchor' => 'n' }) end TkButton.new(bottom) do text " Up " command proc { tlab.configure("text"=>(temp+=1).to_s) } pack Left end TkButton.new(bottom) do text "Down" command proc { tlab.configure("text"=>(temp-=1).to_s) } pack Left end top.pack Top bottom.pack Bottom Tk.mainloop
Ruby – kompendium znalostí pro začátečníky i profesionály
451
Pomocí toho kódu vytváříme dva rámce. Horní rámec ovládá pouze zobrazení. Teplotu ve Fahrenheitech zobrazujeme prostřednictvím velkého písma (pro znak stupně používáme malé, strategicky umístěné, písmenko "o"). Spodní rámec je pak použit pro tlačítka "nahoru" a "dolů". Povšimněte si, že používáme několik nových vlastností pro objekt TkLabel. Metoda font specifikuje písmo a velikost textu. Hodnota řetězce je závislá na platformě. Ta, která je ukázána zde, je platná pro systém Windows. V systému Unix by se obvykle jednalo o dlouhý a těžkopádný název fontu ve stylu -Adobe-Helvetica-Bold-R-Normal–*-120-*-*-*-*-*-*. Metoda foreground nastavuje barvu textu. V našem příkladu vkládáme řetězec "green", který má v útrobách Tk předem definovaný význam. (Pokud chcete zjistit, zdali je v Tk předdefinována nějaká barva, nejsnazší cestou je to vyzkoušet). Barva pozadí se nastavuje pomocí metody background. V tomto případě specifikujeme barvu odlišně, protože ji chceme v typickém hexadecimálním formátu (řetězec "#606060" reprezentuje pěknou šedou barvu). Abychom nekomplikovali pěkný jednoduchý design, nepřidali jsme do něj žádné tlačítko "Konec". Aplikaci můžete jako obvykle zavřít kliknutím na ikonu Close v pravém horním rohu okna. V příkazech pro tlačítka je použita metoda configure, kterou se mění obsah horního popisku při zvyšování nebo snižování aktuální teploty. Jak už jsem zmínil dříve, tímto způsobem může být za běhu změněna jakákoliv vlastnost (přičemž její efekt se okamžitě projeví na monitoru). Nyní zmíníme dva další triky, které můžete s textovými tlačítky provést. Metoda justify přijímá parametr (left, right nebo center), kterým můžete specifikovat umístění textu na tlačítku (výchozí hodnota je center). Tlačítko může obsahovat i několik řádků textu – metoda wraplength specifikuje sloupec, ve kterém dojde k zalomení textu. Styl tlačítka může být změněn prostřednictvím metody relief, která mu dá 3D vzhled. Parametrem této metody musí být jeden z těchto řetězců: flat, groove, raised, ridge (výchozí), sunken nebo solid. Metody width a height explicitně nastavují velikost tlačítka. Je také dostupná metoda borderwidth. Pro další volby – kterých je opravdu mnoho – se podívejte do dokumentace. Pojďme se nyní podívat na některé další příklady. Naše nová tlačítka na sobě nebudou mít text, ale pouze obrázek. Pro tyto účely jsem předem vytvořil dvojici obrázků ve formátu GIF, které obsahují šipky nahoru a dolů. (Tyto grafické soubory jsou pojmenovány vskutku intuitivně – up.gif a down.gif.) Pro získání reference na každý z nich můžeme použít třídu TkPhotoImage. Poté můžeme tyto reference použít při instanciaci tlačítek. up_img = TkPhotoImage.new("file"=>"up.gif") down_img = TkPhotoImage.new("file"=>"down.gif") TkButton.new(bottom) do image up_img command proc { tlab.configure("text"=>(temp+=1).to_s) } pack Left end
452
Kapitola 12 – Grafická rozhraní pro Ruby
TkButton.new(bottom) do image down_img command proc { tlab.configure("text"=>(temp-=1).to_s) } pack Left end
Tímto kódem jednoduše nahraďte odpovídající řádky v našem prvním příkladu s termostatem. S výjimkou změněných tlačítek je všechno stejné. Obrázek 12.2 ukazuje aplikaci termostatu.
Obrázek 12.2. Simulace termostatu (s grafickými tlačítky).
12.1.4 – Práce s textovými poli Vstupní textové pole může být zobrazeno a spravováno použitím widgetu TkEntry. Jak asi očekáváte, pro ovládání velikosti, barvy a chování tohoto widgetu je dostupných mnoho voleb; my vám nabídneme jeden rozsáhlý příklad, který ilustruje několik z nich. Vstupní pole je užitečné pouze v případě, kdy z něj dokážeme získat hodnotu, která byla do něj vložena. Pole bude vázáno na proměnnou TkVariable (jak uvidíme později sami), ačkoliv může být použita i metoda get. Předpokládejme, že chceme vytvořit telnet klienta, který bude přijímat čtyři informace – hosta, číslo portu (standardně 23), uživatelské ID a heslo. Přidáme také dvě tlačítka pro operaci "přihlášení" a"zrušení". Následující kus kódu provádí nějaké triky s rámci pro seřazení věcí a pro jejich lepší vzhled. Kód není napsán přenositelným způsobem a skutečný Tk guru by tento přístup odsoudil. Ale protože se jedná pouze o ilustrační ukázku, tato skutečnost nás vůbec netrápí. Na obrázku 12.3 je zobrazena již dokončená aplikace. Její kód je pak uveden ve výpisu 12.2.
Obrázek 12.3. Simulovaný telnet klient.
KAPITOLA 19 Ruby a webové aplikace Ó, jak zamotanou síť jsme to upředli...! – Sir Walter Scott, Píseň posledního skotského barda Ruby je univerzální jazyk; nemůže být nazýván "jazykem webu". Webové aplikace (a webové nástroje obecně) ovšem patří mezi nejběžnější využití Ruby. Existuje mnoho způsobů, jak provádět vývoj webových aplikací v Ruby – od knihoven, které jsou malé a nízkoúrovňové, až po rozsáhlé frameworky (pracovní rámce), jež určují váš styl myšlení a kódování. A začněme na spodním konci – podíváme se na knihovnu cgi.rb, která je standardem v Ruby.
19.1 – CGI programování v Ruby Libovolný člověk obeznámený s programováním webových aplikací nepochybně již slyšel o termínu CGI (Common Gateway Interface). CGI bylo vytvořeno brzy po vzniku samotného webu, aby mohly existovat programově implementované stránky, a pro dosažení větší interakce mezi koncovým uživatelem a webovým serverem. Ačkoliv od doby jeho vzniku bylo představeno nespočetné množství náhradních technologií, CGI ve světě webu stále žije a daří se mu dobře. Velká část úspěchu a dlouhověkosti CGI může být přisuzována jeho jednoduchosti. Kvůli této jednoduchosti je například snadné implementovat CGI programy v libovolném jazyce. Standard CGI specifikuje, jakým způsobem bude proces webového serveru předávat data mezi sebou a jeho potomky. Většina této interakce se děje skrze standardní proměnné prostředí a proudy v operačním systému. CGI programování (a pro úplnost – i samotné HTTP) je založeno na mechanismu bezstavového požadavku a odpovědi. Obecně lze říci, že jakmile je ustanoveno jediné TCP spojení, klient (což je obvykle webový prohlížeč) zahájí konverzaci prostřednictvím jednoduchého HTTP příkazu. Dva nejběžněji používané příkazy v tomto protokolu jsou GET a POST (k jejich významu se dostaneme za chvíli). Po vydání příkazu webový server odpovídá a zavírá výstupní proud.
Kapitola 19 – Ruby a webové aplikace
660
Následující ukázka kódu, která je o něco málo pokročilejší než standardní "Hello, World!", názorně ukazuje, jak provést vstup a výstup přes CGI. def parse_query_string inputs = Hash.new raw = ENV['QUERY_STRING'] raw.split("&").each do |pair| name,value = pair.split("=") inputs[name] = value end inputs end inputs = parse_query_string print "Content-type: text/html\n\n" print "<HTML><BODY>" print "<B><I>Hello</I>, #{inputs['name']}!</B>" print "</BODY></HTML>"
Použití URL http://mywebserver/cgi-bin/hello.cgi?name=Dali vyprodukuje výstup "Hello, Dali!" ve vašem prohlížeči. Jak už jsem zmínil dříve, existují dva hlavní způsoby, jak přistoupit k URL – HTTP metody GET a POST. Z nedostatku místa bohužel musím dát přednost jejich jednoduchému popisu před pečlivou definicí. Metoda GET je obvykle volána při kliknutí na odkaz nebo při přímém použití URL (jako tomu bylo v předchozím příkladu). Parametry jsou předávány prostřednictvím dotazovacího řetězce URL, který je CGI programům přístupný přes proměnnou prostředí QUERY_STRING. Metoda POST se nejčastěji používá v HTML formulářích. Parametry, které jsou odeslány prostřednictvím metody POST, jsou zahrnuty v těle zprávy a nejsou viditelné v URL. CGI programům jsou doručovány přes standardní vstupní proud. Ačkoliv předchozí příklad byl velmi jednoduchý, cokoliv méně banálního by mohlo vést ke zbytečným zmatkům. Programy, které jsou potřebné pro spolupráci s několika HTTP metodami, nahráváním souborů, cookies, relacemi a dalšími složitostmi, je nejlepší řešit prostřednictvím univerzální knihovny pro práci s CGI prostředím. Ruby naštěstí poskytuje plnohodnotnou sadu tříd, které automatizují velkou část práce, již byste jinak museli dělat ručně. Spousta dalších nástrojů a knihoven se pokouší vývoj CGI zjednodušit. Mezi nejlepší z nich patří ruby-web od Patricka Maye (dříve Narf). Pokud chcete nízkoúrovňové řízení, ale standardní CGI knihovna není vaší zálibou, vyzkoušejte tuto knihovnu (k nalezení na http://ruby-web.org). Pokud chcete řešení postavené na šablonách, Amrita (http://amrita.sourceforge.jp) může být pro vás dobrým řešením. Také se podívejte na Cerise, což je webový aplikační server založený na Amritě (http://cerise.rubyforge.org). Pravděpodobně existují i další knihovny, takže pokud jste nenašli to, co jste hledali, zkuste pohledat na webu nebo se někoho zeptejte.
Ruby – kompendium znalostí pro začátečníky i profesionály
661
19.1.1 – Představení knihovny cgi.rb Knihovna CGI je umístěna v souboru cgi.rb ve standardní distribuci Ruby. Většina její funkcionality je implementována kolem centrální třídy vhodně pojmenované jako CGI. Jednou z prvních věcí, kterou budete chtít udělat při používání této knihovny, je vytvoření instance CGI. require "cgi" cgi = CGI.new("html4")
Inicializátor pro třídu CGI přijímá jeden parametr, který specifikuje úroveň HTML, jež by měla být podporována metodami pro generování HTML kódu v CGI balíčku. Tyto metody zajišťují, aby programátor nemusel řešit vkládání množství HTML kódu do jinak čistého Ruby kódu: cgi.out do cgi.html do cgi.body do cgi.h1 { "Hello Again, "} + cgi.b { cgi['name']} end end end
Zde jsme použili CGI knihovny k téměř přesné reprodukci funkcionality předchozího programu. Jak můžete sami vidět, třída CGI se stará o analýzu jakéhokoliv vstupu, přičemž výsledné hodnoty interně ukládá ve struktuře podobné haši. Takže – pokud jste specifikovali URL jako some.program.cgi?age=4, hodnota může být být zpřístupněna přes cgi['age’]. V předchozím kódu si povšimněte, že je ve skutečnosti použita pouze návratová hodnota bloku. HTML je vybudováno a uloženo postupně (tj. není okamžitě vypsáno). Jinak řečeno – spojování řetězců, které vidíme zde, je absolutně nezbytné. Bez toho by se objevil pouze poslední vyhodnocený řetězec. Třída CGI dále poskytuje užitečné mechanismy pro práci s URL-kódovanými řetězci a pro citace HTML nebo XML kódu. URL kódování je proces, který spočívá v konverzi řetězců s nebezpečnými znaky do formátu, jenž je zobrazitelný v řetězci URL. Výsledkem jsou podivně vypadající "%" řetězce, jež vidíte v některých URL, zatímco prohlížíte web. Tyto řetězce jsou ve skutečnosti numerické ASCII kódy, které jsou reprezentovány hexadecimálně s prefixem "%". require "cgi" s = "This| is^(aT$test" s2 = CGI.escape(s)
# "This%7C+is%5E%28aT%24test"
puts CGI.unescape(s2)
# Vytiskne "This| is^(aT$test"
Třída CGI může být dále použita k ošetření HTML nebo XML kódu, který by měl být zobrazen (tj. nikoliv vykonán) v prohlížeči. Například řetězec "<some_stuff>" nebude v prohlížeči zobrazen požadovaným způsobem. Pokud tedy máte potřebu doslovně zobrazovat HTML nebo XML kód
Kapitola 19 – Ruby a webové aplikace
662
v prohlížeči (například při výuce HTML), třída CGI vám nabízí podporu pro konverzi speciálních znaků do příslušných entit, viz následující ukázka: require "cgi" some_text = "<B>This is how you make text bold</B>" translated = CGI.escapeHTML(some_text) # "<B>This is how you make text bold</B>" puts CGI.unescapeHTML(translated) # Vytiskne "<B>This is how you make text bold</B>"
19.1.2 – Zobrazení a zpracování formulářů Nejběžnější způsob interakce s CGI programy je skrze HTML formuláře. HTML formuláře jsou vytvořeny pomocí specifických značek, které jsou následně přeloženy do vstupních widgetů v prohlížeči. Podrobnější diskuse k tomuto tématu je bohužel mimo rozsah této kapitoly, nicméně na webu (a také v různých knihách) lze nalézt dostatek potřebných informací. Třída CGI nabízí metody pro generování všech prvků souvisejících s HTML formuláři. Následující fragment kódu ukazuje, jak zobrazit a zpracovat HTML formulář. require "cgi" def reverse_ramblings(ramblings) if ramblings[0] == nil then return " " end chunks = ramblings[0].split(/\s+/) chunks.reverse.join(" ") end cgi = CGI.new("html4") cgi.out do cgi.html do cgi.body do cgi.h1 { "sdrawkcaB txeT" } + cgi.b { reverse_ramblings(cgi['ramblings'])} + cgi.form("action" => "/cgi-bin/rb/form.cgi") do cgi.textarea("ramblings") { cgi['ramblings'] } + cgi.submit end end end end
Tento příklad zobrazuje textovou oblast (text area) a obsah, který bude rozdělen do slov a obrácen. Například – pokud do formuláře napíšete větu "This is test", po zpracování se objeví text "test is
Ruby – kompendium znalostí pro začátečníky i profesionály
663
This". Metoda form třídy CGI může přijímat parametr method, který určuje HTTP metodu (GET, POST atd.) použitou daným formulářem. Výchozí metoda je metoda POST. Tento příklad předvedl pouze několik málo prvků, které lze použít v HTML stránce. Pro jejich kompletní seznam nahlédněte do libovolné referenční příručky o HTML.
19.1.3 – Práce s cookies HTTP je bezstavový protokol. To znamená, že poté, co prohlížeč dokončí požadavek na webovou stránku, webový server nemá žádnou možnost, jak rozlišit jeho další požadavky od jakéhokoliv jiného prohlížeče na webu. A to je okamžik, kdy na scénu přichází HTTP cookies. Cookies totiž nabízí způsob (ačkoliv poněkud neohrabaný) pro udržování stavu mezi jednotlivými požadavky ze stejného prohlížeče. Mechanismus fungování cookie je snadný. Webový server prostřednictvím HTTP záhlaví odpovědi posílá prohlížeči příkaz, aby někde uložil dvojici klíč/hodnota. Tato data mohou být uložena v paměti nebo na disku. Pro každý následující požadavek, který směřuje k doméně specifikované v této cookie, bude prohlížeč posílat data cookie v HTTP hlavičce požadavku. Ačkoliv všechna tato cookies můžete vytvářet a číst ručně, určitě je vám jasné, že něco takového není potřeba. CGI knihovny Ruby totiž poskytují třídu Cookie, která umí pracovat s cookies. require "cgi" lastacc = CGI::Cookie.new("kabhi", "lastaccess=#{Time.now.to_s}") cgi = CGI.new("html3") if cgi.cookies.size < 1 cgi.out("cookie" => lastacc) do "Hit refresh for a lovely cookie" end else cgi.out("cookie" => lastacc) do cgi.html do "Hi, you were last here at: "+ "#{cgi.cookies['kabhi'].join.split('=')[1]}" end end end
Prostřednictvím tohoto fragmentu kódu je vytvořena cookie s názvem kabhi, která obsahuje klíč lastacces nastavený na aktuální čas. Pokud má prohlížeč předchozí uloženou hodnotu pro tuto cookie, bude zobrazena. Cookies jsou přístupné jako proměnná instance na třídě CGI a uloženy jako Hash. Každá cookie může ukládat více dvojic klíč/hodnota, takže když přistoupíte k cookie jejím jménem, získáte pole.
Kapitola 19 – Ruby a webové aplikace
664
19.1.4 – Práce s relacemi uživatele Cookies jsou fajn, pokud chcete ukládat jednoduchá data a nevadí vám, že za jejich uchování je odpovědný prohlížeč. Ale v mnoha případech máte poněkud vyšší nároky na perzistenci dat. Například v případě, kdy máte k dispozici větší množství dat, která chcete trvale udržovat a jež nechcete posílat sem a tam s každým požadavkem. Co dělat v případě, kdy se jedná o nějaká citlivá data, která potřebujete asociovat s příslušnou relací a kdy nevěříte webovému prohlížeči? Pro pokročilejší perzistenci ve webových aplikacích použijte třídu CGI::Sessions. Práce s touto třídou se velmi podobá práci s třídou CGI::Cookie v tom, že hodnoty jsou uloženy a získávány přes strukturu podobnou haši. require "cgi" require "cgi/session" cgi = CGI.new("html4") sess = CGI::Session.new( cgi, "session_key" => "a_test", "prefix" => "rubysess.") lastaccess = sess["lastaccess"].to_s sess["lastaccess"] = Time.now if cgi['bgcolor'][0] =~ /[a-z]/ sess["bgcolor"] = cgi['bgcolor'] end cgi.out do cgi.html do cgi.body ("bgcolor" => sess["bgcolor"]) do "The background of this page" + "changes based on the 'bgcolor'" + "each user has in session." + "Last access time: #{lastaccess}" end end end
Přístup k "/thatscript.cgi?bgcolor=red" změní uživateli stránku na červenou pro každé následující načtení až do té doby, než bude v URL specifikována jiná barva pro bgcolor. Třída CGI:: Session je instanciována s objektem CGI a sadou nastavení v Hash. Nepovinný parametr session_key specifikuje klíč, který bude prohlížečem použit při každém požadavku pro identifikaci sama sebe. Data relace jsou uložena v dočasných souborech pro každou relaci, přičemž parametr prefix specifikuje řetězec, kterým bude začínat název souboru, což učiní vaši relaci snadno identifikovatelnou v souborovém systému serveru.
Ruby – kompendium znalostí pro začátečníky i profesionály
665
Třída CGI::Session v současnosti stále postrádá spoustu užitečných rysů – například schopnost ukládat objekty (nikoliv pouze řetězce), ukládat relaci napříč několika servery atd. Dnes již ovšem existuje plugin database_manager, který může zjednodušit implementaci některých těchto rysů. Pokud děláte cokoliv vzrušujícího s CGI::Session, rozhodně se o to podělte s ostatními.
19.2 – Používání FastCGI Nejčastěji kritizovaný nedostatek CGI je ten, že pro každé nové volání vyžaduje vytvoření nového procesu. To má významný vliv na výkon. Neschopnost ponechat objekty v paměti mezi jednotlivými požadavky může mít negativní dopad i na návrh. Kombinace těchto potíží vedla k vytvoření FastCGI. FastCGI v podstatě není nic víc než definice protokolu a softwarová implementace tohoto protokolu. FastCGI je obvykle implementováno ve formě pluginu webového serveru (například ve formě modul pro Apache), přičemž v rámci běžících procesů umožňuje zachytávat HTTP požadavků, které jsou přes socket přesměrovávány k trvale běžícímu procesu na pozadí. Tento přístup má pozitivní vliv na rychlost, zejména ve srovnání s tradičním spouštěním nového procesu pro obsloužení požadavku. Dále poskytuje programátorovi možnost ukládat věci do paměti (a při dalším požadavku je tam samozřejmě najít). Servery pro FastCGI byly implementovány v mnoha programovacích jazycích včetně Ruby. Eli Green vytvořil modul, který je kompletně vytvořen v Ruby, jenž implementuje protokol FastCGI a který rapidně zjednodušuje vývoj FastCGI programů. Aniž bychom zacházeli do podrobnějších detailů, ve výpisu 19.1 prezentujeme vzorovou aplikaci. Jak sami vidíte, tento kousek kódu poskytuje funkcionalitu shodnou s předchozím příkladem. Výpis 19.1. Vzorová aplikace ve FastCGI. require "fastcgi" require "cgi" last_time = "" def get_ramblings(instream) # Nepěkně získá hodnotu prvního páru jmého/hodnota # CGI to pro nás může udělat. data = "" if instream != nil data = instream.split("&")[0].split("=")[1] || "" end return CGI.unescape(data) end def reverse_ramblings(ramblings)
666
Kapitola 19 – Ruby a webové aplikace
if ramblings == nil then return "" end chunks = ramblings.split(/\s+/) chunks.reverse.join(" ") end server = FastCGI::TCP.new('localhost', 9000) begin server.each_request do |request| stuff = request.in.read out = request.out out << "Content-type: text/html\r\n\r\n" out << <<-EOF <html> <head><title>Text Backwardizer</title></head> <h1>sdrawkcaB txeT</h1> <i>You previously said: #{last_time}</i><BR> <b>#{reverse_ramblings(get_ramblings(stuff))}</b> <form method="POST" action="/fast/serv.rb"> <textarea name="ramblings"> </textarea> <input type="submit" name="submit" </form> </body></html> EOF last_time = get_ramblings(stuff) request.finish end ensure server.close end
První věci, které si na tomto kódu nepochybně povšimnete (pokud jste četli předchozí sekci), je několik detailů, které musíte ve FastCGI udělat ručně, a které byste tato nemuseli dělat při použití CGI. (Jedním takovým detailem je například zadrátovaný HTML kód.) Druhou věcí je metoda get_ramblings, která ručně analyzuje vstup a vrací pouze relevantní hodnoty. Tento kód mimochodem pracuje pouze s HTTP metodou POST, což je jedna z dalších nevýhod, na kterou narazíte při nepoužívání knihovny CGI. Použití FastGCi samozřejmě s sebou nese i nějaké výhody. Ačkoliv v tomto příkladu nespouštíme žádné srovnávací testy, FastCGI je rychlejší než normální CGI. Například režii pro vytvoření nového procesu jsme ušetřili ve prospěch vytvoření spojení v místní síti na port 9000 (FastCGI:: TCP.new('localhost', 9000)). A dále – proměnná last_time byla v tomto příkladu použita k uchování stavu v paměti mezi jednotlivými požadavky. Toto je v tradičním CGI nemožné.
Ruby – kompendium znalostí pro začátečníky i profesionály
667
Dále zde chci poukázat na skutečnost, že je možné tyto knihovny využívat pouze částečně. Pomocné funkce z cgi.rb mohou být použity samostatně (tj. bez použití této knihovny pro řízení aplikace). Například funkce CGI.escapeHTML může být použita izolovaně od zbytku knihovny. Toto by udělalo předchozí příklad o něco čitelnější.
19.3 – Ruby on Rails Jedním z nejznámějších webových frameworků ve světě Ruby je nepochybně Ruby on Rails (nebo jednoduše Rails). Tento framework je dílem Davida Heinemeiera Hansona. Rails využívá dynamických rysů Ruby. Má svou vlastní filozofii návrhu a umožňuje rychlý vývoj webových aplikací. Rails je nejenom velmi dobře známý, ale také dobře zdokumentovaný. V této knize se mu věnujeme pouze zběžně.
19.3.1 – Principy a technologie Rails je vybudován na paradigmatu návrhového vzoru MVC (Model-View-Controller). Každá webová aplikace, která je postavena na Rails, je přirozeně rozdělena do modelů (jež modelují doménu problému), pohledů (které prezentují informaci uživateli a umožňují interakci) a ovladačů (jež zajišťují spojení mezi pohledy a modely). Chování Rails jako frameworku je založeno na několika principech. Jeden princip je méně softwaru – nepište kód k tomu, aby navázal jednu věc na jinou, pokud tyto věci mohou být dohromady navázány automaticky. Další příbuzný princip je přednost konvence před konfigurací. Následováním určitých předdefinovaných stylů programování a pojmenovávání se konfigurace stává méně důležitou, čímž se tak přiblížíte k ideálnímu prostředí s "nulovou konfigurací". Rails je dobrý v automatizaci úkolů, které vyžadují omezenou úroveň inteligence. Generuje kód vždy, když je to praktické, takže programátor není nucen vytvářet tyto kousky kódu ručně. Protože webové aplikace se mnohdy neobejdou bez nějaké databáze v pozadí, Rails nabízí hladkou integraci databází. Existuje rovněž silná tendence, aby webové frameworky měly svůj oblíbený ORM (object-relational mapper) a Rails v tomto ohledu není výjimkou. Výchozí ORM pro Rails je ActiveRecord, což jsme si popsali již v kapitole 10. Databáze jsou popsány v souboru config/database.yaml, což je jeden z mála konfiguračních souborů, které budete potřebovat. Tento soubor, který je samozřejmě uložen ve formátu YAML, specifikuje tři různé databáze – jednu pro vývoj, jednu pro testování a jednu pro ostrý provoz. Ačkoliv tyto tři vyhrazené databáze mohou na první pohled vypadat nesmyslně, toto schéma je tím, co dává Rails jeho sílu. Rails pro vás generuje prázdné modely a ovladače (controllers). Když tyto modely editujete, definujete vztah mezi databázovými tabulkami metodami, jako například has_many a belongs_to (abych uvedl aspoň dvě). Protože modely a tabulky vzájemně korespondují, tento kód rovněž definuje vztah mezi samotnými modely. Platnost dat může být bezproblémová s metodami jako va-