Your SlideShare is downloading. ×
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Rails. Zaawansowane programowanie
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

Rails. Zaawansowane programowanie

653

Published on

Twórz zaawansowane projekty w Rails! …

Twórz zaawansowane projekty w Rails!



* Jak zadbać o bezpieczeństwo?
* Jak zapewnić wydajność Twojej aplikacji?
* Jak stworzyć i utrzymać duży projekt w Rails?

Ruby on Rails przebojem wdarł się na rynek szkieletów aplikacji internetowych. Stworzony w architekturze MVC z wykorzystaniem popularnego języka Ruby, został entuzjastycznie przyjęty przez społeczność programistów. Główne założenia autora tego projektu, Davida Heinemeiera Hanssona, to szybkość, łatwość i przyjemność tworzenia kodu. Ruby on Rails jest dojrzałym rozwiązaniem, wykorzystywanym przez wiele firm w aplikacjach internetowych, tworzonych pod kątem ich specyficznych potrzeb. Liczba aplikacji, które powstały z wykorzystaniem tego szkieletu, świadczy o jego wysokiej jakości oraz niewątpliwie ma wpływ na wzrost popularności samego języka Ruby.

"Rails. Zaawansowane programowanie" porusza te tematy, które Wy, programiści, lubicie najbardziej! Dzięki tej książce dowiesz się, w jaki sposób wykorzystać gotowe wtyczki oraz jak stworzyć nowe. Nauczysz się stosować zaawansowane funkcje bazy danych oraz podłączać się jednocześnie do wielu baz. Po lekturze tego podręcznika bez problemu zapewnisz swojej aplikacji najwyższy poziom bezpieczeństwa, optymalną wydajność i skalowalność. Autor wskazuje tutaj również niezwykle interesujące kwestie, dotyczące projektowania dużych aplikacji, wykorzystania systemów kontroli wersji oraz utrzymywania właściwej struktury projektu.


* Przypomnienie i omówienie podstawowych elementów Ruby i Rails
* Stosowanie ActiveSupport oraz RailTies
* Zastosowanie i projektowanie wtyczek
* Zaawansowane wykorzystanie baz danych
* Uwierzytelnianie za pomocą LDAP
* Bezpieczne szyfrowanie haseł
* Bezpieczne przetwarzanie formularzy i danych użytkownika
* Zapewnienie wydajności
* Skalowanie architektury
* Wykorzystywanie usług Web
* Tworzenie wielojęzycznych aplikacji
* Zarządzanie dużymi projektami
* Używanie systemów kontroli wersji


