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!
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
<% 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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
* 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
0 comments
Post a comment