Poznaj wszystkie funkcje Ruby on Rails!

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
653
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
3
Comments
0
Likes
0
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Rails. Zaawansowane programowanie Autor: Brad Ediger T³umaczenie: Pawe³ Gonera ISBN: 978-83-246-1724-1 Tytu³ orygina³u: Advanced Rails Format: 168x237 , stron: 336 Twórz zaawansowane projekty w Rails! • Jak zadbaæ o bezpieczeñstwo? • Jak zapewniæ wydajnoœæ Twojej aplikacji? • Jak stworzyæ i utrzymaæ du¿y projekt w Rails? Ruby on Rails przebojem wdar³ siê na rynek szkieletów aplikacji internetowych. Stworzony w architekturze MVC z wykorzystaniem popularnego jêzyka Ruby, zosta³ entuzjastycznie przyjêty przez spo³ecznoœæ programistów. G³ówne za³o¿enia autora tego projektu, Davida Heinemeiera Hanssona, to szybkoœæ, ³atwoœæ i przyjemnoœæ tworzenia kodu. Ruby on Rails jest dojrza³ym rozwi¹zaniem, wykorzystywanym przez wiele firm w aplikacjach internetowych, tworzonych pod k¹tem ich specyficznych potrzeb. Liczba aplikacji, które powsta³y z wykorzystaniem tego szkieletu, œwiadczy o jego wysokiej jakoœci oraz niew¹tpliwie ma wp³yw na wzrost popularnoœci samego jêzyka Ruby. „Rails. Zaawansowane programowanie” porusza te tematy, które Wy, programiœci, lubicie najbardziej! Dziêki tej ksi¹¿ce dowiesz siê, w jaki sposób wykorzystaæ gotowe wtyczki oraz jak stworzyæ nowe. Nauczysz siê stosowaæ zaawansowane funkcje bazy danych oraz pod³¹czaæ siê jednoczeœnie do wielu baz. Po lekturze tego podrêcznika bez problemu zapewnisz swojej aplikacji najwy¿szy poziom bezpieczeñstwa, optymaln¹ wydajnoœæ i skalowalnoœæ. Autor wskazuje tutaj równie¿ niezwykle interesuj¹ce kwestie, dotycz¹ce projektowania du¿ych aplikacji, wykorzystania systemów kontroli wersji oraz utrzymywania w³aœciwej struktury projektu. • Przypomnienie i omówienie podstawowych elementów Ruby i Rails • Stosowanie ActiveSupport oraz RailTies • Zastosowanie i projektowanie wtyczek • Zaawansowane wykorzystanie baz danych • Uwierzytelnianie za pomoc¹ LDAP • Bezpieczne szyfrowanie hase³ • Bezpieczne przetwarzanie formularzy i danych u¿ytkownika • Zapewnienie wydajnoœci • Skalowanie architektury • Wykorzystywanie us³ug Web • Tworzenie wielojêzycznych aplikacji • Zarz¹dzanie du¿ymi projektami • U¿ywanie systemów kontroli wersji Poznaj wszystkie funkcje Ruby on Rails!
  • 2. Spis tre ci Wstýp ........................................................................................................................................5 1. Techniki podstawowe ...................................................................................................9 Czym jest metaprogramowanie? 9 Podstawy Ruby 12 Techniki metaprogramowania 30 Programowanie funkcyjne 41 Przykäady 46 Propozycje dalszych lektur 49 2. ActiveSupport oraz RailTies ........................................................................................ 51 Ruby, jakiego nie znamy 51 Jak czytaè kod? 54 ActiveSupport 61 Core Extensions 65 RailTies 79 Propozycje dalszych lektur 81 3. Wtyczki Rails ................................................................................................................83 Wtyczki 83 Tworzenie wtyczek 87 Przykäad wtyczki 89 Testowanie wtyczek 94 Propozycje dalszych lektur 97 4. Bazy danych .................................................................................................................99 Systemy zarzñdzania bazñ danych 99 Du e obiekty (binarne) 104 Zaawansowane funkcje baz danych 112 Podäñczanie do wielu baz danych 118 Buforowanie 120 Wyrównywanie obciñ enia i wysoka dostöpno è 121 LDAP 126 Propozycje dalszych lektur 127 3
  • 3. 5. Bezpiecze stwo ......................................................................................................... 129 Problemy w aplikacji 129 Problemy w sieci WWW 138 Wstrzykiwanie SQL 145 rodowisko Ruby 146 Propozycje dalszych lektur 147 6. Wydajno ë .................................................................................................................. 149 Narzödzia pomiarowe 150 Przykäad optymalizacji Rails 156 Wydajno è ActiveRecord 165 Skalowanie architektury 174 Inne systemy 181 Propozycje dalszych lektur 183 7. REST, zasoby oraz us ugi Web .................................................................................. 185 Czym jest REST? 185 Zalety architektury REST 203 REST w Rails 207 Analiza przypadku — Amazon S3 226 Propozycje dalszych lektur 230 8. i18n oraz L10n ............................................................................................................ 231 Lokalizacje 231 Kodowanie znaków 232 Unicode 233 Rails i Unicode 235 Rails L10n 243 Propozycje dalszych lektur 262 9. Wykorzystanie i rozszerzanie Rails ..........................................................................263 Wymiana komponentów Rails 263 Wykorzystanie komponentów Rails 274 Udziaä w tworzeniu Rails 279 Propozycje dalszych lektur 285 10. Du e projekty .............................................................................................................287 Kontrola wersji 287 ledzenie bäödów 298 Struktura projektu 299 Instalacja Rails 305 Propozycje dalszych lektur 311 Skorowidz ............................................................................................................................. 313 4 _ Spis tre ci
  • 4. ROZDZIA 1. Techniki podstawowe Do osiñgniöcia niezawodno ci jest wymagana prostota. Edsger W. Dijkstra Od pierwszego wydania w lipcu 2004 roku rodowisko Ruby on Rails stale zdobywa popu- larno è. Rails przyciñga programistów PHP, Java i .NET swojñ prostotñ — architekturñ model-widok-kontroler (MVC), rozsñdnymi warto ciami domy lnymi („konwencja nad kon- figuracjñ”) oraz zaawansowanym jözykiem programowania Ruby. rodowisko Rails miaäo przez pierwszy rok lub dwa säabñ reputacjö z uwagi na braki w do- kumentacji. Luka ta zostaäa wypeäniona przez tysiñce programistów, którzy korzystali ze rodowiska Ruby on Rails, wspóätworzyli je i pisali na jego temat, jak równie dziöki pro- jektowi Rails Documentation (http://railsdocumentation.org). Dostöpne sñ tysiñce blogów, które zawierajñ samouczki oraz porady na temat programowania w Rails. Celem tej ksiñ ki jest zebranie najlepszych praktyk oraz wiedzy zgromadzonej przez rodo- wisko programistów Rails i zaprezentowanie ich w äatwej do przyswojenia, zwartej formie. Poszukiwaäem ponadto tych aspektów programowania dla WWW, które sñ czösto niedoce- niane lub pomijane przez rodowisko Rails. Czym jest metaprogramowanie? Rails udostöpnia metaprogramowanie dla mas. Choè nie byäo to pierwsze zastosowanie za- awansowanych funkcji Ruby, to jednak jest ono chyba najbardziej popularne. Aby zrozumieè dziaäanie Rails, konieczne jest wcze niejsze zapoznanie siö z tymi mechanizmami Ruby, które zostaäy wykorzystane w tym rodowisku. W tym rozdziale przedstawione zostanñ podsta- wowe mechanizmy zapewniajñce dziaäanie technik przedstawianych w pozostaäych rozdzia- äach ksiñ ki. Metaprogramowanie to technika programowania, w której kod jest wykorzystywany do tworzenia innego kodu, bñd dokonania introspekcji samego siebie. Przedrostek meta (z gre- ki) wskazuje na abstrakcjö; kod wykorzystujñcy techniki metaprogramowania dziaäa jedno- cze nie na dwóch poziomach abstrakcji. Metaprogramowanie jest wykorzystywane w wielu jözykach, ale jest najbardziej popularne w jözykach dynamicznych, poniewa majñ one zwykle wiöcej funkcji pozwalajñcych na ma- nipulowanie kodem jako danymi. Pomimo tego, e w jözykach statycznych, takich jak C# lub 9
  • 5. Java, dostöpny jest mechanizm refleksji, to nie jest on nawet w czö ci tak przezroczysty, jak w jözykach dynamicznych, takich jak Ruby, poniewa kod i dane znajdujñ siö w czasie dzia- äania aplikacji na dwóch osobnych warstwach. Introspekcja jest zwykle wykonywana na jednym z tych poziomów. Introspekcja syntak- tyczna jest najni szym poziomem introspekcji — pozwala na bezpo redniñ analizö tekstu programu lub strumienia tokenów. Metaprogramowanie bazujñce na szablonach lub ma- krach zwykle dziaäa na poziomie syntaktycznym. Ten typ metaprogramowania jest wykorzystany w jözyku Lisp poprzez stosowanie S-wyra eþ (bezpo redniego täumaczenia drzewa abstrakcji skäadni programu) zarówno w przypadku kodu, jak i danych. Metaprogramowanie w jözyku Lisp wymaga intensywnego korzystania z makr, które sñ tak naprawdö szablonami kodu. Daje to mo liwo è pracy na jednym poziomie; kod i dane sñ reprezentowane w ten sam sposób, a jedynym, co odró nia kod od danych, jest to, e jest on warto ciowany. Jednak metaprogramowanie na poziomie syntaktycznym ma swoje wady. Przechwytywanie zmiennych oraz przypadkowe wielokrotne warto ciowanie jest bez- po redniñ konsekwencjñ umieszczenia kodu na dwóch poziomach abstrakcji dla tej samej prze- strzeni nazw. Choè dostöpne sñ standardowe idiomy jözyka Lisp pozwalajñce na uporanie siö z tymi problemami, to jednak sñ one kolejnymi elementami, których programista Lisp musi siö nauczyè i pamiötaè o nich. Introspekcja syntaktyczna w Ruby jest dostöpna za po rednictwem biblioteki ParseTree, która pozwala na täumaczenie kodu ródäowego Ruby na S-wyra enia1. Interesujñcym zastosowa- niem tej biblioteki jest Heckle2, biblioteka uäatwiajñca testowanie, która analizuje kod ródäowy Ruby i zmienia go poprzez modyfikowanie ciñgów oraz zmianö warto ci true na false i odwrotnie. W zaäo eniach, je eli nasz kod jest odpowiednio pokryty testami, ka da mody- fikacja kodu powinna zostaè wykryta przez testy jednostkowe. Alternatywñ dla introspekcji syntaktycznej jest dziaäajñca na wy szym poziomie introspekcja semantyczna, czyli analiza programu z wykorzystaniem struktur danych wy szego pozio- mu. Sposób realizacji tej techniki ró ni siö w ro nych jözykach programowania, ale w Ruby zwykle oznacza to operowanie na poziomie klas i metod — tworzenie, modyfikowanie i alia- sowanie metod; przechwytywanie wywoäaþ metod; manipulowanie äaþcuchem dziedzicze- nia. Techniki te sñ zwykle bardziej zwiñzane z istniejñcym kodem ni metody syntaktyczne, poniewa najczö ciej istniejñce metody sñ traktowane jako czarne skrzynki i ich implementa- cja nie jest swobodnie zmieniana. Nie powtarzaj siý Na wysokim poziomie metaprogramowanie jest przydatne do wprowadzania zasady DRY (ang. Don’t Repeat Yourself — „nie powtarzaj siö”). Zgodnie z tñ technikñ, nazywanñ równie „Raz i tylko raz”, ka dy element informacji musi byè zdefiniowany w systemie tylko raz. Powielanie jest zwykle niepotrzebne, szczególnie w jözykach dynamicznych, takich jak Ruby. Podobnie jak abstrakcja funkcjonalna pozwala nam na unikniöcie powielania kodu, który jest taki sam lub niemal taki sam, metaprogramowanie pozwala nam uniknñè podobnych kon- cepcji, wykorzystywanych w aplikacji. 1 http://www.zenspider.com/ZSS/Products/ParseTree. 2 http://rubyforge.org/projects/seattlerb. 10 _ Rozdzia 1. Techniki podstawowe
  • 6. Metaprogramowanie ma na celu zachowanie prostoty. Jednym z najäatwiejszych sposobów na zapoznanie siö z metaprogramowaniem jest analizowanie kodu i jego refaktoryzacja. Nadmiarowy kod mo e byè wydzielany do funkcji; nadmiarowe funkcje lub wzorce mogñ byè czösto wydzielone z u yciem metaprogramowania. Wzorc e projektowe definiujñ nakäadajñce siö obszary; wzorce zostaäy zaprojektowane w celu zminimalizowania liczby sytuacji, w których musimy rozwiñzywaè ten sam problem. W spoäeczno ci Ruby wzorce projektowe majñ dosyè zäñ reputacjö. Dla czö ci programistów wzorce sñ wspólnym säownikiem do opisu rozwiñzaþ powtarzajñcych siö problemów. Dla innych sñ one „przeprojektowane”. Aby byè pewnym tego, e zastosowane zostanñ wszystkie dostöpne wzorce, muszñ byè one nadu ywane. Je eli jednak bödñ u ywane rozsñdnie, nie musi tak byè. Wzorce projektowe sñ u yteczne jedynie w przypadku, gdy pozwalajñ zmniejszaè zäo ono è kognitywnñ. W Ruby czö è najbardziej szczegóäowych wzorców jest tak przezroczy- sta, e nazywanie ich „wzorcami” mo e byè nieintuicyjne; sñ one w rzeczywisto ci idiomami i wiökszo è programistów, którzy „my lñ w Ruby”, korzysta z nich bezwiednie. Wzorce powinny byè uwa ane za säownik wykorzystywany przy opisie architektury, a nie za bibliotekö wstöpnie przygotowanych rozwiñzaþ implementacji. Dobre wzorce projektowe dla Ruby znacznie ró niñ siö w tym wzglödzie od dobrych wzorców projektowych dla C++. Uogólniajñc, metaprogramowanie nie powinno byè wykorzystywane tylko do powtarzania kodu. Zawsze powinno siö przeanalizowaè wszystkie opcje, aby sprawdziè, czy inna techni- ka, na przykäad abstrakcja funkcjonalna, nie nadaje siö lepiej do rozwiñzania problemu. Jed- nak w kilku przypadkach powtarzanie kodu poprzez metaprogramowanie jest najlepszym sposobem na rozwiñzanie problemu. Je eli na przykäad w obiekcie musi byè zdefiniowanych kilka podobnych metod, tak jak w metodach pomocniczych ActiveRecord, mo na w takim przypadku skorzystaè z metaprogramowania. Pu apki Kod, który siö sam modyfikuje, mo e byè bardzo trudny do tworzenia i utrzymania. Wybra- ne przez nas konstrukcje programowe powinny zawsze speäniaè nasze potrzeby — powinny one upraszczaè ycie, a nie komplikowaè je. Przedstawione poni ej techniki powinny uzu- peäniaè zestaw narzödzi w naszej skrzynce, a nie byè jedynymi narzödziami. Programowanie wstýpujéce Programowanie wstöpujñce jest koncepcjñ zapo yczonñ z wiata Lisp. Podstawowñ koncep- cjñ w tym sposobie programowania jest tworzenie abstrakcji od najni szego poziomu. Przez utworzenie na poczñtku konstrukcji najni szego poziomu budujemy w rzeczywisto ci pro- gram na bazie tych abstrakcji. W pewnym sensie piszemy jözyk specyficzny dla domeny, za pomocñ którego tworzymy programy. Koncepcja ta jest niezmiernie u yteczna w przypadku ActiveRecord. Po utworzeniu podsta- wowych schematów i modelu obiektowego mo na rozpoczñè budowanie abstrakcji przy wy- korzystaniu tych obiektów. Wiele projektów Rails zaczyna siö od tworzenia podobnych do zamieszczonej poni ej abstrakcji modelu, zanim powstanie pierwszy wiersz kodu kontrolera lub nawet projekt interfejsu WWW: Czym jest metaprogramowanie? _ 11
  • 7. class Order < ActiveRecord::Base has_many :line_items def total subtotal + shipping + tax end def subtotal line_items.sum(:price) end def shipping shipping_base_price + line_items.sum(:shipping) end def tax subtotal * TAX_RATE end end Podstawy Ruby Zakäadamy, e Czytelnik dobrze zna Ruby. W podrozdziale tym przedstawimy niektóre z aspektów jözyka, które sñ czösto mylñce lub le rozumiane. Niektóre z nich mogñ byè Czytelnikowi znane, ale sñ to najwa niejsze koncepcje tworzñce podstawy technik metaprogramowania przedstawianych w dalszej czö ci rozdziaäu. Klasy i modu y Klasy i moduäy sñ podstawñ programowania obiektowego w Ruby. Klasy zapewniajñ mecha- nizmy hermetyzacji i separacji. Moduäy mogñ byè wykorzystywane jako tzw. mixin — zbiór funkcji umieszczonych w klasie, stanowiñcych namiastkö mechanizmu dziedziczenia wielo- bazowego. Moduäy sñ równie wykorzystywane do podziaäu klas na przestrzenie nazw. W Ruby ka da nazwa klasy jest staäñ. Dlatego wäa nie Ruby wymaga, aby nazwy klas rozpoczy- naäy siö od wielkiej litery. Staäa ta jest warto ciowana na obiekt klasowy, który jest obiektem klasy Class. Ró ni siö od obiektu Class, który reprezentuje faktycznñ klasö Class3. Gdy mówi- my o „obiekcie klasowym”, mamy na my li obiekt reprezentujñcy klasö (wraz z samñ klasñ Class). Gdy mówimy o „obiekcie Class”, mamy na my li klasö o nazwie Class, bödñcñ klasñ bazowñ dla wszystkich obiektów klasowych. Klasa Class dziedziczy po Module; ka da klasa jest równie moduäem. Istnieje tu jednak nie- zwykle wa na ró nica. Klasy nie mogñ byè mieszane z innymi klasami, a klasy nie mogñ dziedziczyè po obiektach; jest to mo liwe tylko w przypadku moduäów. Wyszukiwanie metod Wyszukiwanie metod w Ruby mo e byè dosyè mylñce, a w rzeczywisto ci jest dosyè regularne. Najprostszym sposobem na zrozumienie skomplikowanych przypadków jest przedstawienie struktur danych, jakie Ruby wewnötrznie tworzy. 3 Je eli nie jest to wystarczajñco skomplikowane, trzeba pamiötaè, e obiekt Class posiada równie klasö Class. 12 _ Rozdzia 1. Techniki podstawowe
  • 8. Ka dy obiekt Ruby4 posiada zbiór pól w pamiöci: klass Wska nik do obiektu klasy danego obiektu (zostaäa u yta nazwa klass zamiast class, poniewa ta druga jest säowem kluczowym w C++ i Ruby; je eli nazwaliby my jñ class, Ruby kompilowaäby siö za pomocñ kompilatora C, ale nie mo na byäoby u yè kompila- tora C++. Ta wprowadzona umy lnie literówka jest u ywana wszödzie w Ruby). iv_tbl „Tablica zmiennych instancyjnych” to tablica mieszajñca zawierajñca zmienne instancyjne nale ñce do tego obiektu. flags Pole bitowe znaczników Boolean zawierajñce informacje statusu, takie jak stan ladu obiektu, znacznik zbierania nieu ytków oraz to, czy obiekt jest zamro ony. Ka da klasa Ruby posiada te same pola, jak równie dwa dodatkowe: m_tbl „Tablica metod” — tabela mieszajñca metod instancyjnych danej klasy lub moduäu. super Wska nik klasy lub moduäu bazowego. Pola te peäniñ wa nñ rolö w wyszukiwaniu metod i sñ wa ne w zrozumieniu tego mechani- zmu. W szczególno ci mo na zwróciè uwagö na ró nicö pomiödzy wska nikami obiektu kla- sy: klass i super. Zasady Zasady wyszukiwania metod sñ bardzo proste, ale zale ñ od zrozumienia sposobu dziaäania struktur danych Ruby. Gdy do obiektu jest wysyäany komunikat5, wykonywane sñ nastöpujñce operacje: 1. Ruby korzysta z wska nika klass i przeszukuje m_tbl z obiektu danej klasy, szukajñc odpowiedniej metody (wska nik klass zawsze wskazuje na obiekt klasowy). 2. Je eli nie zostanie znaleziona metoda, Ruby korzysta z wska nika super obiektu klaso- wego i kontynuuje wyszukiwanie w m_tbl klasy bazowej. 3. Ruby wykonuje wyszukiwanie w ten sposób a do momentu znalezienia metody bñd te do osiñgniöcia koþca äaþcucha wska ników super. 4. Je eli w adnym obiekcie äaþcucha nie zostanie znaleziona metoda, Ruby wywoäuje me- todö method_missing z obiektu odbiorcy metody. Powoduje to ponowne rozpoczöcie te- go procesu, ale tym razem wyszukiwana jest metoda method_missing zamiast poczñt- kowej metody. 4 Poza obiektami natychmiastowymi (Fixnums, symbols, true, false oraz nil), które przedstawimy pó niej. 5 W Ruby czösto stosowana jest terminologia przekazywania komunikatów pochodzñca z jözyka Smalltalk — gdy jest wywoäywana metoda, mówi siö, e jest przesyäany komunikat. Obiekt, do którego jest wysyäany ko- munikat, jest nazywany odbiorcñ. Podstawy Ruby _ 13
  • 9. Zasady te sñ stosowane w sposób uniwersalny. Wszystkie interesujñce mechanizmy wykorzy- stujñce wyszukiwanie metod (mixin, metody klasowe i klasy singleton) wykorzystujñ struktu- rö wska ników klass oraz super. Przedstawimy teraz ten proces nieco bardziej szczegóäowo. Dziedziczenie klas Proces wyszukiwania metod mo e byè mylñcy, wiöc zacznijmy od prostego przykäadu. Poni- ej przedstawiona jest najprostsza mo liwa definicja klasy w Ruby: class A end Kod ten powoduje wygenerowanie w pamiöci nastöpujñcych struktur (patrz rysunek 1.1). Rysunek 1.1. Struktury danych dla pojedynczej klasy Prostokñty z podwójnymi ramkami reprezentujñ obiekty klas — obiekty, których wska nik klass wskazuje na obiekt Class. Wska nik super wskazuje na obiekt klasy Object, co oznacza, e A dziedziczy po Object. Od tego momentu bödziemy pomijaè wska niki klass dla Class, Module oraz Object, je eli nie bödzie to powodowaäo niejasno ci. Nastöpnym przypadkiem w kolejno ci stopnia skomplikowania jest dziedziczenie po jednej klasie. Dziedziczenie klas wykorzystuje wska niki super. Utwórzmy na przykäad klasö B dzie- dziczñcñ po A: class B < A end Wynikowe struktury danych sñ przedstawione na rysunku 1.2. Säowo kluczowe super pozwala na przechodzenie wzdäu äaþcucha dziedziczenia, tak jak w poni szym przykäadzie: class B def initialize logger.info "Tworzenie obiektu B" super end end Wywoäanie super w initialize pozwala na przej cie standardowej metody wyszukiwania metod, zaczynajñc od A#initialize. 14 _ Rozdzia 1. Techniki podstawowe
  • 10. Rysunek 1.2. Jeden poziom dziedziczenia Konkretyzacja klas Teraz mo emy przedstawiè sposób wyszukiwania metod. Na poczñtek utworzymy instancjö klasy B: obj = B.new Powoduje to utworzenie nowego obiektu i ustawienie wska nika klass na obiekt klasowy B (patrz rysunek 1.3). Rysunek 1.3. Konkretyzacja klas Podstawy Ruby _ 15
  • 11. Pojedyncza ramka wokóä obj reprezentuje zwykäy obiekt. Trzeba pamiötaè, e ka dy prosto- kñt na tym diagramie reprezentuje instancje obiektu. Jednak prostokñty o podwójnej ramce, reprezentujñce obiekty, sñ obiektami klasy Class (których wska nik klass wskazuje na obiekt Class). Gdy wysyäamy komunikat do obj: obj.to_s realizowany jest nastöpujñcy äaþcuch operacji: 1. Wska nik klass obiektu obj jest przesuwany do B; w metodach klasy B (w m_tbl) wy- szukiwana jest odpowiednia metoda. 2. W klasie B nie zostaje znaleziona odpowiednia metoda. Wykorzystywany jest wska nik super z obiektu klasy B i metoda jest poszukiwana w klasie A. 3. W klasie A nie zostaje znaleziona odpowiednia metoda. Wykorzystywany jest wska nik super z obiektu klasy A i metoda jest poszukiwana w klasie Object. 4. Klasa Object zawiera metodö to_s w kodzie natywnym (rb_any_to_s). Metoda ta jest wywoäywana z parametrem takim jak #<B:0x1cd3c0>. Metoda rb_any_to_s analizuje wska nik klass odbiorcy w celu okre lenia nazwy klasy wy wietlenia; dlatego pokazy- wana jest nazwa B, pomimo tego, e wywoäywana metoda znajduje siö w Object. Do éczanie modu ów Gdy zaczniemy korzystaè z moduäów, sprawa stanie siö bardziej skomplikowana. Ruby ob- säuguje doäñczanie moduäów zawierajñcych ICLASS6, które sñ po rednikami moduäów. Gdy doäñczamy moduä do klasy, Ruby wstawia ICLASS reprezentujñcy doäñczony moduä do äaþ- cucha super doäñczajñcej klasy. W naszym przykäadzie doäñczania moduäu upro cimy nieco sytuacjö przez zignorowanie kla- sy B. Zdefiniujemy moduä i dodamy go do A, co spowoduje powstanie struktur danych przed- stawionych na rysunku 1.4: module Mixin def mixed_method puts "Witamy w mixin" end end class A include Mixin end Tutaj wäa nie do gry wkracza ICLASS. Wska nik super wskazujñcy z A na Object jest prze- chwytywany przez nowy ICLASS (reprezentowany przez kwadrat narysowany przerywanñ liniñ). ICLASS jest po rednikiem dla moduäu Mixin. Zawiera on wska niki do tablic iv_tbl z Mixin (zmienne instancyjne) oraz m_tbl (metody). 6 ICLASS jest nazwñ dla klas po redniczñcych, wprowadzonñ przez Mauricia Fernándeza. Nie majñ one oficjalnej nazwy, ale w kodzie ródäowym Ruby noszñ nazwö T_ICLASS. 16 _ Rozdzia 1. Techniki podstawowe
  • 12. Rysunek 1.4. Wäñczenie moduäu w äaþcuch wyszukiwania Na podstawie tego diagramu mo na äatwo wywnioskowaè, do czego säu ñ nam klasy po red- niczñce — ten sam moduä mo e zostaè doäñczony do wielu ró nych klas; klasy mogñ dziedzi- czyè po ró nych klasach (i przez to mieè inne wska niki super). Nie mo emy bezpo rednio wäñczyè klasy Mixin do äaþcucha wyszukiwania, poniewa jego wska nik super bödzie wskazy- waä na dwa ró ne obiekty, je eli zostanie doäñczony do klas majñcych ró nych rodziców. Gdy utworzymy obiekt klasy A, struktury bödñ wyglñdaäy jak na rysunku 1.5. objA = A.new Rysunek 1.5. Wyszukiwanie metod dla klasy z doäñczonym moduäem Wywoäujemy tu metodö mixed_method z obiektu mixin, z objA jako odbiorcñ: objA.mixed_method # >> Witamy w mixin Podstawy Ruby _ 17
  • 13. Wykonywany jest nastöpujñcy proces wyszukiwania metody: 1. W klasie obiektu objA, czyli A, wyszukiwana jest pasujñca metoda. adna nie zostaje znale- ziona. 2. Wska nik super klasy A prowadzi do ICLASS, który jest po rednikiem dla Mixin. Pasujñca metoda jest wyszukiwana w obiekcie po rednika. Poniewa tablica m_tbl po rednika jest taka sama jak tablica m_tbl klasy Mixin, metoda mixed_method zostaje odnaleziona i wy- woäana. W wielu jözykach majñcych mo liwo è dziedziczenia wielobazowego wystöpuje problem diamentu, polegajñcy na braku mo liwo ci jednoznacznego identyfikowania metod obiektów, których klasy majñ schemat dziedziczenia o ksztaäcie diamentu, jak jest to pokazane na ry- sunku 1.6. Rysunek 1.6. Problem diamentu przy dziedziczeniu wielobazowym Biorñc jako przykäad diagram przedstawiony na tym rysunku, je eli obiekt klasy D wywoäuje metodö zdefiniowanñ w klasie A, która zostaäa przesäoniöta zarówno w B, jak i C, nie mo na jasno okre liè, która metoda zostanie wywoäana. W Ruby problem ten zostaä rozwiñzany przez szeregowanie kolejno ci doäñczania. W czasie wywoäywania metody äaþcuch dziedziczenia jest przeszukiwany liniowo, doäñczajñc wszystkie ICLASS dodane do äaþcucha. Trzeba przypomnieè, e Ruby nie obsäuguje dziedziczenia wielobazowego; jednak wiele mo- duäów mo e byè doäñczonych do klas i innych moduäów. Z tego powodu A, B oraz C muszñ byè moduäami. Jak widaè, nie wystöpuje tu niejednoznaczno è; wybrana zostanie metoda doäñczona jako ostatnia do äaþcucha wywoäania: module A def hello "Witamy w A" end end module B include A def hello "Witamy w B" end 18 _ Rozdzia 1. Techniki podstawowe
  • 14. end module C include A def hello "Witamy w C" end end class D include B include C end D.new.hello # => "Witamy w C" Je eli zmienimy kolejno è doäñczania, odpowiednio zmieni siö wynik: class D include C include B end D.new.hello # => "Witamy w B" W przypadku ostatniego przykäadu, gdzie B zostaä doäñczony jako ostatni, diagram obiektów jest przedstawiony na rysunku 1.7 (dla uproszczenia wska niki od Object i Class zostaäy usuniöte). Rysunek 1.7. Rozwiñzanie problemu diamentu w Ruby — szeregowanie Podstawy Ruby _ 19
  • 15. Klasa singleton Klasy singleton (równie metaklasy lub eigenklasy; patrz nastöpna ramka, „Terminologia klas singleton”) pozwalajñ na zró nicowanie dziaäania obiektu w stosunku do innych obiek- tów danej klasy. Czytelnik prawdopodobnie spotkaä siö wcze niej z notacjñ pozwalajñcñ na otwarcie klasy singleton: class A end objA = A.new objB = A.new objA.to_s # => "#<A:0x1cd0a0>" objB.to_s # => "#<A:0x1c4e28>" class <<objA # Otwarcie klasy singleton dla objA def to_s; "Obiekt A"; end end objA.to_s # => "Obiekt A" objB.to_s # => "#<A:0x1c4e28>" Zapis class <<objA otwiera klasö singleton dla objA. Metody instancyjne dodane do klasy singleton funkcjonujñ jako metody instancyjne w äaþcuchu wyszukiwania. Wynikowe struk- tury danych sñ przedstawione na rysunku 1.8. Rysunek 1.8. Klasa singleton dla obiektu Jak zwykle, obiekt objB jest klasy A. Je eli poprosimy Ruby o podanie typu objA, oka e siö, e jest to równie obiekt klasy A: objA.class # => A Jednak wewnötrznie obiekt ten dziaäa nieco inaczej. Do äaþcucha wyszukiwania zostaje do- dana inna klasa. Jest to obiekt klasy singleton dla objA. W tej dokumentacji bödziemy go na- zywaè Class:objA. Ruby nadaje mu podobnñ nazwö: #<Class:#<A:0x1cd0a0>>. Podobnie jak inne klasy, wska nik klass klasy singleton (niepokazany) wskazuje na obiekt Class. 20 _ Rozdzia 1. Techniki podstawowe
  • 16. Terminologia klas singleton Termin metaklasa nie jest szczególnie precyzyjny w okre laniu klas singleton. Nazwanie klasy „meta” wskazuje, e jest ona nieco bardziej abstrakcyjna ni zwykäa klasa. W tym przypadku nie ma to miejsca; klasy singleton sñ po prostu klasami nale ñcymi do okre lonej instancji. Prawdziwe metaklasy sñ dostöpne w takich jözykach, jak Smalltalk, gdzie mamy bogaty proto- kóä metaobiektów. Metaklasy w Smalltalku to klasy, których instancjami sñ klasy. W przypad- ku Ruby jedynñ metaklasñ jest Class, poniewa wszystkie klasy sñ obiektami Class. Dosyè popularnym alternatywnym terminem dla klasy singleton jest eigenklasa, od niemiec- kiego säowa eigen („wäasny”). Klasa singleton obiektu jest jego eigenklasñ (wäasnñ klasñ). Klasa singleton zostaje oznaczona jako klasa wirtualna (jeden ze znaczników flags wskazuje, e klasa jest wirtualna). Klasy wirtualne nie mogñ byè konkretyzowane i zwykle nie sñ wy- korzystywane w Ruby, o ile nie zadamy sobie trudu, aby ich u yè. Gdy chcieli my okre liè klasö obiektu objA, Ruby wykorzystywaä wska niki klass i super w hierarchii, a do mo- mentu znalezienia pierwszej klasy niewirtualnej. Z tego powodu uzyskali my odpowied , e klasñ objA jest A. Wa ne jest, aby to zapamiötaè — klasa obiektu (z perspektywy Ruby) mo e nie odpowiadaè obiektowi, na który wskazuje klass. Klasy singleton sñ tak nazwane nie bez powodu — w obiekcie mo e byè zdefiniowana tylko jedna taka klasa. Dziöki temu mo emy bez niejednoznaczno ci odwoäywaè siö do klasy sin- gleton objA lub Class:objA. W naszym kodzie mo emy zaäo yè, e klasa singleton istnieje; w rzeczywisto ci Ruby tworzy jñ w momencie pierwszego wywoäania. Ruby pozwala na definiowanie klas singleton w dowolnych obiektach poza Fixnum oraz sym- bolach. Symbole oraz Fixnum sñ warto ciami natychmiastowymi (dla zapewnienia odpowied- niej wydajno ci sñ przechowywane w pamiöci bezpo rednio, a nie jako wska niki do struktur danych). Poniewa sñ one przechowywane w caäo ci, nie posiadajñ wska ników klass, wiöc nie ma mo liwo ci zmiany äaþcucha wyszukiwania metod. Mo na równie otworzyè klasö singleton dla true, false oraz nil, ale zwracana bödzie ta sama klasa singleton co klasa obiektu. Warto ciami sñ obiekty singleton (jedyne instancje), odpo- wiednio TrueClass, FalseClass oraz NilClass. Gdy odwoäamy siö do klasy singleton dla true, otrzymamy TrueClass, poniewa jest to jedyna mo liwa instancja tej klasy. W Ruby: true.class # => TrueClass class << true; self; end # => TrueClass true.class == (class << true; self; end) # => true Klasy singleton i obiekty klas Teraz sprawy siö komplikujñ. Nale y pamiötaè o podstawowej zasadzie wyszukiwania me- tod — na poczñtku Ruby przechodzi po wska nikach klass i wyszukuje metody; nastöpnie korzysta z wska ników super do przeglñdania äaþcucha, a do znalezienia odpowiedniej metody lub osiñgniöcia koþca äaþcucha. Wa ne jest, aby pamiötaè, e klasy sñ równie obiektami. Tak jak zwykäe obiekty mogñ mieè klasö singleton, tak samo obiekty klas mogñ równie posiadaè klasy singleton. Te klasy singleton, podobnie jak inne klasy, mogñ posiadaè metody. Poniewa klasy singleton sñ dostöpne za Podstawy Ruby _ 21
  • 17. pomocñ wska nika klass z obiektu klasy, metody instancji klasy singleton sñ metodami klasy wäa ciciela singletonu. Peäny zbiór struktur danych poni szego kodu jest pokazany na rysunku 1.9. class A end Rysunek 1.9. Peäny zbiór struktur danych jednej klasy Klasa A dziedziczy po Object. Obiekt klasy A jest typu Class. Class dziedziczy po Module, który z kolei dziedziczy po Object. Metody zapisane w tablicy m_tbl klasy A sñ metodami instancyjnymi A. Co siö wiöc stanie, gdy wywoäamy metodö klasowñ z A? A.to_s # => "A" Stosowane sñ te same zasady wyszukiwania, przy u yciu A jako odbiorcy (nale y pamiötaè, e A jest staäñ warto ciowanñ jako obiekt klasy A). Na poczñtek Ruby korzysta ze wska nika klass pomiödzy A a Class. W tablicy m_tbl Class wyszukiwana jest funkcja o nazwie to_s. Poniewa nic nie zostaäo znalezione, Ruby przechodzi za pomocñ wska nika super z Class do Module, gdzie zostaje odnaleziona funkcja to_s (w kodzie natywnym, rb_mod_to_s). Nie powinno byè to niespodziankñ. Nie ma tu adnej magii. Metody klasowe sñ wyszukiwane w ten sam sposób co metody instancyjne — jedynñ ró nicñ jest to, e odbiorcñ jest klasa, a nie instancja klasy. Teraz, gdy wiemy, w jaki sposób sñ wyszukiwane metody, mo emy wnioskowaè, e mo e- my zdefiniowaè metodö klasowñ dla dowolnej klasy przez zdefiniowanie metody instancyjnej obiektu Class (aby wstawiè go do m_tbl Class). Faktycznie — to dziaäa: class A; end # z Module#to_s A.to_s # => "A" class Class def to_s; "Class#to_s"; end end A.to_s # => "Class#to_s" 22 _ Rozdzia 1. Techniki podstawowe
  • 18. Jest to interesujñca sztuczka, ale o ograniczonej u yteczno ci. Zwykle chcemy zdefiniowaè osobne metody klasowe dla ka dej z klas. W takim przypadku mo na wykorzystaè klasy singleton dla obiektów klasowych. Aby otworzyè klasö singleton dla klasy, nale y po prostu u yè nazwy klasy w notacji klasy singleton: class A; end class B; end class <<A def to_s; "Klasa A"; end end A.to_s # => "Klasa A" B.to_s # => "B" Wynikowe struktury danych sñ przedstawione na rysunku 1.10. Dla uproszczenia klasa B jest pominiöta. Rysunek 1.10. Klasa singleton dla klasy Metoda to_s zostaäa dodana do klasy singleton dla A lub Class:A. Teraz, gdy zostanie wywo- äana metoda A.to_s, Ruby skorzysta z wska nika klass do Class:A i wywoäa z niej odpo- wiedniñ metodö. W definicji metody znajduje siö jeszcze jeden problem. W definicji klasy lub moduäu self zawsze wskazuje na obiekt klasy lub moduäu: class A self # => A end Tak wiöc class<<A wewnñtrz definicji klasy A mo e byè zapisane jako class<<self, poniewa self wewnñtrz definicji A wskazuje na ten sam obiekt. Ten idiom jest u ywany wszödzie w Rails do definiowania metod klasowych. Poni szy przykäad przedstawia wszystkie sposoby defi- niowania metod klasowych. class A def A.class_method_one; "Metoda klasowa"; end def self.class_method_two; "Równie metoda klasowa"; end class <<A def class_method_three; "Nadal metoda klasowa"; end end Podstawy Ruby _ 23
  • 19. class <<self def class_method_four; "Kolejna metoda klasowa"; end end end def A.class_method_five "To dzia a poza definicjî klasy" end class <<A def A.class_method_six "Metaklas mo na otworzyð poza definicjî klasy" end end # Drukuje po kolei wyniki wywoáania ka dej metody. %w(one two three four five six).each do |number| puts A.send(:"class_method_#{number}") end # >> Metoda klasowa # >> Równie metoda klasowa # >> Nadal metoda klasowa # >> Kolejna metoda klasowa # >> To dziaáa poza definicj klasy # >> Metaklas mo na otworzyü poza definicj klasy Oznacza to równie , e wewnñtrz definicji klasy singleton — podobnie jak w ka dej innej defi- nicji klasy — self nadal wskazuje na obiekt definiowanej klasy. Gdy pamiötamy, e ta warto è w definicji bloku lub klasy jest warto ciñ ostatniej wykonanej instrukcji, to wiemy, e warto- ciñ class <<objA; self; end jest obiekt klasa singleton objA. Konstrukcja class <<objA otwiera klasö singleton, a self (klasa singleton) jest zwracany z definicji klasy. ãñczñc to wszystko, mo emy otworzyè klasö Object i dodaè metodö instancyjnñ do ka dego obiektu, który zwraca klasö singleton obiektu: class Object def metaclass class <<self self end end end Metoda ta tworzy podstawy metaid, o czym wkrótce. Brakujéce metody Po caäym tym zamieszaniu method_missing jest dosyè prosta. Istnieje tylko jedna reguäa — je eli caäa procedura wyszukiwania metod zawiedzie, wyszukiwanie metody jest wykony- wane ponownie; szukana jest tym razem metoda method_missing zamiast poczñtkowej metody. Je eli metoda zostanie znaleziona, wywoäywana jest z argumentami oryginalnej metody, z do- äñczonñ nazwñ metody. Przekazywany jest równie ka dy blok. Domy lna metoda method_missing z Object (rb_method_missing) zgäasza wyjñtek. 24 _ Rozdzia 1. Techniki podstawowe
  • 20. Metaid Autorem niewielkiej biblioteki o nazwie metaid.rb, wspomagajñcej metaprogramowanie w Ruby, jest why the lucky stiff. Jest ona na tyle u yteczna, aby doäñczaè jñ do ka dego projektu, w którym potrzebne jest metaprogramowanie7: class Object # Ukryty singleton ledzi ka dego. def metaclass; class << self; self; end; end def meta_eval &blk; metaclass.instance_eval &blk; end # Dodanie metody do metaklasy. def meta_def name, &blk meta_eval { define_method name, &blk } end # Definiowanie metody instancyjnej wewn trz klasy. def class_def name, &blk class_eval { define_method name, &blk } end end W ka dym obiekcie biblioteka ta definiuje cztery metody: metaclass Odwoäuje siö do klasy singletonu odbiorcy (self). meta_eval Odpowiednik class_eval dla klas singletonów. Warto ciuje dany blok w kontek cie klasy singletonu odbiorcy. meta_def Definiuje metodö w klasie singleton odbiorcy. Je eli odbiorca jest klasñ lub moduäem, spo- woduje to utworzenie metody klasowej (metody instancyjnej klasy singleton odbiorcy). class_def Definiuje metodö instancyjnñ odbiorcy (który musi byè klasñ lub moduäem). Korzystanie z metaid jest tak proste, poniewa zastosowano w niej znaczne uproszczenia. Przez wykorzystanie skrótu do odwoäywania siö i rozszerzania metaklas nasz kod staje siö bardziej czytelny, poniewa nie jest zatäoczony konstrukcjami takimi jak class << self; self; end. Im krótszy i czytelny jest kod realizujñcy danñ technikö, tym bardziej prawdopodobne jest, e u yjemy go we wäa ciwy sposób w naszym kodzie. Poni szy przykäad pokazuje zastosowanie metaid do uproszczenia naszej klasy singleton: class Person def name; "Bob"; end def self.species; "Homo sapiens"; end end Metody klasowe sñ dodawane jako metody instancyjne klasy singleton: Person.instance_methods(false) # => ["name"] Person.metaclass.instance_methods - Object.metaclass.instance_methods # => ["species"] 7 Seeing Metaclasses Clearly: http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html. Podstawy Ruby _ 25
  • 21. Przy u yciu metod z metaid mo emy napisaè nasze definicje metod w nastöpujñcy sposób: Person.class_def(:name) { "Bob" } Person.meta_def(:species) { "Homo sapiens" } Wyszukiwanie zmiennych W Ruby wystöpujñ cztery rodzaje zmiennych — zmienne globalne, zmienne klasowe, zmienne instancyjne oraz zmienne lokalne8. Zmienne globalne sñ przechowywane globalnie, a zmienne lokalne sñ przechowywane leksykalnie, wiöc nie bödñ one przedmiotem naszej dyskusji, ponie- wa nie wykorzystujñ systemu klas Ruby. Zmienne instancyjne sñ specyficzne dla okre lonego obiektu. Sñ one prefiksowane za pomocñ symbolu @: @price jest zmiennñ instancyjnñ. Poniewa ka dy obiekt Ruby ma strukturö iv_tbl, ka dy obiekt mo e posiadaè zmienne instancyjne. Poniewa ka da klasa jest równie obiektem, klasy równie mogñ posiadaè zmienne instan- cyjne. W poni szym przykäadzie kodu przedstawiony jest sposób odwoäania do zmiennej in- stancyjnej klasy: class A @ivar = "Zmienna instancyjna klasy A" end A.instance_variable_get(:@ivar) # => "Zmienna instancyjna klasy A" Zmienne instancyjne sñ zawsze wyszukiwane na podstawie obiektu wskazywanego przez self. Poniewa self jest obiektem klasowym A w definicji klasy A ... end, @ivar nale y do obiektu klasowego A. Zmienne klasowe sñ inne. Do zmiennych klasowych (które zaczynajñ siö od @@) mo e odwoäy- waè siö ka da zmienna klasowa. Zmienne klasowe mogñ byè równie wykorzystywane w samej definicji klasy. Choè zmienne klasowe i instancyjne sñ podobne, nie sñ one tym samym: class A @var = "Zmienna instancyjna klasy A" @@var = "Zmienna klasowa klasy A" def A.ivar @var end def A.cvar @@var end end A.ivar # => "Zmienna instancyjna klasy A" A.cvar # => "Zmienna klasowa klasy A" W tym przykäadzie @var oraz @@var sñ przechowywane w tym samym miejscu — w tablicy iv_tbl klasy A. Sñ to jednak inne zmienne, poniewa majñ one inne nazwy (symbole @ sñ doäñczane do nazwy zmiennej przy przechowywaniu). Funkcje Ruby do odwoäywania siö do zmiennych instancyjnych i klasowych sprawdzajñ, czy przekazywane nazwy sñ we wäa ci- wym formacie: 8 Istniejñ równie staäe, ale nie ma to w tej chwili wiökszego znaczenia. 26 _ Rozdzia 1. Techniki podstawowe
  • 22. A.instance_variable_get(:@@var) # ~> -:17:in 'instance_variable_get': '@@var' is not allowed as an instance variable name (NameError) Zmienne klasowe sñ nieco mylñce w u yciu. Sñ one wspóädzielone w caäej hierarchii dziedzicze- nia, wiöc klasa pochodna modyfikujñca zmiennñ klasowñ modyfikuje równie zmiennñ klasowñ rodzica. >> class A; @@x = 3 end => 3 >> class B < A; @@x = 4 end => 4 >> class A; @@x end => 4 Mo e to byè zarówno przydatne, jak i mylñce. Generalnie potrzebujemy albo zmiennych in- stancyjnych — które sñ niezale ne od hierarchii dziedziczenia — albo dziedziczonych atry- butów klasy zapewnianych przez ActiveSupport, które propagujñ warto ci w kontrolowany, dobrze zdefiniowany sposób. Bloki, metody i procedury Jednñ z zaawansowanych funkcji Ruby jest mo liwo è wykorzystywania fragmentów kodu jako obiektów. Do tego celu wykorzystuje siö trzy klasy: Proc Klasa Proc reprezentuje blok kodu — fragment kodu, który mo e byè wywoäywany z ar- gumentami i mo e zwracaè warto è. UnboundMethod Jest ona podobna do Proc; reprezentuje metodö instancyjnñ okre lonej klasy (nale y pamiötaè, e metoda klasowa jest równie metodñ instancyjnñ obiektu klasowego, wiöc UnboundMethods mo e reprezentowaè równie metody klasowe). UnboundMethod musi byè zwiñzana z klasñ przed wywoäaniem. Method Obiekty Method sñ obiektami UnboundMethod, które zostaäy zwiñzane z obiektem za po- mocñ UnboundMethod#bind. Mo na je równie uzyskaè za pomocñ Object#method. Przeanalizujemy teraz kilka sposobów na uzyskanie obiektów Proc oraz Method. Jako przy- käadu u yjemy metody Fixnum#+. Zwykle wywoäujemy jñ przy pomocy uproszczonej skäadni: 3 + 5 # => 8 Mo na jednak u yè wywoäania metody instancyjnej z obiektu Fixnum, tak samo jak innych metod instancyjnych: 3.+(5) # => 8 Do uzyskania obiektu reprezentujñcego metodö instancyjnñ mo na wykorzystaè metodö Object# ´method. Metoda ta bödzie zwiñzana z obiektem, na którym zostaäa wywoäana, czyli 3. add_3 = 3.method(:+) add_3 # => #<Method: Fixnum#+> Metoda ta mo e byè skonwertowana do Proc lub wywoäana bezpo rednio z argumentami: add_3.to_proc # => #<Proc:0x00024b08@-:6> add_3.call(5) # => 8 # Metoda#[] jest wygodnym synonimem dla Metoda#call. add_3[5] # => 8 Podstawy Ruby _ 27
  • 23. Istniejñ dwa sposoby na uzyskanie metody niezwiñzanej. Mo emy wywoäaè instance_method na obiekcie klasy: add_unbound = Fixnum.instance_method(:+) add_unbound # => #<UnboundMethod: Fixnum#+> Mo na równie odäñczyè metodö, która zostaäa wcze niej zwiñzana z obiektem: add_unbound == 3.method(:+).unbind # => true add_unbound.bind(3).call(5) # => 8 Mo emy zwiñzaè UnboundMethod z dowolnym obiektem tej samej klasy: add_unbound.bind(15)[4] # => 19 Jednak doäñczany obiekt musi byè instancjñ tej samej klasy, poniewa w przeciwnym razie otrzymamy TypeError: add_unbound.bind(1.5)[4] # => # ~> -:16:in 'bind': bind argument must be an instance of Fixnum (TypeError) # ~> from -:16 Otrzymali my ten bäñd, poniewa + jest zdefiniowany w Fixnum; dlatego obiekt UnboundMethod, jaki otrzymujemy, musi byè zwiñzany z obiektem, który jest kind_of?(Fixnum). Gdyby me- toda + byäa zdefiniowana w Numeric (z którego dziedziczñ Fixnum oraz Float), wcze niejszy kod zwróciäby 5.5. Bloki na procedury i procedury na bloki Bie ñca implementacja Ruby ma wyra nñ wadö — bloki nie zawsze sñ obiektami Proc i odwrot- nie. Zwykäe bloki (tworzone za pomocñ do...end oraz {}) muszñ byè doäñczane do wywoäania metody i nie sñ automatycznie obiektami. Nie mo na na przykäad zapisaè code_ block ={puts"abc"}. Przydajñ siö tu funkcje Kernel#lambda i Proc.new, które konwertujñ bloki na obiekty Proc9. block_1 = lambda { puts "abc" } # => #<Proc:0x00024914@-:20> block_2 = Proc.new { puts "abc" } # => #<Proc:0x000246a8@-:21> Pomiödzy Kernel#lambda i Proc.new wystöpuje niewielka ró nica. Powrót z obiektu Proc utworzonego za pomocñ Kernel#lambda powoduje zwrócenie wyniku do funkcji wywoäujñcej; powrót z obiektu Proc utworzonego za pomocñ Proc.new powoduje próbö wykonania po- wrotu z funkcji wywoäujñcej, a je eli nie jest to mo liwe, zgäaszany jest LocalJumpError. Poni- ej pokazany jest przykäad: def block_test lambda_proc = lambda { return 3 } proc_new_proc = Proc.new { return 4 } lambda_proc.call # => 3 proc_new_proc.call # => puts "Nigdy nie zostanie wywo ane" end block_test # => 4 Instrukcja powrotu w lambda_proc zwraca warto è 3. W przypadku proc_new_proc instrukcja powrotu powoduje wyj cie z funkcji wywoäujñcej block_test — dlatego warto è 4 jest zwra- 9 Kernel#proc jest innñ nazwñ dla Kernel#lambda, ale jest ona przestarzaäa. 28 _ Rozdzia 1. Techniki podstawowe
  • 24. cana przez block_test. Instrukcja puts nie zostanie nigdy wykonana, poniewa instrukcja proc_new_proc.call spowoduje wcze niejsze zakoþczenie block_test. Bloki mogñ byè konwertowane do obiektów Proc przez przekazanie ich do funkcji przy wy- korzystaniu & w parametrach formalnych funkcji: def some_function(&b) puts "Blokiem jest #{b}, który zwraca #{b.call}" end some_function { 6 + 3 } # >> Blokiem jest #<Proc:0x00025774@-:7>, który zwraca 9 Mo na równie zastñpiè Proc za pomocñ &, je eli funkcja oczekuje bloku: add_3 = lambda {|x| x+3} (1..5).map(&add_3) # => [4, 5, 6, 7, 8] Zamkniýcia Zamkniöcia (ang. closure) sñ tworzone w przypadku, gdy blok lub obiekt Proc odwoäuje siö do zmiennej zdefiniowanej poza ich zakresem. Pomimo tego, e blok zawierajñcy mo e wyj è z zakresu, zmienne sñ utrzymywane do momentu wyj cia z zakresu przez odwoäujñcy siö do nich blok lub obiekt Proc. Uproszczony przykäad, pomimo e niezbyt praktyczny, demonstruje tö zasadö: def get_closure data = [1, 2, 3] lambda { data } end block = get_closure block.call # => [1, 2, 3] Funkcja anonimowa (lambda) zwracana przez get_closure odwoäuje siö do danych ze zmien- nej lokalnej, która jest zdefiniowana poza jej zakresem. Dopóki zmienna block znajduje siö w zakresie, bödzie przechowywaäa wäasnñ referencjö do data, wiöc instancja data nie zostanie usuniöta (pomimo tego, e funkcja get_closure zakoþczyäa siö). Nale y zwróciè uwagö, e przy ka dym wywoäaniu get_closure, data odwoäuje siö do innej zmiennej (poniewa jest lo- kalna dla funkcji): block = get_closure block2 = get_closure block.call.object_id # => 76200 block2.call.object_id # => 76170 Klasycznym przykäadem zamkniöcia jest funkcja make_counter, która zwraca funkcjö licznika (Proc), która po uruchomieniu zwiöksza i zwraca ten licznik. W Ruby funkcja make_counter mo e byè zaimplementowana w nastöpujñcy sposób: def make_counter(i=0) lambda { i += 1 } end x = make_counter x.call # => 1 x.call # => 2 y = make_counter y.call # => 1 y.call # => 2 Podstawy Ruby _ 29
  • 25. Funkcja lambda tworzy zamkniöcie obejmujñce bie ñcñ warto è zmiennej lokalnej i. Nie tylko mo na odwoäywaè siö do zmiennych, ale mo na równie modyfikowaè jej warto ci. Ka de zamkniöcie uzyskuje osobnñ instancjö zmiennej (poniewa jest to zmienna lokalna dla ka dej z instancji make_counter). Poniewa x oraz y zawierajñ referencje do innych instancji zmiennej lokalnej i, majñ one inny stan. Techniki metaprogramowania Po omówieniu podstaw Ruby przedstawimy kilka powszechnie stosowanych technik metapro- gramowania wykorzystywanych w tym jözyku. Choè przykäady sñ napisane w Ruby, wiökszo è z technik mo na wykorzystaè w dowolnym dynamicznym jözyku programowania. W rzeczywisto ci wiele z idiomów metaprogramowa- nia stosowanych w Ruby jest bezwstydnie skradzionych z jözyków Lisp, Smalltalk lub Perl. Opó nienie wyszukiwania metod do czasu wykonania Czasami chcemy utworzyè interfejs, którego metody sñ zale ne od danych dostöpnych w czasie wykonywania programu. Najwa niejszym przykäadem takiej konstrukcji sñ metody akcesorów atrybutów w ActiveRecord dostöpne w Rails. Wywoäania metod obiektu ActiveRecord (tak jak person.name) sñ modyfikowane w czasie dziaäania na odwoäania do atrybutów. Na pozio- mie metod klasy ActiveRecord oferuje niezwykäñ elastyczno è — wyra enie Person.find_ ´all_by_user_id_and_active(42, true) jest zamieniane na odpowiednie zapytanie SQL, a dodatkowo, je eli rekord nie zostanie znaleziony, zgäaszany jest wyjñtek NoMethodError. Umo liwia to metoda method_missing dostöpna w Ruby. Gdy na obiekcie zostanie wywoäana nieistniejñca metoda, Ruby przed zgäoszeniem wyjñtku NoMethodError wyszukuje w klasie obiektu metodö method_missing. Pierwszym argumentem method_missing jest nazwa wy- woäywanej metody; pozostaäe argumenty odpowiadajñ argumentom przekazanym do metody. Ka dy blok przekazany do metody jest równie przekazywany do method_missing. Tak wiöc kompletna sygnatura tej metody jest nastöpujñca: def method_missing(method_id, *args, &block) ... end Istnieje kilka wad wykorzystywania method_missing: x Jest wolniejsza ni konwencjonalne wyszukiwanie metod. Proste testy pokazujñ, e wyszu- kiwanie metod za pomocñ method_missing jest co najmniej dwa do trzech razy bardziej czasochäonne ni konwencjonalne wyszukiwanie. x Poniewa wywoäywana metoda nigdy faktycznie nie istnieje — jest po prostu przechwy- tywana w ostatnim kroku procesu wyszukiwania metod — nie mo e byè dokumentowana lub poddawana introspekcji, jak konwencjonalne metody. x Poniewa wszystkie metody dynamiczne muszñ przechodziè przez metodö method_missing, mo e ona znacznie urosnñè, je eli w kodzie znajduje siö wiele miejsc wymagajñcych dy- namicznego dodawania metod. x Zastosowanie method_missing ogranicza zgodno è z przyszäymi wersjami API. Gdy bö- dziemy polegaè na metodzie method_missing przy obsäudze niezdefiniowanych metod, wprowadzenie nowych metod w przyszäych wersjach API mo e zmieniè oczekiwania naszych u ytkowników. 30 _ Rozdzia 1. Techniki podstawowe
  • 26. Dobrñ alternatywñ jest podej cie zastosowane w funkcji generate_read_methods z Active ´Record. Zamiast czekaè na przechwycenie wywoäania przez method_missing, ActiveRecord generuje implementacje dla metod odczytu i modyfikacji atrybutu, dziöki czemu mogñ byè one wywoäywane przez konwencjonalny mechanizm wyszukiwania metod. Jest to bardzo wydajna metoda, a dynamiczna natura Ruby pozwala na napisanie metod, któ- re przy pierwszym wywoäaniu wymieniajñ siö na swoje zoptymalizowane wersje. Jest to u y- wane w routingu Ruby, który musi byè bardzo szybki; zastosowanie tej metody jest przed- stawione w dalszej czö ci rozdziaäu. Programowanie generacyjne — tworzenie kodu na bie éco Jednñ z efektywnych technik, która skäada siö z kilku kolejnych, jest programowanie genera- cyjne — pisanie kodu tworzñcego kod. Technika ta mo e byè zastosowana do bardzo prostych zadaþ, takich jak pisanie skryptu automatyzujñcego niektóre nudne czö ci programowania. Mo na na przykäad wypeäniè przypadki testowe dla ka dego z u ytkowników: brad_project: id: 1 owner_id: 1 billing_status_id: 12 john_project: id: 2 owner_id: 2 billing_status_id: 4 ... Je eli byäby to jözyk bez mo liwo ci zastosowania skryptów do definiowania przypadków testowych, konieczne byäoby napisanie ich röcznie. Mo e to zaczñè sprawiaè problemy, gdy dane przekroczñ masö krytycznñ, a jest niemal niemo liwe, gdy przypadki testowe majñ dziwne za- le no ci w danych ródäowych. Programowanie generacyjne pozwala napisaè skrypt do gene- rowania tych przypadków u ycia na podstawie danych ródäowych. Choè nie jest to idealne rozwiñzanie, jest to znaczne usprawnienie w stosunku do pisania przypadków u ycia röcznie. Wystöpuje tu jednak problem z utrzymaniem — konieczne jest wäñczenie skryptu w proces tworzenia oraz zapewnienie, e przypadki testowe sñ regenerowane w momencie zmiany danych ródäowych. Jest to (na szczö cie) rzadko, o ile w ogóle potrzebne w Ruby on Rails. Niemal w ka dym aspekcie konfiguracji aplikacji Rails mo na stosowaè skrypty, co jest spowodowane w wiökszo ci przez zastosowanie wewnötrznych jözyków specyficznych dla domeny (DSL — ang. Domain Specific Language). W wewnötrznym DSL mo na mieè do dyspozycji wszystkie mo liwo ci jö- zyka Ruby, nie tylko okre lony interfejs biblioteki, jaki autor zdecydowaä siö udostöpniè. Wracajñc do poprzedniego przykäadu, ERb (ang. Embedded Ruby) znacznie uäatwia pracö. Mo - na wstrzyknñè dowolny kod Ruby na poczñtek pliku YAML10 z u yciem znaczników ERb <% %> oraz <%= %>, doäñczajñc tam dowolnñ logikö: 10 Uniwersalny jözyk formalny przeznaczony do reprezentowania ró nych danych w ustrukturyzowany sposób. Säowo YAML to akronim rekursywny od säów YAML Ain’t Markup Language. Pierwotnie interpretowano ten skrót jako Yet Another Markup Language. Pierwsza wersja zaproponowana zostaäa w 2001 roku przez Clarka Evansa we wspóäpracy z Ingy döt Net oraz Oren Ben-Kiki — przyp. red. Techniki metaprogramowania _ 31
  • 27. <% User.find_all_by_active(true).each_with_index do |user, i| %> <%= user.login %>_project: id: <%= i %> owner_id: <%= user.id %> billing_status_id: <%= user.billing_status.id %> <% end %> Implementacja tego wygodnego mechanizmu w ActiveRecord nie mo e byè prostsza: yaml = YAML::load(erb_render(yaml_string)) przy wykorzystaniu metody pomocniczej erb_render: def erb_render(fixture_content) ERB.new(fixture_content).result end W programowaniu generacyjnym czösto wykorzystuje siö Module#define_method lub class_eval oraz def do tworzenia metod na bie ñco. Technika ta jest wykorzystywana do akcesorów atry- butów; funkcja generate_read_methods definiuje metody do modyfikacji i odczytu jako metody instancyjne klasy ActiveRecord w celu zmniejszenia liczby wywoäaþ metody method_missing (która jest dosyè kosztownñ technikñ). Kontynuacje Kontynuacje sñ bardzo efektywnñ technikñ kontroli przepäywu sterowania. Kontynuacja repre- zentuje okre lony stan stosu wywoäaþ oraz zmiennych leksykalnych. Jest to migawka wykonana w momencie, gdy Ruby wykonuje dany fragment kodu. Niestety, w implementacji Ruby 1.8 implementacja kontynuacji jest tak powolna, e technika ta jest bezu yteczna w wielu aplikacjach. W kolejnych wersjach maszyn wirtualnych Ruby 1.9 sytuacja mo e siö poprawiè, ale nie mo na siö spodziewaè dobrej wydajno ci dziaäania kontynuacji w Ruby 1.8. Jest to jednak bardzo u yteczna konstrukcja, a biblioteki WWW bazujñce na kontynuacjach sñ interesujñcñ alter- natywñ dla bibliotek takich jak Rails, wiöc przedstawimy tu ich zastosowanie. Kontynuacje sñ tak efektywne z kilku powodów: x Kontynuacje sñ po prostu obiektami; mogñ byè one przekazywane z funkcji do funkcji. x Kontynuacje mogñ byè wywoäywane z dowolnego miejsca. Je eli mamy referencjö kon- tynuacji, mo emy jñ wywoäaè. x Kontynuacje mogñ byè u ywane wielokrotnie. Mo na je wielokrotnie wykorzystywaè do powrotu z funkcji. Kontynuacje sñ czösto przedstawiane jako „strukturalne GOTO”. Z tego powodu powinny byè traktowane z takñ samñ uwagñ jak ka da inna konstrukcja GOTO. Kontynuacje majñ niewielkie lub adne zastosowanie w kodzie aplikacji; powinny byè ukryte w bibliotece. Nie uwa am, e nale y chroniè programistów przed nimi samymi. Chodzi o to, e kontynuacje majñ wiökszy sens przy tworzeniu abstrakcji ni przy bezpo rednim wykorzystaniu. Gdy programista buduje aplikacjö, powinien my leè o „zewnötrznym iteratorze” lub „koprocedurze” (obie te abstrakcje sñ zbudowane za pomocñ kontynuacji), a nie o „kontynuacji”. 32 _ Rozdzia 1. Techniki podstawowe
  • 28. Seaside11 jest bibliotekñ WWW dla jözyka Smalltalk, która bazuje na kontynuacjach. Sñ one wykorzystywane w Seaside do zarzñdzania stanem sesji. Ka dy z u ytkowników odpowiada kontynuacji na serwerze. Gdy zostanie odebrane ñdanie, wywoäywana jest kontynuacja i wy- konywany jest dalszy kod. Dziöki temu caäa transakcja mo e byè zapisana jako jeden strumieþ kodu pomimo tego, e mo e ona skäadaè siö z wielu ñdaþ HTTP. Biblioteka ta korzysta z tego, e kontynuacje w Smalltalku sñ serializowalne; mogñ byè one zapisane do bazy danych lub w systemie plików, a nastöpnie po odebraniu ñdania pobierane i ponownie wywoäywane. Kontynuacje w Ruby nie sñ serializowalne. W Ruby kontynuacje sñ tylko obiektami pamiöcio- wymi i nie mogñ byè transformowane do strumienia bajtów. Borges (http://borges.rubyforge.org) jest prostym przeniesieniem Seaside 2 do Ruby. Gäównñ ró - nicñ pomiödzy Seaside a Borges jest to, e biblioteka Borges musi przechowywaè wszystkie bie ñce kontynuacje w pamiöci, poniewa nie sñ one serializowalne. To znaczne ograniczenie uniemo liwia stosowanie biblioteki Borges do aplikacji WWW o jakimkolwiek obciñ eniu. Je eli w jednej z implementacji Ruby powstanie mechanizm serializowania kontynuacji, ograniczenie to zostanie usuniöte. Efektywno è kontynuacji przedstawia poni szy kod przykäadowej aplikacji Borges, która ge- neruje listö elementów z magazynu dostöpnego online: class SushiNet::StoreItemList < Borges::Component def choose(item) call SushiNet::StoreItemView.new(item) end def initialize(items) @batcher = Borges::BatchedList.new items, 8 end def render_content_on(r) r.list_do @batcher.batch do |item| r.anchor item.title do choose item end end r.render @batcher end end # class SushiNet::StoreItemList Wiökszo è akcji jest wykonywana w metodzie render_content_on, która korzysta z BatchedList (do stronicowania) w celu wygenerowania stronicowanej listy äñczy do produktów. Jednak caäa zabawa zaczyna siö od wywoäania anchor, w którym jest przechowywane wywoäanie do wykonania po klikniöciu odpowiedniego äñcza. Nie ma jednak zgody, w jaki sposób wykorzystywaè kontynuacje do programowania WWW. HTTP zostaä zaprojektowany jako protokóä bezstanowy, a kontynuacje dla transakcji WWW sñ caäkowitym przeciwieþstwem bezstanowo ci. Wszystkie kontynuacje muszñ byè przecho- wywane na serwerze, co zajmuje pamiöè i przestrzeþ na dysku. Wymagane sñ równie doäñ- czajñce sesje do kierowania wywoäaþ u ytkownika na ten sam serwer. W wyniku tego, je eli jeden z serwerów zostanie wyäñczony, wszystkie jego sesje zostajñ utracone. Najbardziej po- pularna aplikacja Seaside, DabbleDB (http://dabbledb.com) wykorzystuje kontynuacje w bardzo maäym stopniu. 11 http://seaside.st. Techniki metaprogramowania _ 33
  • 29. Do éczenia Doäñczenia zapewniajñ kontekst dla warto ciowania w kodzie Ruby. Doäñczenia to zbiór zmien- nych i metod, które sñ dostöpne w okre lonym (leksykalnym) punkcie kodu. Ka de miejsce w kodzie Ruby, w którym sñ warto ciowane instrukcje, posiada doäñczenia i mogñ byè one po- brane za pomocñ Kernel#binding. Doäñczenia sñ po prostu obiektami klasy Binding i mogñ byè one przesyäane tak jak zwykäe obiekty: class C binding # => #<Binding:0x2533c> def a_method binding end end binding # => #<Binding:0x252b0> C.new.a_method # => #<Binding:0x25238> Generator rusztowania Rails zapewnia dobry przykäad zastosowania doäñczeþ: class ScaffoldingSandbox include ActionView::Helpers::ActiveRecordHelper attr_accessor :form_action, :singular_name, :suffix, :model_instance def sandbox_binding binding end # ... end ScaffoldingSandbox to klasa zapewniajñca czyste rodowisko, na podstawie którego generu- jemy szablon. ERb mo e generowaè szablon na podstawie kontekstu doäñczeþ, wiöc API jest dostöpne z poziomu szablonów ERb. part_binding = template_options[:sandbox].call.sandbox_binding # ... ERB.new(File.readlines(part_path).join,nil,'-').result(part_binding) Wcze niej wspomniaäem, e bloki sñ zamkniöciami. Doäñczenie zamkniöcia reprezentuje jego stan — zbiór zmiennych i metod, do których posiada dostöp. Doäñczenie zamkniöcia mo na uzyskaè za pomocñ metody Proc#binding: def var_from_binding(&b) eval("var", b.binding) end var = 123 var_from_binding {} # => 123 var = 456 var_from_binding {} # => 456 U yli my tutaj tylko obiektu Proc jako obiektu, dla którego pobierali my doäñczenie. Poprzez dostöp do doäñczeþ (kontekstu) tych bloków mo na odwoäaè siö do zmiennej lokalnej var przy pomocy zwykäego eval na doäñczeniu. 34 _ Rozdzia 1. Techniki podstawowe
  • 30. Introspekcja i ObjectSpace — analiza danych i metod w czasie dzia ania Ruby udostöpnia wiele metod pozwalajñcych na zaglñdanie do obiektów w czasie dziaäania programu. Dostöpne sñ metody dostöpu do zmiennych instancyjnych, ale poniewa äamiñ one zasadö hermetyzacji, nale y ich u ywaè z rozwagñ. class C def initialize @ivar = 1 end end c = C.new c.instance_variables # => ["@ivar"] c.instance_variable_get(:@ivar) # => 1 c.instance_variable_set(:@ivar, 3) # => 3 c.instance_variable_get(:@ivar) # => 3 Metoda Object#methods zwraca tablicö metod instancyjnych wraz z metodami typu singleton zdefiniowanymi w obiekcie odbiorcy. Je eli pierwszym parametrem methods jest false, zwra- cane sñ wyäñcznie metody typu singleton. class C def inst_method end def self.cls_method end end c = C.new class << c def singleton_method end end c.methods - Object.methods # => ["inst_method", "singleton_method"] c.methods(false) # => ["singleton_method"] Z kolei metoda Module#instance_methods zwraca tablicö metod instancyjnych klasy lub moduäu. Nale y zwróciè uwagö, e instance_methods jest wywoäywana w kontek cie klasy, na- tomiast methods — w kontek cie instancji. Przekazanie warto ci false do instance_methods powoduje, e zostanñ pominiöte metody klas nadrzödnych: C.instance_methods(false) # => ["inst_method"] Do analizy metod klasy C mo emy wykorzystaè metodö metaclass z metaid: C.metaclass.instance_methods(false) # => ["new", "allocate", "cls_method", "superclass"] Z mojego do wiadczenia wynika, e metody te sñ zwykle wykorzystywane do zaspokojenia ciekawo ci. Z wyjñtkiem bardzo niewielu dobrze zdefiniowanych idiomów rzadko zdarza siö, e w kodzie produkcyjnym wykorzystywana jest refleksja metod obiektu. Znacznie czö ciej techniki te sñ wykorzystywane we wierszu poleceþ konsoli do wyszukiwania dostöpnych metod obiektu — zwykle jest to szybsze od siögniöcia do podröcznika. Array.instance_methods.grep /sort/ # => ["sort!", "sort", "sort_by"] Techniki metaprogramowania _ 35
  • 31. ObjectSpace ObjectSpace to moduä wykorzystywany do interakcji z systemem obiektowym Ruby. Posia- da on kilka przydatnych metod moduäu, które uäatwiajñ operacje niskopoziomowe. x Metody usuwania nieu ytków: define_finalizer (konfiguruje metodö wywoäania zwrot- nego wywoäywanñ bezpo rednio przed zniszczeniem obiektu), undefine_finalizer (usuwa to wywoäanie zwrotne) oraz garbage_collect (uruchamia usuwanie nieu ytków). x _id2ref konwertuje ID obiektu na referencjö do tego obiektu Ruby. x each_object pozwala na iteracjö po wszystkich obiektach (lub wszystkich obiektach okre lonej klasy) i przekazuje je do bloku. Jak zawsze, du e mo liwo ci wiñ ñ siö ze znacznñ odpowiedzialno ciñ. Choè metody te mogñ byè przydatne, mogñ równie byè niebezpieczne. Nale y korzystaè z nich rozsñdnie. Przykäad prawidäowego zastosowania ObjectSpace znajduje siö w bibliotece Test::Unit. W kodzie tym metoda ObjectSpace.each_object jest wykorzystana do wyliczenia wszystkich istniejñcych klas, które dziedziczñ po Test::Unit::TestCase: test_classes = [] ObjectSpace.each_object(Class) { | klass | test_classes << klass if (Test::Unit::TestCase > klass) } Niestety ObjectSpace znacznie komplikuje niektóre z maszyn wirtualnych Ruby. W szczegól- no ci wydajno è JRuby znacznie spada po aktywowaniu ObjectSpace, poniewa interpreter Ruby nie mo e bezpo rednio przeglñdaè sterty JVM w poszukiwaniu obiektów. Z tego po- wodu JRuby musi ledziè obiekty wäasnymi mechanizmami, co powoduje powstanie znacz- nego narzutu czasowego. Poniewa ten sam mechanizm mo na uzyskaè przy wykorzystaniu metod Module.extend oraz Class.inherit, pozostaje niewiele przypadków, w których zasto- sowanie ObjectSpace jest niezbödne. Delegacja przy wykorzystaniu klas po redniczécych Delegacja jest odmianñ kompozycji. Jest podobna do dziedziczenia, ale pomiödzy kompo- nowanymi obiektami pozostawiona jest pewna „przestrzeþ” koncepcyjna. Delegacja pozwala na modelowanie relacji „zawiera”, a nie „jest”. Gdy obiekt deleguje operacjö do innego, nadal istniejñ dwa obiekty, a nie powstaje jeden obiekt bödñcy wynikiem zastosowania hierarchii dziedziczenia. Delegacja jest wykorzystywana w asocjacjach ActiveRecord. Klasa AssociationProxy deleguje wiökszo è metod (w tym class) do obiektów docelowych. W ten sposób asocjacje mogñ byè äadowane z opó nieniem (nie sñ äadowane do momentu odwoäania do danych) przy u yciu caäkowicie przezroczystego interfejsu. DelegateClass oraz Forwardable Standardowa biblioteka Ruby posiada mechanizmy dedykowane dla delegacji. Najprostszym jest DelegateClass. Przez dziedziczenie po DelegateClass(klass) oraz wywoäanie w kon- struktorze super(instance) klasa deleguje wszystkie wywoäania nieznanych metod do przeka- zanego obiektu klass. Jako przykäad we my klasö Settings, która deleguje wywoäania do Hash: 36 _ Rozdzia 1. Techniki podstawowe
  • 32. require 'delegate' class Settings < DelegateClass(Hash) def initialize(options = {}) super({:initialized_at => Time.now - 5}.merge(options)) end def age Time.now - self[:initialized_at] end end settings = Settings.new :use_foo_bar => true # Wywoáania metod s delegowane do obiektu settings[:use_foo_bar] # => true settings.age # => 5.000301 Konstruktor Settings wywoäuje super w celu ustawienia delegowanego obiektu na nowy obiekt Hash. Nale y zwróciè uwagö na ró nicö pomiödzy kompozycjñ a dziedziczeniem — je eli za- stosowaliby my dziedziczenie po Hash, Settings byäby obiektem Hash, natomiast w tym przypadku Settings zawiera obiekt Hash i deleguje do niego wywoäania. Taka relacja kompozycji zapewnia wiökszñ elastyczno è, szczególnie gdy obiekt do wydelegowania mo e zmieniaè siö (funkcja zapewniana przez SimpleDelegator). Biblioteka standardowa Ruby zawiera równie interfejs Forwardable, za pomocñ którego po- szczególne metody, a nie wszystkie niezdefiniowane metody, mogñ byè delegowane do in- nych obiektów. ActiveSupport w Rails zapewnia te same funkcje poprzez Module#delegate, a dodatkowo jej API jest znacznie ja niejsze: class User < ActiveRecord::Base belongs_to :person delegate :first_name, :last_name, :phone, :to => :person end Monkeypatching W Ruby wszystkie klasy sñ otwarte. Ka da klasa i obiekt mogñ byè modyfikowane w dowolnym momencie. Daje to mo liwo è rozszerzania lub zmieniania istniejñcych funkcji. Takie rozsze- rzanie mo e byè zrealizowane w bardzo elegancki sposób, bez modyfikowania oryginalnych definicji. Rails w znacznym stopniu korzysta z otwarto ci systemu klas Ruby. Otwieranie klas i dodawanie kodu jest nazywane monkeypatching (termin zapo yczony ze spoäeczno ci jözyka Python). Choè brzmi to odpychajñco, termin ten zostaä u yty w zdecydowanie pozytywnym wietle; technika ta jest uwa ana za niezwykle przydatnñ. Niemal wszystkie wtyczki do Rails wykonujñ w pewien sposób monkeypatching rdzenia Rails. Wady techniki monkeypatching Technika monkeypatching ma dwie gäówne wady. Przede wszystkim kod jednej metody mo e byè rozsiany po kilku plikach. Najwa niejszym tego przykäadem jest metoda process z ActionController. Metoda ta w czasie dziaäania jest przechwytywana przez metody z piö- ciu ró nych plików. Ka da z tych metod dodaje kolejnñ funkcjö: filtry, obsäugö wyjñtków, komponenty i zarzñdzanie sesjñ. Zysk z rozdzielenia komponentów funkcjonalnych na osob- ne pliki przewa a rozdmuchanie stosu wywoäaþ. Techniki metaprogramowania _ 37
  • 33. Innñ konsekwencjñ rozsiania funkcji jest problem z prawidäowym dokumentowaniem metod. Poniewa dziaäanie metody process mo e zmieniaè siö w zale no ci od zaäadowanego kodu, nie istnieje dobre miejsce do umieszczenia dokumentowania operacji dodawanych przez ka dñ z metod. Problem ten wynika z powodu zmiany identyfikacji metody process wraz z äñczeniem ze sobñ metod. Dodawanie funkcji do istniejécych metod Poniewa Rails wykorzystuje filozofiö rozdzielania problemów, czösto pojawia siö potrzeba rozszerzania funkcji istniejñcego kodu. W wielu przypadkach chcemy „dokleiè” fragment do istniejñcej funkcji bez wpäywania na kod tej funkcji. Ten dodatek nie musi byè bezpo rednio zwiñzany z oryginalnym przeznaczeniem funkcji — mo e zapewniaè uwierzytelnianie, reje- strowanie lub inne zagadnienia przekrojowe. Przedstawimy teraz kilka zagadnieþ zwiñzanych z problemami przekrojowymi i dokäadnie wyja nimy jeden (äñczenie metod), który zdobyä najwiöksze zainteresowanie w spoäeczno ci Ruby. Podklasy W tradycyjnym programowaniu obiektowym klasa mo e byè rozszerzana przez dziedziczenie po niej i zmianö danych lub dziaäania. Paradygmat ten dziaäa dla wiökszo ci przypadków, ale ma kilka wad: x Zmiany, jakie chcemy wprowadziè, mogñ byè niewielkie, co powoduje, e tworzenie nowej klasy jest zbyt skomplikowane. Ka da nowa klasa w hierarchii dziedziczenia powoduje, e zrozumienie kodu jest trudniejsze. x Mo e byè konieczne wprowadzenie serii powiñzanych ze sobñ zmian do klas, które nie sñ ze sobñ w inny sposób zwiñzane. Tworzenie kolejnych podklas mo e byè przesadñ, a do- datkowo spowoduje rozdzielenie funkcji, które powinny byè przechowywane razem. x Klasa mo e byè ju u ywana w aplikacji, a my chcemy globalnie zmieniè jej dziaäanie. x Mo na chcieè dodawaè lub usuwaè operacje w czasie dziaäania programu, co powinno da- waè globalny efekt. (Technika ta zostanie przedstawiona w peänym przykäadzie w dalszej czö ci rozdziaäu). W tradycyjnym programowaniu obiektowym funkcje te mogñ wymagaè zastosowania skom- plikowanego kodu. Kod nie tylko bödzie skomplikowany, ale równie znacznie ci lej zwiñ- zany z istniejñcym kodem lub kodem wywoäujñcym go. Programowanie aspektowe Programowanie aspektowe (AOP — ang. Aspect-oriented Programming) jest jednñ z technik, któ- re majñ za zadanie rozwiñzaè problemy z separacjñ zadaþ. Prowadzonych jest du o dyskusji na temat stosowania AOP w Ruby, poniewa wiele z zalet AOP mo na osiñgnñè przez u ycie metaprogramowania. Istnieje propozycja implementacji AOP bazujñcej na przeciöciach w Ruby12, ale zanim zostanie ona doäñczona do oficjalnej wersji, mogñ minñè miesiñce lub nawet lata. 12 http://wiki.rubygarden.org/Ruby/page/show/AspectOrientedRuby. 38 _ Rozdzia 1. Techniki podstawowe
  • 34. W AOP bazujñcym na przeciöciach, przeciöcia te sñ czasami nazywane „przezroczystymi podklasami”, poniewa w modularny sposób rozszerzajñ funkcje klas. Przeciöcia dziaäajñ tak jak podklasy, ale nie ma potrzeby tworzenia instancji tych podklas, wystarczy utworzyè instancje klas bazowych. Biblioteka Ruby Facets (facets.rubyforge.org) zawiera bibliotekö AOP bazujñcñ na przeciöciach, zrealizowanñ wyäñcznie w Ruby. Posiada ona pewne ograniczenia spowodowane tym, e jest napisana wyäñcznie w Ruby, ale jej u ycie jest dosyè jasne: class Person def say_hi puts "Cze ð!" end end cut :Tracer < Person do def say_hi puts "Przed metodî" super puts "Po metodzie" end end Person.new.say_hi # >> Przed metod # >> Cze ü! # >> Po metodzie Jak widaè, przeciöcie Tracer jest przezroczystñ podklasñ — gdy tworzymy instancjö Person, jest ona modyfikowana przez przeciöcie Tracer i „nie wie” ona nic o jego istnieniu. Mo emy równie zmieniè metodö Person#say_hi bez konieczno ci modyfikacji naszego przeciöcia. Z ró nych powodów techniki AOP w Ruby nie przyjöäy siö. Przedstawimy teraz standardowe metody radzenia sobie z problemami separacji w Ruby. éczenie metod Standardowym rozwiñzaniem tego problemu w Ruby jest äñczenie metod — nadawanie ist- niejñcej metodzie synonimu i nadpisywanie starej definicji nowñ tre ciñ. W nowej tre ci zwykle wywoäuje siö starñ definicjö metody przez odwoäanie siö do synonimu (odpowiednik wywo- äania super w dziedziczonej, nadpisywanej metodzie). W efekcie tego mo na modyfikowaè dziaäanie istniejñcych metod. Dziöki otwartej naturze klas Ruby mo na dodawaè funkcje do niemal ka dego fragmentu kodu. Oczywi cie trzeba pamiötaè, e musi byè to wykonywane rozwa nie, aby zachowaè przejrzysto è kodu. ãñczenie metod w Ruby jest zwykle realizowane za pomocñ standardowego idiomu. Zaäó my, e mamy pewnñ bibliotekö kodu, która pobiera obiekt Person poprzez sieè: class Person def refresh # (pobranie danych z serwera) end end Operacja trwa przez pewien czas, wiöc chcemy go zmierzyè i zapisaè wyniki. Wykorzystujñc otwarte klasy Ruby, mo emy po prostu otworzyè klasö Person i dodaè kod rejestrujñcy do metody refresh: Techniki metaprogramowania _ 39
  • 35. class Person def refresh_with_timing start_time = Time.now.to_f retval = refresh_without_timing end_time = Time.now.to_f logger.info "Refresh: #{"%.3f" % (end_time-start_time)} s." retval end alias_method :refresh_without_timing, :refresh alias_method :refresh, :refresh_with_timing end Mo emy umie ciè ten kod w osobnym pliku (byè mo e razem z pozostaäym kodem pomiaro- wym) i je eli doäñczymy ten plik za pomocñ require po oryginalnej definicji refresh, kod po- miarowy bödzie w odpowiedni sposób dodany przed wywoäaniem i po wywoäaniu oryginalnej metody. Pomaga to w zachowaniu separacji, poniewa mo emy podzieliè kod na kilka plików, w zale no ci od realizowanych zadaþ, a nie na podstawie obszaru, jaki jest modyfikowany. Dwa wywoäania alias_method wokóä oryginalnego wywoäania refresh pozwalajñ na doda- nie kodu pomiarowego. W pierwszym wywoäaniu nadajemy oryginalnej metodzie synonim refresh_without_timing (dziöki czemu otrzymujemy nazwö, poprzez którñ bödziemy siö odwoäywaè do oryginalnej metody z wnötrza refresh_with_timing), natomiast w drugim nadajemy naszej nowej metodzie nazwö refresh. Ten paradygmat u ycia dwóch wywoäaþ alias_method w celu dodania funkcji jest na tyle powszechny, e ma w Ruby swojñ nazwö — alias_method_chain. Wykorzystywane sñ tu dwa argumenty: nazwa oryginalnej metody oraz nazwa funkcji. Przy u yciu alias_method_chain mo emy poäñczyè dwa wywoäania alias_method w jedno: alias_method_chain :refresh, :timing Modularyzacja Technika monkeypatching daje nam ogromne mo liwo ci, ale za mieca przestrzeþ nazw modyfikowanej klasy. Czösto mo na osiñgnñè te same efekty w bardziej elegancki sposób, przez modularyzacjö dodatku i wstawienie moduäu w äaþcuch wyszukiwania klasy. Wtyczka Active Merchant, której autorem jest Tobias Lütke, wykorzystuje to podej cie w pomocy do widoków. Najpierw tworzony jest moduä z metodami pomocniczymi: module ActiveMerchant module Billing module Integrations module ActionViewHelper def payment_service_for(order, account, options = {}, &proc) ... end end end end end Nastöpnie, w skrypcie wtyczki init.rb, moduä jest doäñczany do ActionView::Base: require 'active_merchant/billing/integrations/action_view_helper' ActionView::Base.send(:include, ActiveMerchant::Billing::Integrations::ActionViewHelper) 40 _ Rozdzia 1. Techniki podstawowe
  • 36. Oczywi cie, znacznie pro ciej byäoby bezpo rednio otworzyè ActionView::Base i dodaè metodö, ale ta metoda pozwala wykorzystaè zaletö modularno ci. Caäy kod Active Merchant znajduje siö w module ActiveMerchant. Metoda ta ma jednñ wadö. Poniewa w doäñczonym module metody sñ wyszukiwane wedäug wäasnych metod klasy, nie mo na bezpo rednio nadpisywaè metod klasy przez doäñczenie moduäu: module M def test_method "Test z M" end end class C def test_method "Test z C" end end C.send(:include, M) C.new.test_method # => "Test z C" Zamiast tego powinni my utworzyè nowñ nazwö w module i skorzystaè z alias_method_chain: module M def test_method_with_module "Test z M" end end class C def test_method "Test z C" end end # W przypadku wtyczki te dwa wiersze znajd si w init.rb. C.send(:include, M) C.class_eval { alias_method_chain :test_method, :module } C.new.test_method # => "Test z M" Programowanie funkcyjne Paradygmat programowania funkcyjnego skupia siö na warto ciach, a nie efektach ubocznych warto ciowania. W odró nieniu od programowania imperatywnego styl funkcjonalny ope- ruje na warto ciach wyra eþ w sensie matematycznym. Aplikacje oraz kompozycje funkcyjne korzystajñ z koncepcji pierwszej klasy, a zmieniajñcy siö stan (który oczywi cie istnieje na niskim poziomie) jest niewidoczny dla programisty. Jest to dosyè zdradliwa koncepcja i jest ona czösto nieznana nawet do wiadczonym programi- stom. Najlepsze porównania sñ zaczerpniöte z matematyki, z której korzysta programowanie funkcyjne. Rozwa my równanie matematyczne x = 3. Znak równo ci w tym wyra eniu wskazuje na rów- nowa no è: „x jest równe 3”. Dla porównania, wyra enie x = 3 w Ruby ma zupeänie innñ na- turö. Znak równo ci oznacza przypisanie: „przypisz 3 do x”. Najwa niejsza ró nica polega na Programowanie funkcyjne _ 41
  • 37. tym, e jözyki programowania funkcyjnego okre lajñ, co nale y policzyè, natomiast jözyki pro- gramowania imperatywnego zwykle definiujñ, jak to policzyè. Funkcje wysokiego poziomu Kamieniami wögielnymi programowania funkcyjnego sñ oczywi cie funkcje. Gäównym spo- sobem wpäywu paradygmatu programowania funkcyjnego na gäówny kierunek rozwoju pro- gramowania w Ruby jest u ycie funkcji wysokiego poziomu (nazywanych równie funkcjami pierwszej kategorii, choè te dwa terminy nie sñ dokäadnie tym samym). Funkcje wysokiego poziomu sñ funkcjami dziaäajñcymi na innych funkcjach. Zwykle wymagajñ jednej lub wiöcej funkcji jako argumentów lub zwracajñ funkcjö. W Ruby funkcje sñ obsäugiwane zwykle jako obiekty wysokiego poziomu; mogñ byè one two- rzone, zmieniane, przekazywane, zwracane i wywoäywane. Funkcje anonimowe sñ reprezen- towane jako obiekty Proc tworzone za pomocñ Proc.new lub Kernel#lambda: add = lambda{|a,b| a + b} add.class # => Proc add.arity # => 2 # Wywoáanie Proc za pomoc Proc#call. add.call(1,2) # => 3 # Skáadnia alternatywna. add[1,2] # => 3 Najczöstszym zastosowaniem bloków w Ruby jest u ycie ich razem z iteratorami. Wielu pro- gramistów, którzy przeszli na Ruby z bardziej imperatywnych jözyków, zaczyna pisaè kod w nastöpujñcy sposób: collection = (1..10).to_a for x in collection puts x end Ten sam fragment kodu napisany zgodnie z duchem Ruby korzysta z iteratora, Array#each i przekazania warto ci do bloku. Jest to druga natura do wiadczonych programistów Ruby: collection.each {|x| puts x} Ta metoda jest odpowiednikiem utworzenia obiektu Proc i przekazania go do each: print_me = lambda{|x| puts x} collection.each(&print_me) Przykäady te majñ na celu pokazanie, e funkcje sñ obiektami pierwszej kategorii i mogñ byè traktowane tak jak inne obiekty. Modu Enumerable Moduä Enumerable w Ruby udostöpnia kilka przydatnych metod, które mogñ byè doäñczane do klas, które sñ „wyliczalne”, czyli mo na na nich wykonaè iteracjö. Metody te korzystajñ z metody instancyjnej each i opcjonalnie metody <=> (porównanie lub „statek kosmiczny”). Metody moduäu Enumerable mo na podzieliè na kilka kategorii. 42 _ Rozdzia 1. Techniki podstawowe
  • 38. Predykaty Reprezentujñ one wäa ciwo ci kolekcji, które mogñ przyjmowaè warto ci true lub false. all? Zwraca true, je eli dany blok zwraca warto è true dla wszystkich elementów w kolekcji. any? Zwraca true, je eli dany blok zwraca warto è true dla dowolnego elementu w kolekcji. include?(x), member?(x) Zwraca true, je eli x jest czäonkiem kolekcji. Filtry Metody te zwracajñ podzbiór elementów kolekcji. detect, find Zwraca pierwszy element z kolekcji, dla którego blok zwraca warto è true lub nil, je eli nie zostanie znaleziony taki element. select, find_all Zwraca tablicö elementów z kolekcji, dla których blok zwraca warto è true. reject Zwraca tablicö elementów z kolekcji, dla których blok zwraca warto è false. grep(x) Zwraca tablicö elementów z kolekcji, dla których x=== item ma warto è true. Jest to od- powiednik select{|item| x === item}. Transformatory Metody te przeksztaäcajñ kolekcjö na innñ, zgodnie z jednñ z kilku zasad. map, collect Zwraca tablicö skäadajñcñ siö z wyników danego bloku zastosowanego dla ka dego z ele- mentów kolekcji. partition Odpowiednik [select(&block), reject(&block)]. sort Zwraca nowñ tablicö elementów z kolekcji posortowanych przy u yciu bloku (traktowanego jako metoda <=>) lub wäasnej metody <=> elementu. sort_by Podobnie jak sort, ale warto ci, na podstawie których jest wykonywane sortowanie, sñ uzyskiwane z bloku. Poniewa porównywanie tablic jest wykonywane w kolejno ci ele- mentów, mo na sortowaè wedäug wielu pól przy u yciu person.sort_by{|p| [p.city, p.name]}. Wewnötrznie metoda sort_by korzysta z transformacji Schwartza, wiöc jest znacznie bardziej efektywna ni sort, je eli warto ciowanie bloku jest kosztowne. zip(*others) Zwraca tablicö krotek zbudowanych z kolejnych elementów self i others: puts [1,2,3].zip([4,5,6],[7,8,9]).inspect # >> [[1, 4, 7], [2, 5, 8], [3, 6, 9]] Programowanie funkcyjne _ 43
  • 39. Gdy wszystkie kolekcje sñ tej samej wielko ci, zip(*others) jest odpowiednikiem ([self]+ ´others).transpose: puts [[1,2,3],[4,5,6],[7,8,9]].transpose.inspect # >> [[1, 4, 7], [2, 5, 8], [3, 6, 9]] Gdy zostanie podany blok, jest on wykonywany raz dla ka dego elementu tablicy wyni- kowej: [1,2,3].zip([4,5,6],[7,8,9]) {|x| puts x.inspect} # >> [1, 4, 7] # >> [2, 5, 8] # >> [3, 6, 9] Agregatory Metody te pozwalajñ na agregowanie lub podsumowanie danych. inject(initial) Skäada operacje z kolekcji. Na poczñtku inicjowany jest akumulator (pierwsza warto è jest dostarczana przez initial) oraz pierwszy obiekt bloku. Zwrócona warto è jest u y- wana jako akumulator dla kolejnych iteracji. Suma kolekcji jest czösto definiowana w na- stöpujñcy sposób: module Enumerable def sum inject(0){|total, x| total + x} end end Je eli nie zostanie podana warto è poczñtkowa, pierwsza iteracja pobiera pierwsze dwa elementy. max Zwraca maksymalnñ warto è z kolekcji przy u yciu tych samych procedur co w przypadku metody sort. min Podobnie jak max, ale zwraca warto è minimalnñ w kolekcji. Pozosta e each_with_index Podobnie jak each, ale korzysta z indeksu zaczynajñcego siö od 0. entries, to_a Umieszcza kolejne elementy w tablicy, a nastöpnie zwraca tablicö. Metody moduäu Enumerable sñ bardzo przydatne i zwykle mo na znale è metodö odpo- wiedniñ do tego, co potrzebujemy. Je eli jednak nie znajdziemy odpowiedniej, warto odwiedziè witrynö Ruby Facets (http://facets.rubyforge.org). Enumerator Ruby zawiera równie maäo znany moduä biblioteki standardowej, Enumerator. (Poniewa jest to biblioteka standardowa, a nie podstawy jözyka, konieczne jest u ycie frazy require "enumerator"). 44 _ Rozdzia 1. Techniki podstawowe
  • 40. Moduä Enumerable zawiera wiele enumeratorów, które mogñ byè u yte dla dowolnego obiektu wyliczalnego, ale posiadajñ one jedno ograniczenie — wszystkie te iteratory bazujñ na metodach instancyjnych. Je eli bödziemy chcieli skorzystaè z innego iteratora ni each, jako podstawy dla map, inject lub innych funkcji z Enumerable, mo na skorzystaè z moduäu Enumerator jako äñcznika. Metoda Enumerator.new posiada sygnaturö Enumerator.new(obj, method,*args), gdzie obj jest obiektem do enumeracji, method jest bazowym iteratorem, a args to dowolne argumenty przekazywane do iteratora. Mo na na przykäad napisaè funkcjö map_with_index (odmiana map, która przekazuje obiekt z indeksem do bloku): require "enumerator" module Enumerable def map_with_index &b enum_for(:each_with_index).map(&b) end end puts ("a".."f").map_with_index{|letter, i| [letter, i]}.inspect # >> [["a", 0], ["b", 1], ["c", 2], ["d", 3], ["e", 4], ["f", 5]] Metoda enum_for zwraca obiekt Enumerator, którego ka da z metod dziaäa podobnie do each_with_index z oryginalnego obiektu. Ten obiekt Enumerator zostaä wcze niej rozszerzony o metody instancyjne z Enumerable, wiöc mo emy po prostu wywoäaè na nim map, przekazujñc odpowiedni blok. Enumerator dodaje równie do Enumerable kilka przydatnych metod. Metoda Enumerable# ´each_slice(n) iteruje po fragmentach tablicy, po n jednocze nie: (1..10).each_slice(3){|slice| puts slice.inspect} # >> [1, 2, 3] # >> [4, 5, 6] # >> [7, 8, 9] # >> [10] Podobnie Enumerable#each_cons(n) porusza „oknem przesuwnym” o rozmiarze n po kolekcji, o jeden element na raz: (1..10).each_cons(3){|slice| puts slice.inspect} # >> [1, 2, 3] # >> [2, 3, 4] # >> [3, 4, 5] # >> [4, 5, 6] # >> [5, 6, 7] # >> [6, 7, 8] # >> [7, 8, 9] # >> [8, 9, 10] Enumeracje zostaäy usprawnione w Ruby 1.9. Moduä Enumerator staä siö czö ciñ podstawowe- go jözyka. Dodatkowo iteratory zwracajñ automatycznie obiekt Enumerator, je eli nie zostanie do nich przekazany blok. W Ruby 1.8 do mapowania warto ci tablicy mieszajñcej wykorzysty- wany byä nastöpujñcy kod: hash.values.map{|value| ... } Na podstawie tablicy mieszajñcej tworzona byäa tablica warto ci, a nastöpnie mapowanie byäo realizowane na tej tablicy. Aby pominñè krok po redni, mo na u yè obiektu Enumerator: hash.enum_for(:each_value).map{|value| ... } Programowanie funkcyjne _ 45
  • 41. W ten sposób mamy obiekt Enumerator, którego ka da z metod dziaäa identycznie jak metoda each_value z klasy hash. Jest to zalecane w przeciwieþstwie do tworzenia potencjalnie du ych tablic, które sñ za chwilö zwalniane. W Ruby 1.9 jest to domy lne dziaäanie, o ile nie zostanie przekazany blok. Znacznie to upraszcza nasz kod: hash.each_value.map{|value| ... } Przyk ady Zmiany funkcji w czasie dzia ania Przykäad ten äñczy kilka technik przedstawionych w tym rozdziale. Wracamy do przykäadu Person, w którym chcemy zmierzyè czas dziaäania kilku kosztownych metod: class Person def refresh # ... end def dup # ... end end Nie chcemy pozostawiaè caäego kodu pomiarowego w rodowisku produkcyjnym, poniewa wprowadza on dodatkowy narzut czasowy. Jednak najlepiej pozostawiè sobie mo liwo è wäñczenia tej opcji w czasie rozwiñzywania problemów. Napiszemy wiöc kod, który pozwoli dodawaè i usuwaè funkcje (w tym przypadku kod pomiarowy) w czasie pracy programu bez modyfikowania kodu ródäowego. Najpierw napiszemy metody otaczajñce nasze kosztowne metody poleceniami pomiarowymi. Jak zwykle, wykorzystamy metodö monkeypatching do doäñczenia metod pomiarowych z in- nego pliku do Person, co pozwala oddzieliè kod pomiarowy od funkcji logiki modelu13: class Person TIMED_METHODS = [:refresh, :dup] TIMED_METHODS.each do |method| # Konfiguracja synonimu _without_timing dla oryginalnej metody. alias_method :"#{method}_without_timing", method # Konfiguracja metody _with_timing method, która otacza kod poddawany pomiarowi. define_method :"#{method}_with_timing" do start_time = Time.now.to_f returning(self.send(:"#{method}_without_timing")) do end_time = Time.now.to_f puts "#{method}: #{"%.3f" % (end_time-start_time)} s." end end end end 13 W tym przykäadowym kodzie wykorzystana zostaäa interpolacja zmiennych w literaäach symboli. Poniewa symbol jest definiowany z wykorzystaniem ciñgu w cudzysäowach, interpolacja zmiennych jest tak samo dozwolo- na jak w innych zastosowaniach ciñgu w cudzysäowach — symbol :"sym#{2+2}" jest tym samym co :sym4. 46 _ Rozdzia 1. Techniki podstawowe
  • 42. Aby wäñczaè lub wyäñczaè ledzenie, dodajemy do Person metody singleton: class << Person def start_trace TIMED_METHODS.each do |method| alias_method method, :"#{method}_with_timing" end end def end_trace TIMED_METHODS.each do |method| alias_method method, :"#{method}_without_timing" end end end Aby wäñczyè ledzenie, umieszczamy ka de wywoäanie metody w wywoäaniu metody pomia- rowej. Aby je wyäñczyè, po prostu wskazujemy metodö z powrotem na oryginalnñ metodö (która jest dostöpna tylko przez jej synonim _without_timing). Aby skorzystaè z tych dodatków, po prostu wywoäujemy metodö Person.trace: p = Person.new p.refresh # => (...) Person.start_trace p.refresh # => (...) # -> refresh: 0.500 s. Person.end_trace p.refresh # => (...) Gdy mamy teraz mo liwo è dodawania i usuwania kodu pomiarowego w czasie pracy, mo e- my udostöpniè ten mechanizm w aplikacji; mo emy udostöpniè administratorowi lub progra- mi cie interfejs do ledzenia wszystkich lub wybranych funkcji bez konieczno ci ponownego uruchomienia aplikacji. Podej cie takie ma kilka zalet w stosunku do dodawania kodu reje- strujñcego dla ka dej funkcji osobno: x Oryginalny kod jest niezmieniony, mo e on byè modyfikowany lub ulepszany bez wpäywa- nia na kod ledzñcy. x Po wyäñczeniu ledzenia kod dziaäa dokäadnie tak samo jak wcze niej, poniewa kod ledzñ- cy jest niewidoczny w ladzie stosu. Nie ma narzutu wydajno ciowego po wyäñczeniu ledzenia. Istnieje jednak kilka wad kodu, który sam siö modyfikuje: x ledzenie jest dostöpne tylko na poziomie funkcji. Bardziej szczegóäowe ledzenie wy- maga zmiany lub äatania oryginalnego kodu. W kodzie Rails jest to rozwiñzywane przez korzystanie z maäych metod o samoopisujñcych siö nazwach. x Po wäñczeniu ledzenia zapis stosu staje siö bardziej skomplikowany. Przy wäñczonym ledzeniu zapis stosu dla metody Person#refresh zawiera dodatkowy poziom — #refresh_with_timing, a nastöpnie #refresh_without_timing (oryginalna metoda). x Podej cie takie mo e zawie è przy u yciu wiöcej ni jednego serwera aplikacji, poniewa synonimy funkcji sñ tworzone w pamiöci. Zmiany nie sñ propagowane pomiödzy serwe- rami i zostanñ wycofane, gdy proces serwera zostanie ponownie uruchomiony. Jednak mo e to byè niewielki problem; zwykle nie profilujemy caäego ruchu w obciñ onym ro- dowisku produkcyjnym, a jedynie jego fragment. Przyk ady _ 47
  • 43. Kod sterujécy Rails Kod sterujñcy jest prawdopodobnie najbardziej skomplikowanym koncepcyjnie kodem w Rails. Kod ten podlega kilku ograniczeniom: x Segmenty cie ek mogñ przechwytywaè wiele czö ci adresu URL: x Kontrolery mogñ byè dzielone za pomocñ przestrzeni nazw, wiöc cie ka ":controller/: ´action/:id" mo e odpowiadaè adresowi URL "/store/product/edit/15" kontro- lera "store/product". x cie ki mogñ zawieraè segmenty path_info, które pozwalajñ na podziaä wielu seg- mentów URL: cie ka "page/*path_info" mo e odpowiadaè adresowi URL "/page/ ´products/top_products/15" z segmentem path_info przechwytujñcym pozostaäñ czö è URL. x cie ki mogñ byè ograniczane przez warunki, które muszñ byè speänione, aby dopasowaè cie kö. x System cie ek musi byè dwukierunkowy; dziaäa on w przód w celu rozpoznawania cie ek i w tyä do ich generowania. x Rozpoznawanie cie ek musi byè szybkie, poniewa jest wykonywane dla ka dego ñ- dania HTTP. Generowanie cie ek musi byè niezwykle szybkie, poniewa mo e byè wy- konywane dziesiñtki razy na ñdanie HTTP (po jednym na äñcze wychodzñce) podczas tworzenia strony. Nowy kod routing_optimisation w Rails 2.0 (actionpack/lib/action_controller/ ´routing_optimisation.rb), którego autorem jest Michael Koziarski, rozwiñzuje pro- blem zäo ono ci sterowania w Rails. W nowym kodzie zoptymalizowany zostaä pro- sty przypadek generowania nazwanych cie ek bez dodatkowych :requirements. Poniewa szybko è jest wymagana zarówno przy generowaniu, jak i rozpoznawaniu, kod sterujñcy modyfikuje siö w czasie dziaäania. Klasa ActionController::Routing::Route repre- zentuje pojedynczñ cie kö (jeden wpis w config/routes.rb). Metoda Route#recognize sama siö modyfikuje: class Route def recognize(path, environment={}) write_recognition recognize path, environment end end Metoda recognize wywoäuje write_recognition, która przetwarza cie kö i tworzy jej skompilowanñ wersjö. Metoda write_recognition nadpisuje definicjö recognize za pomocñ tej definicji. W ostatnim wierszu oryginalnej metody recognize wywoäywana jest metoda recognize (która zostaäa zastñpiona przez jej skompilowanñ wersjö) z oryginalnymi argumen- tami. W ten sposób cie ka jest skompilowana w pierwszym wywoäaniu recognize. Wszystkie kolejne wywoäania korzystajñ ze skompilowanej wersji i nie wykonujñ ponownie parsowania i wykonywania kodu kierujñcego. Poni ej przedstawiona jest metoda write_recognition: def write_recognition # Tworzenie struktury if do pobrania parametrów, o ile wyst puj . 48 _ Rozdzia 1. Techniki podstawowe
  • 44. body = "params = parameter_shell.dupn#{recognition_extraction * "n"}nparams" body = "if #{recognition_conditions.join(" && ")}n#{body}nend" # Budowanie deklaracji metody i jej kompilacja. method_decl = "def recognize(path, env={})n#{body}nend" instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" method_decl end Zmienna lokalna body jest wypeäniana skompilowanym kodem cie ki. Jest ona umieszczona w deklaracji metody nadpisujñcej recognize. Dla domy lnej cie ki: map.connect ':controller/:action/:id' metoda write_recognition generuje nastöpujñcy kod: def recognize(path, env={}) if (match = /(long regex)/.match(path)) params = parameter_shell.dup params[:controller] = match[1].downcase if match[1] params[:action] = match[2] || "index" params[:id] = match[3] if match[3] params end end Metoda parameter_shell zwraca domy lny zbiór parametrów zwiñzanych ze cie kñ. W tre ci tej metody wykonywane sñ testy przy u yciu wyra enia regularnego, wypeäniana i zwracana jest tablica params, o ile parametry zostanñ wykryte przez wyra enie regularne. Je eli nie zo- stanñ wykryte, metoda zwraca nil. Po utworzeniu tre ci metody jest ona warto ciowana w kontek cie cie ki, przy u yciu instance_eval. Powoduje to nadpisanie metody recognize okre lonej cie ki. Propozycje dalszych lektur wietnym wprowadzeniem do wewnötrznych mechanizmów Ruby jest Ruby Hacking Guide, którego autorem jest Minero AOKI. Pozycja ta zostaäa przetäumaczona na angielski i jest do- stöpna pod adresem http://rhg.rubyforge.org. Kilka kolejnych artykuäów technicznych na temat Ruby mo na znale è na witrynie Eigenclass (http://eigenclass.org). Evil.rb jest bibliotekñ pozwalajñcñ na dostöp do wnötrza obiektów Ruby. Pozwala ona na zmianö wewnötrznego stanu obiektów, ledzenie i przeglñdanie wska ników klass oraz su- per, zmianö klasy obiektu i powodowanie niezäego zamötu. Nale y korzystaè z niej rozwa - nie. Biblioteka jest dostöpna pod adresem http://rubyforge.org/projects/evil/. Mauricio Fernández przedstawia mo liwo ci biblioteki Evil w artykule dostöpnym pod adresem http://eigenclass.org/ ´hiki.rb?evil.rb+dl+and+unfreeze. Jamis Buck w dokäadny sposób przedstawia kod sterujñcy Rails, jak równie kilka innych zäo onych elementów Rails — pod adresem http://weblog.jamisbuck.org/under-the-hood. Jednym z najäatwiejszych do zrozumienia i najlepiej zaprojektowanych fragmentów Ruby, z jakim miaäem do czynienia, jest Capistrano 2, którego autorem jest równie Jamis Buck. Propozycje dalszych lektur _ 49
  • 45. Capistrano ma nie tylko jasne API, ale jest równie niezwykle dobrze napisany. Zagäöbienie siö w szczegóäach Capistrano jest warte po wiöconego czasu. Kod ródäowy jest dostöpny za po rednictwem Subversion pod adresem http://svn.rubyonrails.org/rails/tools/capistrano. Ksiñ ka High-Order Perl (Morgan Kaufman Publishers), której autorem jest Mark Jason Dominus, byäa rewolucjñ przy wprowadzaniu koncepcji programowania funkcyjnego w jözyku Perl. Gdy ksiñ ka ta zostaäa wydana, w roku 2005, jözyk ten nie byä znany ze wsparcia programowania funkcyjnego. Wiökszo è przykäadów z tej ksiñ ki zostaäa przetäumaczona dosyè wiernie na Ruby; jest to dobre èwiczenie, je eli Czytelnik zna Perl. James Edvard Gray II napisaä swojñ wersjö High-Order Ruby, dostöpnñ pod adresem http://blog.grayproductions.net/categories/highe ´rorder_ruby. Ksiñ ka Ruby Programming Language, autorstwa Davida Flanagana i Yukihiro Matsumoto (O’Reilly), obejmuje zarówno Ruby 1.8, jak i 1.9. Zostaäa ona wydana w styczniu 2008 roku. Jej czö è po wiöcono technikom programowania funkcyjnego w Ruby. 50 _ Rozdzia 1. Techniki podstawowe

×