Cyfroteka.pl

klikaj i czytaj online

Cyfro
Czytomierz
00104 005894 13638050 na godz. na dobę w sumie
Rails. Projektowanie systemów klasy enterprise - książka
Rails. Projektowanie systemów klasy enterprise - książka
Autor: Liczba stron: 328
Wydawca: Helion Język publikacji: polski
ISBN: 978-83-246-2198-9 Data wydania:
Lektor:
Kategoria: ebooki >> komputery i informatyka >> webmasterstwo >> rails - programowanie
Porównaj ceny (książka, ebook, audiobook).

Poznaj najbardziej zaawansowane tajniki Rails!

Rynek szkieletów aplikacji internetowych jest niezwykle urozmaicony. Wśród wielu dostępnych opcji można znaleźć tu rozwiązania przeznaczone dla projektów o różnej skali złożoności, zarówno te mniej, jak i bardziej popularne. Warto jednak sięgnąć po rozwiązanie absolutnie unikatowe i wyjątkowe - Rails. Szkielet ten świetnie sprawdza się zarówno w projektach małych, jak i tych klasy enterprise, a ponadto znany jest ze swoich możliwości, wydajności oraz elastyczności. Warto także podkreślić, że w pakiecie razem z nim dostaniemy liczną, chętną do pomocy społeczność użytkowników!

Autor książki porusza interesujące kwestie związane z budową zaawansowanych systemów informatycznych opartych o Rails. W trakcie lektury dowiesz się, jak wykorzystać system wtyczek jako środek organizujący Twój kod oraz jak w tej roli sprawdzą się moduły. Kolejne rozdziały przyniosą solidny zastrzyk wiedzy na temat tworzenia rozbudowanego i bezpiecznego modelu danych, dziedziczenia wielotabelarycznego oraz wykorzystania wyzwalaczy jako narzędzia kontroli skomplikowanych zależności w danych. Dan Chak duży nacisk kładzie na zagadnienia związane z SOA (skrót od ang. Service Oriented Architecture) oraz wydajnością. Jest to genialna pozycja dla wszystkich programistów i projektantów uczestniczących w projekcie wytwarzanym z wykorzystaniem Rails.

Obowiązkowa pozycja dla wszystkich programistów i projektantów korzystających z Rails!

Znajdź podobne książki Ostatnio czytane w tej kategorii

Darmowy fragment publikacji:

Rails. Projektowanie systemów klasy enterprise Autor: Dan Chak T³umaczenie: Andrzej Gra¿yñski ISBN: 978-83-246-2198-9 Tytu³ orygina³u: Enterprise Rails Format: 168×237, stron: 328 Poznaj najbardziej zaawansowane tajniki Rails! (cid:129) Jak zorganizowaæ kod, wykorzystuj¹c system wtyczek lub modu³y? (cid:129) Jakie zalety posiada architektura SOA? (cid:129) Jak zwiêkszyæ wydajnoœæ Rails? Rynek szkieletów aplikacji internetowych jest niezwykle urozmaicony. Wœród wielu dostêpnych opcji mo¿na znaleŸæ tu rozwi¹zania przeznaczone dla projektów o ró¿nej skali z³o¿onoœci, zarówno te mniej, jak i bardziej popularne. Warto jednak siêgn¹æ po rozwi¹zanie absolutnie unikatowe i wyj¹tkowe – Rails. Szkielet ten œwietnie sprawdza siê zarówno w projektach ma³ych, jak i tych klasy enterprise, a ponadto znany jest ze swoich mo¿liwoœci, wydajnoœci oraz elastycznoœci. Warto tak¿e podkreœliæ, ¿e w pakiecie razem z nim dostaniemy liczn¹, chêtn¹ do pomocy spo³ecznoœæ u¿ytkowników! Autor ksi¹¿ki porusza interesuj¹ce kwestie zwi¹zane z budow¹ zaawansowanych systemów informatycznych opartych o Rails. W trakcie lektury dowiesz siê, jak wykorzystaæ system wtyczek jako œrodek organizuj¹cy Twój kod oraz jak w tej roli sprawdz¹ siê modu³y. Kolejne rozdzia³y przynios¹ solidny zastrzyk wiedzy na temat tworzenia rozbudowanego i bezpiecznego modelu danych, dziedziczenia wielotabelarycznego oraz wykorzystania wyzwalaczy jako narzêdzia kontroli skomplikowanych zale¿noœci w danych. Dan Chak du¿y nacisk k³adzie na zagadnienia zwi¹zane z SOA (skrót od ang. Service Oriented Architecture) oraz wydajnoœci¹. Jest to genialna pozycja dla wszystkich programistów i projektantów uczestnicz¹cych w projekcie wytwarzanym z wykorzystaniem Rails. (cid:129) Komponenty aplikacji (cid:129) Organizacja kodu z wykorzystaniem wtyczek (cid:129) Rola modu³ów w porz¹dkowaniu kodu (cid:129) Budowa solidnego modelu danych (cid:129) Normalizacja modelu (cid:129) Obs³uga danych dziedzinowych (cid:129) Wykorzystanie wyzwalaczy w celu kontroli zale¿noœci w danych (cid:129) Dziedziczenie jedno- i wielotabelaryczne (cid:129) Zastosowanie modeli widokowych (cid:129) Architektura SOA (cid:129) Dostarczanie us³ug typu XML-RPC (cid:129) Us³ugi typu REST (cid:129) Zwiêkszenie wydajnoœci Rails Obowi¹zkowa pozycja dla wszystkich programistów i projektantów korzystaj¹cych z Rails! Spis treļci Wstýp .............................................................................................................................9 Co to znaczy „enterprise?” Powolny wzrost Komponenty aplikacji 1. Widok z góry ................................................................................................................ 19 19 21 24 24 26 29 32 33 33 Warstwa danych Warstwa aplikacyjna Warstwa cache’owania System komunikacyjny Serwer WWW Zapora sieciowa KorzyĈci Tworzenie wäasnej wtyczki 2. Wtyczki jako ļrodek organizacji kodu ........................................................................35 36 37 38 40 45 45 Rozszerzanie klas wbudowanych Rozszerzenia uniwersalne WdraĔanie svn:externals Pliki i katalogi Granice moduäu wyznaczajñ przestrzeþ nazw 3. Organizacja kodu za pomocé moduĥów .....................................................................47 47 49 50 51 51 53 54 Miödzymoduäowe skojarzenia klas modelowych Relacje wzajemne Modularyzacja jako wstöp do architektury usäugowej Wymuszenie prawidäowej kolejnoĈci äadowania definicji klas çwiczenia 3 Refaktoring Wyodröbnianie moduäów fizycznych Uwalnianie metod uĔytkowych 54 54 55 Baza danych jako czöĈè aplikacji „Jedno Ĉrodowisko wyznacza reguäy” „Nasi programiĈci nie popeäniajñ bäödów” „Baza danych moja i tylko moja” 4. Baza danych jak forteca ..............................................................................................57 58 58 58 59 59 59 60 62 63 Wybór wäaĈciwego systemu bazy danych À propos migracji Obalajñc mity… Raporty, raporty… Siadajñc na ramionach gigantów Bilety do kina 5. Budowanie solidnego modelu danych ........................................................................67 67 68 70 78 78 85 Na poczñtek bardzo prosto Ograniczenia Obalamy mity IntegralnoĈè referencyjna Wprowadzenie do indeksowania Trzecia postaè normalna Zacznij od normalizacji 6. Refaktoryzacja bazy do trzeciej postaci normalnej ...................................................87 87 91 92 95 96 Dziedziczenie tabel i domieszki çwiczenia Refaktoryzacja 7. Dane dziedzinowe .......................................................................................................97 99 101 104 Kody pocztowe i geograficzne dane dziedzinowe Wzorzec projektowy — strategia dla tabel dziedzinowych Refaktoryzacja od samego poczñtku Wybór kluczy naturalnych Siedzñc juĔ na ramionach giganta… Klucze naturalne — korzyĈci i käopoty 8. Klucze zĥoŜone i postaë normalna DKNF .................................................................. 107 108 111 112 113 116 120 122 Migracja do postaci normalnej DKNF Klucze wielokolumnowe i ich implementacja w Rails Odroczona kontrola integralnoĈci referencyjnej CoĈ za coĈ… 4 _ Spis treļci çwiczenia Refaktoryzacja Klucz jednokolumnowy Klucz wielokolumnowy 123 124 124 125 9. Wyzwalacze jako narzýdzia kontroli skomplikowanych zaleŜnoļci wewnétrz danych .......................................................................................................127 127 Kontrola ograniczeþ za pomocñ wyzwalaczy 130 Anatomia funkcji w jözyku PL/pgSQL 131 131 132 132 132 133 To tylko äaþcuchy… Zmienne lokalne i przypisywanie im wartoĈci Bloki Dodatkowe cechy wyzwalacza Wyzwalacz — äagodna zapora lub bezpiecznik Instrukcje warunkowe O co chodzi? Polimorfizm — co to jest? Dziedziczenie a dane fizyczne Dziedziczenie jednotabelaryczne Dziedziczenie wielotabelaryczne Alternatywa wyäñczajñca dla zbioru kolumn Implementacja MTI w Rails 10. Dziedziczenie wielotabelaryczne ............................................................................. 135 135 137 138 140 140 143 145 151 152 152 152 153 Klasy-fabryki çwiczenia Refaktoryzacja Z STI do MTI Z :polymorphic = true do MTI Widoki Definiowanie widoku Definiowanie klasy modelowej na bazie widoku Specyfika widoków 11. Modele widokowe ..................................................................................................... 155 156 156 157 158 159 159 160 161 161 Dodawanie, modyfikowanie i usuwanie rekordów Ograniczenia i klucze obce Indeksowanie çwiczenia Refaktoryzacja Spis treļci _ 5 Formatowanie widoku OdĈwieĔanie i uniewaĔnianie sterowane wyzwalaczami Tabela docelowa Funkcje odĈwieĔajñce i uniewaĔniajñce Zarzñdzanie zaleĔnoĈciami czasowymi Kto za to päaci? Reguäy rzñdzñce widokami zmaterializowanymi Widok Ēródäowy 12. Widoki zmaterializowane ......................................................................................... 163 164 165 166 168 169 171 172 175 176 178 178 179 180 181 183 184 185 186 187 Indeksowanie widoku zmaterializowanego To siö naprawdö opäaca… Kaskadowe cache’owanie widoków çwiczenia Tabela movie_showtimes Tabela movies Tabela theatres Tabela orders Tabela purchased_tickets Ukrycie implementacji dziöki widokowi uzgadniajñcemu Periodyczne odĈwieĔanie Czym jest SOA? Dlaczego SOA? 13. SOA — zaczynamy .................................................................................................... 189 189 192 193 196 202 202 205 205 Wspóädzielenie zasobów Redukcja obciñĔenia baz danych SkalowalnoĈè i cache’owanie Lokalna redukcja zäoĔonoĈci Podsumowanie çwiczenia Specyfika usäug Ukryta implementacja Przystöpne API Projektowanie API 14. Specyfika SOA ............................................................................................................207 207 207 210 211 211 213 214 215 Nie rozdrabniaj siö Ogranicz kontakty Korzystaj ze wspóäbieĔnoĈci Tylko to — i nic wiöcej 6 _ Spis treļci REST, XML-RPC i SOAP XML-RPC SOAP 217 217 219 ActionWebService w Rails 2.0 Definiowanie bariery abstrakcji ActiveRecord jako warstwa modelu fizycznego Warstwa modelu logicznego Definiowanie API 15. Usĥugi typu XML-RPC ................................................................................................ 221 221 222 222 224 229 233 235 236 237 238 Wspóädzielony kod Kliencka klasa-singleton Testy integracyjne Wiöcej testów… Wtyczka kliencka Usäuga zamówieþ — OrdersService Integracja z usäugñ MoviesService 16. Przechodzimy na SOA ............................................................................................... 241 242 252 254 256 265 Model obiektowy usäugi MoviesService Podsumowanie Konsekwencje… Podstawy REST REST a SOA Zasoby i polecenia Sprzöt jest czöĈciñ aplikacji 17. Usĥugi typu REST ........................................................................................................267 267 267 269 270 270 271 273 274 276 276 REST a CRUD Uniwersalny interfejs HTTP+POX Definiowanie kontraktu usäugi Klient REST w Rails REST czy XML-RPC? 18. Usĥugi webowe typu RESTful ....................................................................................279 279 281 281 283 Sformuäowanie zadania Narzödzia ROXML Net::HTTP Spis treļci _ 7 Usäuga MoviesWebService Implementacja zasobów serwera Implementacja akcji serwera Implementacja klienta 284 284 287 288 Dla przypomnienia — cache’owanie w warstwie fizycznej Migawki Funkcja odĈwieĔajñca Wyzwalacze uniewaĔniajñce Indeksowanie 19. Cache’owanie ............................................................................................................295 296 296 297 297 298 298 304 307 310 311 311 312 313 314 Cache’owanie planu realizacji Cache’owanie Ĕñdaþ Cache’owanie w Rails Cache’owanie fragmentów, akcji i stron Cache’owanie modeli logicznych Uwarunkowania Puäapka nieaktualnych danych Indeksowanie cache Inne aspekty cache’owania Skorowidz .................................................................................................................. 315 8 _ Spis treļci ROZDZIAĤ 8. Klucze zĥoŜone i postaë normalna DKNF Nasz obecny model znacznie róĔni siö od swego pierwowzoru z rysunku 5.1, doĈwiadczyä bowiem szeregu przeobraĔeþ, polegajñcych na (przypomnijmy): x rozszerzeniu definicji schematu o ograniczenia (constraints) narzucone na dopuszczalnñ postaè danych, x wymuszeniu kontroli integralnoĈci referencyjnej, x skojarzeniu podstawowych indeksów z tabelami, x usuniöciu redundancji danych drogñ dziedziczenia tabel i dziedziczenia klas modelowych oraz zamkniöciu definicji tych ostatnich w formö wtyczek Rails, x utworzeniu nowych tabel na bazie kolumn, których tematyczne rozszerzenie stwarzaäoby ryzyko naruszenia reguä trzeciej postaci normalnej, x magazynowaniu bazy wiedzy aplikacji w postaci tabel dziedzinowych i odpowiadajñcych im klas modelowych i staäych Rails. To bardzo wiele, jednak naszemu modelowi wciñĔ jeszcze trochö brakuje do tego, by uznaè go za wystarczajñco solidny dla aplikacji enterprise. W tym rozdziale zajmiemy siö dwoma mechanizmami, dziöki którym moĔna ów dystans wyraĒnie zmniejszyè: pierwszym z nich sñ klucze zäoĔone, drugim — klucze naturalne dla domen (zwane po prostu „kluczami domenowy- mi”), czyli sekwencje kolumn jednoznacznie identyfikujñce rekordy w ramach tabeli. W kwestii kluczy naturalnych Rails znacznie uäatwia zadanie programistom, przyjmujñc dla danej tabeli pojedynczñ kolumnö id w charakterze jej klucza gäównego. Z jednej strony, pro- gramiĈci nie muszñ siö wiöc martwiè o definiowanie kluczy naturalnych zgodnych z naturñ danych przechowywanych w tabeli, z drugiej jednak, pozbawiajñ siö w ten sposób pewnych zalet sprawiajñcych, Ĕe klucze takie przewyĔszajñ standardowe klucze oparte na kolumnach id (dla prostoty w dalszym ciñgu bödziemy je nazywaè po prostu „kluczami id”). W rzeczy- wistoĈci obydwa typy kluczy majñ swoje säabe i mocne strony, wäaĈnie im poĈwiöcimy zna- czñcñ czöĈè tego rozdziaäu. PokaĔemy m.in., jak za pomocñ wtyczek pogodziè moĔna klucze naturalne z konwencjami Rails; nastöpnie zademonstrujemy, jak moĔna zjeĈè przysäowiowe ciastko i mieè je nadal, czyli jak pogodziè klucze definiowane przez programistów ze standar- dowymi kluczami opartymi na kolumnach id. Jak siö ostatecznie okaĔe, wszystko to osiñgnñè moĔna za cenö umiarkowanego wysiäku programisty. Przeanalizujmy zatem najpierw zalety, jakimi cechujñ siö standardowe klucze id. Najbardziej oczywistñ ich zaletñ jest natychmiastowa dostöpnoĈè — sñ zdefiniowane i czekajñ na to, by ich uĔyè; skojarzenia miödzy tabelami, ustanawiane za pomocñ metod has_many, belongs_to 107 oraz has_and_belongs_to_many, realizowane sñ wäaĈnie za poĈrednictwem kluczy id. Ma to niebagatelne znaczenie, gdy trzeba naprödce stworzyè niewyszukanñ, ale jednak dziaäajñcñ aplikacjö. Drugñ kapitalnñ zaletñ kluczy id jest fakt, Ĕe jako niewchodzñce w skäad „zasadniczej” treĈci przechowywanej w rekordzie pozostajñ bez zwiñzku z edytowaniem tegoĔ rekordu; innymi säowy, edycja rekordu nigdy nie powoduje zmiany klucza gäównego. W rezultacie uĔytkow- nik otrzymuje moĔliwoĈè nieskröpowanego edytowania wszystkich pól. Naruszenie klucza gäównego ma niebagatelne konsekwencje w kontekĈcie integralnoĈci refe- rencyjnej, wymaga bowiem zrewidowania wszystkich zaleĔnoĈci rekordów w innych tabelach od rekordu wäaĈnie zmodyfikowanego. ZaäóĔmy na chwilö, Ĕe pole rating_name peäni rolö klucza gäównego tabeli ratings; dla rekordu, w którym pole to równe jest PG-13, istniejñ praw- dopodobnie skorelowane rekordy w tabeli movies, zawierajñce w polu rating_id tenĔe äaþ- cuch PG-13. JeĔeli w tabeli ratings zmienilibyĈmy zawartoĈè rzeczonego pola na PG13, zmu- szeni bylibyĈmy zrewidowaè równieĔ zawartoĈè odpowiednich rekordów w tabeli movies. UĔywajñc kluczy id, jesteĈmy wolni od tego problemu, bowiem wartoĈè wpisana w pole id säuĔy wyäñcznie kojarzeniu rekordów i nie ma Ĕadnego powodu, by jñ w jakikolwiek sposób jawnie zmieniaè — to jest trzecia zaleta wspomnianych kluczy. Wreszcie, kluczom id bardzo äatwo zapewniè unikalnoĈè, bowiem generowanie „nastöpnej” wartoĈci dla nowo dodawanego rekordu odbywa siö automatycznie, na bazie sekwencji defi- niowanej w schemacie oraz wbudowanego w klasy modelowe mechanizmu serializacji. Dla kluczy naturalnych lista korzyĈci nie jest juĔ tak oczywista, mniej oczywiste bowiem sñ zasady ich uĔywania. W przeciwieþstwie do pola id, którego obecnoĈè nie budzi Ĕadnych wñtpliwoĈci, nie zawsze da siö w schemacie tabeli zidentyfikowaè zestaw kolumn, których (äñczna) zawartoĈè z natury jest unikalna dla rekordów tej tabeli i moĔe kaĔdy z tych rekor- dów jednoznacznie identyfikowaè. JeĔeli jednak taki zestaw da siö okreĈliè w sposób niebu- dzñcy wñtpliwoĈci, warto obsadziè go w roli klucza naturalnego, ze wzglödu na wynikajñce z tego korzyĈci dotyczñce zachowania integralnoĈci danych. Mimo zatem pozornie wiökszej wygody uĔywania kluczy id, w pewnych sytuacjach stoso- wanie kluczy naturalnych jest wysoce uzasadnione, a niekiedy wröcz nieodzowne. Za chwi- lö pokaĔemy, jak zastñpienie klucza id kluczem naturalnym pomoĔe uchroniè bazö danych przed powstaniem powaĔnej luki w integralnoĈci danych — luki niemoĔliwej do wykrycia na poziomie ograniczeþ wpisanych w schemat bazy. Klucze naturalne — korzyļci i kĥopoty O wartoĈci kluczy naturalnych niech przekona czytelników konkretny przykäad, integralnie zwiñzany z naszym serwisem obsäugujñcym internetowñ sprzedaĔ biletów. Na rysunku 8.1 widzimy jego fragment — tabelö movie_showtimes, skorelowanñ z tabelñ auditoriums, która z kolei skorelowana jest z tabelñ theatres. Korelacje te oparte sñ na standardowych dla Rails kluczach id, zgodnie z poniĔszymi definicjami w schemacie: movie_showtimes(auditorium_id) references auditorium(id) auditoriums(theatre_id) references theatres(id) Istniejñce niegdyĈ bezpoĈrednie powiñzanie tabeli movie_showtimes z tabelñ theatres (po- przez klucz obcy theatre_id) zostaäo (jak pamiötamy z rozdziaäu 6.) usuniöte ze wzglödu na zachowanie zgodnoĈci z reguäami trzeciej postaci normalnej i pewnñ anomaliö spowodowanñ 108 _ Rozdziaĥ 8. Klucze zĥoŜone i postaë normalna DKNF Rysunek 8.1. PoĈrednie powiñzanie tabeli projekcji z tabelñ kin brakiem tej zgodnoĈci. Robiñc krok w niewñtpliwie dobrym kierunku, jednoczeĈnie doprowa- dziliĈmy do trochö dziwnej sytuacji. OtóĔ, odwoäanie do kina zwiñzanego z konkretnñ pro- jekcjñ musi teraz nastöpowaè poĈrednio poprzez tabelö reprezentujñcñ sale projekcyjne; gdy chcemy uzyskaè nieskomplikowanñ w gruncie rzeczy informacjö na temat äñcznej liczby se- ansów w kinie, w którym wyĈwietlany jest film reprezentowany przez bieĔñcy rekord z tabe- li movie_showtimes, nie moĔemy juĔ napisaè po prostu, jak niegdyĈ: select count(*) from movie_showtimes where theatre_id = ? lecz musimy trochö siö pogimnastykowaè: select count(*) from auditoriums a, movie_showtimes ms, where ms.auditorium_id = a.id and a.theatre_id = ? MoĔe wiöc usuniöcie bezpoĈredniego powiñzania tabel movie_showtimes i theatres byäo de- cyzjñ zbyt pochopnñ? Przywróèmy je wiöc (jak na rysunku 8.2), dodajñc do definicji schematu kolejnñ klauzulö: movie_showtimes(theatre_id) references theatres(id) A teraz zobaczmy, jak tym drobnym posuniöciem uczyniliĈmy potöĔnñ wyrwö w spójnoĈci przechowywanych danych. Przypomnijmy mianowicie opisanñ w rozdziale 6. anomaliö po- legajñcñ na tym, Ĕe oba odwoäania do tabeli theatres — bezpoĈrednie oraz poprzez tabelö auditoriums — prowadzñ do dwóch róĔnych kin. Mimo iĔ jest to ewidentna anomalia, z punk- tu widzenia definicji zawartych w schemacie wszystko jest w porzñdku — próba zapisania „anormalnych” danych nie spowoduje wystñpienia wyjñtku. Ale to jeszcze nie koniec. UwaĔny czytelnik z pewnoĈciñ zauwaĔyä, Ĕe majñc moĔliwoĈè nie- zaleĔnego ustanawiania powiñzaþ tabeli movie_showtimes z tabelami auditoriums i theatres, moĔna doprowadziè do sytuacji, w której odnoĈna sala projekcyjna nie bödzie czöĈciñ odno- Ĉnego kina! Spójrzmy na poniĔsze dane: Klucze naturalne — korzyļci i kĥopoty _ 109 Rysunek 8.2. Przywrócone pomocnicze powiñzanie tabel movie_showtimes i theatres movies_development=# select id, name from theatres; id | name ----+------------------- 1 | Steller Theatre 2 | Old Towne Theatre (2 rows) movies_development=# select * from auditoriums; id | theatre_id | room | seats_available ----+------------+------+----------------- 1 | 1 | A | 150 2 | 2 | B | 150 (2 rows) movies_development=# select id, movie_id, theatre_id, auditorium_id * from movie_showtimes; id | movie_id | theatre_id | auditorium_id ----+----------+------------+--------------- 1 | 1 | 1 | 2 (1 row) Jak widaè, rekord tabeli movie_showtimes odwoäuje siö do sali B (auditorium_id=2) w kinie Steller Theatre (theatre_id=1). Z punktu widzenia poprawnoĈci odwoäaþ wszystko jest w naj- lepszym porzñdku, sök jednak w tym, Ĕe w kinie Steller Theatre nie ma sali B! Zgodnie z za- wartoĈciñ tabeli sala B jest czöĈciñ Old Towne Theatre: t = Theatre.find_by_name( Steller Theatre ) puts t.movie_showtimes.first.auditorium.theatre.name = Old Towne Theatre Ponownie, tej nieadekwatnej do stanu faktycznego sytuacji nie jest w stanie zapobiec kontro- la integralnoĈci danych na poziomie schematu bazy. Zaprezentowana anomalia nie wynika bowiem z jakiegoĈ bäödu w systemie korelacji tabel, lecz z niefortunnego wyboru klucza natu- ralnego. Innymi säowy, mimo iĔ formalnie zachowana zostaäa integralnoĈè referencyjna „su- rowych” danych, o integralnoĈci dziedziny problemowej nie ma mowy. Wszystko dlatego, Ĕe klucz id tabeli auditorium nie zawiera informacji wystarczajñcych dla zachowania adekwat- noĈci ze stanem rzeczywistym — niezbödne zatem staje siö uĔycie klucza naturalnego. 110 _ Rozdziaĥ 8. Klucze zĥoŜone i postaë normalna DKNF Wybór kluczy naturalnych Klucz zäoĔony to — mówiñc najproĈciej — klucz utworzony z kilku kolumn. OkreĈlenie, któ- re kolumny kwalifikujñ siö jako kluczowe dla tabeli, juĔ takie proste nie jest. Gdy zastanowimy siö nad warunkami, jaki musi speäniaè klucz gäówny, natychmiast oczywi- sty staje siö jeden: klucz ten musi jednoznacznie identyfikowaè rekordy tabeli, czyli musi byè inny dla kaĔdego rekordu. Zasada ta dziaäa równieĔ w drugñ stronö: jeĔeli w modelu danych istnieje zestaw kolumn, na który (zgodnie z dziedzinñ problemowñ) narzucono ograniczenie unikalnoĈci, zestaw taki kwalifikuje siö do roli klucza gäównego. JeĈli ponadto unikalnoĈè ta wynika wprost z natury samych danych, klucz ten nazywamy kluczem naturalnym. Gdy przyjrzymy siö schematowi tabeli auditoriums, szybko spostrzeĔemy, Ĕe taki unikalny zestaw tworzñ dwie kolumny: theatre_id i room. Istotnie, nie ma wiökszego sensu istnieje kilku identycznie nazwanych sal projekcyjnych w tym samym kinie. Ów zestaw stanowi jed- noczeĈnie znakomitñ podstawö odwoäywania siö do danej sali projekcyjnej w innych tabelach: odwoäanie „sala o nazwie A w kinie identyfikowanym przez id=1” brzmi nieco bardziej ko- munikatywnie niĔ „sala identyfikowana przez id=47” . To pierwsze, jako zawierajñce bardziej naturalne okreĈlenie sali projekcyjnej, lepiej nadaje siö do kontrolowania integralnoĈci danych od tego drugiego, identyfikujñcego salö projekcyjnñ wyäñcznie w sposób wewnötrzny, wyni- kajñcy z sekwencji zwiñzanej z tabelñ. Na rysunku 8.3 widzimy zatem kolejne przeobraĔenie naszego schematu — z tabeli audito- riums usuniöta zostaäa kolumna id, jako juĔ niepotrzebna; speänianñ przez niñ dotñd rolö klucza gäównego przejöäa para kolumn (theatre_id, room). W konsekwencji z tabeli movie_showtimes zniknñè musi kolumna auditorium_id — peänionñ dotñd przez niñ rolö klucza obcego przej- muje teraz para (theatre_id, room). OczywiĈcie, zmiana ta musi znaleĒè swe odzwierciedlenie w definicji schematu — zaleĔnoĈè miödzy tabelami theatres, auditoriums i movie_showtimes przedstawia siö teraz nastöpujñco: movie_showtimes(theatre_id) references theatres(id) movie_showtimes(theatre_id, room) references auditoriums(theatre_id, room) auditoriums(theatre_id) references theatres(theatre_id) Rysunek 8.3. Klucz id tabeli auditoriums zastñpiony przez klucz naturalny Klucze naturalne — korzyļci i kĥopoty _ 111 Uaktualniona definicja schematu tabel auditoriums i movie_showtimes widoczna jest na li- stingu 8.1. Listing 8.1. Efekt zastñpienia klucza id kluczem naturalnym w tabeli auditoriums create table auditoriums ( room varchar(64) not null check (length(room) = 1), theatre_id integer not null references theatres(id), seats_available integer not null, primary key (room, theatre_id) ); create sequence movie_showtimes_id_seq; create table movie_showtimes ( id integer not null default nextval( movie_showtimes_id_seq ) movie_id integer not null references movies(id), theatre_id integer not null references theatres(id), room varchar(64) not null, primary key (id), foreign key (theatre_id, room) references auditoriums(theatre_id, room) initially deferred ); W tych warunkach wystñpienie opisanej wczeĈniej anomalii jest niemoĔliwe, bo po pierwsze, zawartoĈè pól theatre_id musi byè identyczna w rekordach obu tabel — movie_showtimes i auditoriums, wykluczone jest wiöc wskazanie dwóch róĔnych kin; po drugie, jako Ĕe ko- lumny theatre_id i room wystöpujñ teraz äñcznie jako identyfikacja sali projekcyjnej, nie jest moĔliwe odwoäanie siö do sali nieistniejñcej danym kinie. Siedzéc juŜ na ramionach giganta… W rozdziale 4. przedstawialiĈmy juĔ technologie bazodanowe jako swoistego giganta, stano- wiñcego solidny fundament dla tworzonego kodu aplikacji — aplikacji sadowiñcej siö na ra- mionach owego giganta. „Giganta”, bo dzisiejszy stan wiedzy w tej dziedzinie stanowi dzie- dzictwo kilkudziesiöciu lat dociekaþ teoretycznych i badaþ eksperymentalnych. Problematyka normalizacji danych oraz odpowiedniego wyboru kluczy naturalnych przewija siö przez sze- reg publikacji od ponad 25 lat, jest wiöc problematykñ doskonale rozpoznanñ i jako taka sta- nowi doskonaäy punkt odniesienia dla poczynaþ programistycznych. W roku 1981 Ronald Fagin z IBM Research Laboratories sformuäowaä ideö postaci normalnej klucza domenowego (DKNF — Domain Key Normal Form); w swej publikacji zatytuäowanej A Normal Form for Relational Databases That Is Based on Domains and Keys udowodniä w spo- sób formalny, Ĕe moĔna caäkowicie wyeliminowaè rozmaite anomalie w danych (np. takie, jak opisana wczeĈniej w tym rozdziale), obsadzajñc w charakterze klucza tabeli taki najmniej- szy zestaw kolumn, którego unikalnoĈè dla poszczególnych rekordów zagwarantowana jest przez naturö danych. Ów „zestaw” moĔe jednak mieè niekiedy postaè pojedynczej kolumny (i czösto faktycznie ma), moĔe teĔ zawieraè kilka kolumn, jedno wszak jest pewne: nie istnieje uniwersalny przepis na wybór „dobrego” klucza naturalnego. Wybór ten musi byè wynikiem dogäöbnej analizy danych przechowywanych w tabeli, a takĔe analizy sposobu, w jaki tabela ta wpisuje siö w ogólny schemat bazy. 112 _ Rozdziaĥ 8. Klucze zĥoŜone i postaë normalna DKNF Najlepsze spoĈród dostöpnych dziĈ systemy zarzñdzania bazami danych stworzone zostaäy na bazie wieloletniego dorobku badawczego; nawet koncepcje wydajñce siö dziĈ nowatorski- mi — jak wybór wielokolumnowego klucza naturalnego — datujñ siö na wiele lat wstecz, czego przykäadem cytowana publikacja Fagina. I choè nie wydaje siö to niczym niezwykäym, nieco zaskakujñcy jawi siö zupeäny brak wsparcia ze strony Rails dla wielu kluczowych kon- cepcji w tej dziedzinie. W konsekwencji wielu programistów, dla których Rails jest podstawowym (lub jedynym) Ĉro- dowiskiem pracy, skäonnych jest uwaĔaè te koncepcje za niezbyt istotne, bo skoro brak ich ob- säugi w tak popularnym Ĉrodowisku, to widocznie nie sñ specjalnie potrzebne. Co wiöcej, jeĈli Rails stanowi dla nich pierwszñ okazjö kontaktu z bazami danych w ogóle, byè moĔe nie wy- obraĔajñ sobie innych kluczy gäównych niĔ klucze id — szczególnie kluczy domenowych. Ma- my nadziejö, Ĕe przeczytanie tego rozdziaäu pomoĔe uchroniè czytelników przed tñ niewiedzñ. Migracja do postaci normalnej DKNF Normalizowanie schematu do postaci DKNF moĔe byè uciñĔliwym zadaniem. Z naszym sche- matem bödzie trochö äatwiej, bo sprowadziliĈmy go juĔ do trzeciej postaci normalnej. Prze- analizujmy zatem naturö danych w poszczególnych tabelach i charakter powiñzaþ miödzy tymi tabelami. W tabeli auditoriums zastñpiliĈmy juĔ klucz id kluczem naturalnym. Kolejny krok to zdecy- dowanie, które z tabel kwalifikujñ siö do posiadania jednokolumnowych kluczy naturalnych; bödñ wĈród nich takie, w których uzasadnione bödzie pozostawienie standardowego klucza id, oraz takie, w których klucz ten tworzyè bödzie kolumna zawierajñca „rzeczywiste” dane. PóĒniej zajmiemy siö kluczami zäoĔonymi (wielokolumnowymi) i ich implementacjñ w Rails za pomocñ dedykowanej wtyczki. Na przykäadzie tabeli movie_showtimes pokaĔemy takĔe, jakie nowe problemy mogñ pojawiè siö w zwiñzku z uĔywaniem kluczy naturalnych i jak moĔ- na je zäagodziè, tworzñc swoiste rozwiñzanie hybrydowe, sprowadzajñce siö do wspóäegzy- stencji tych kluczy ze standardowymi dla Rails kluczami id. Klucze jednokolumnowe Jednokolumnowe klucze gäówne zdefiniowane zostaäy dla tabel movies, payment_types, orders, purchased_tickets, zip_codes i ratings. Klucze jednokolumnowe posiada wiöc wiökszoĈè tabel i jest to sytuacja typowa dla wiökszoĈci schematów — byè moĔe okolicznoĈè ta täumaczy fakt, Ĕe w Rails jedynie takim kluczom zapewniono standardowñ obsäugö. ēeby zdecydowaè, czy dla danej tabeli wystarczajñcy jest jednokolumnowy klucz bödñcy w istocie arbitralnie wybieranñ liczbñ caäkowitñ, naleĔy spróbowaè znaleĒè w schemacie tej tabeli kolumnö, której charakter decyduje o jej unikalnoĈci, zaĈ unikalnoĈè z kolei kwalifikuje kolumnö do roli klucza naturalnego. W tabelach widocznych na rysunku 8.4 wyróĔniono ta- kie kolumny kursywñ (pogrubionñ czcionkñ oznaczone sñ kolumny id jako bazowe dla istnie- jñcych kluczy naturalnych). Ostatecznie wiöc okazuje siö, Ĕe tabele zip_codes, ratings i orders posiadajñ takie „unikal- ne” kolumny, odpowiednio, zip, rating_name i confirmation_code. Oznacza to, Ĕe rekordy tych tabel mogñ byè jednoznacznie identyfikowane na dwa róĔne sposoby, choè — prawdö mówiñc — nie jest to fakt zbyt istotny; waĔne jest natomiast to, iĔ kaĔda z owych unikalnych Siedzéc juŜ na ramionach giganta… _ 113 Rysunek 8.4. Tabele posiadajñce standardowe klucze id kolumn (posiadajñca intuicyjnñ nazwö) moĔe po prostu zastñpiè odnoĈnñ kolumnö id. Z per- spektywy Rails typ danych przechowywanych w kolumnie klucza naturalnego jest w zasa- dzie obojötny, jeĔeli jednak nie jest to arbitralnie generowana liczba caäkowita, sami musimy zadbaè o generowanie unikalnych wartoĈci dla nowo tworzonych rekordów. JeĔeli ponadto nazwa kolumny tworzñcej klucz naturalny jest inna niĔ id, musimy nazwö tö jawnie wskazaè w klasie modelowej jako parametr wywoäania metody set_primary_key. Jak juĔ wspominaliĈmy w rozdziale 7., w tabeli zip_codes takñ kolumnñ jest kolumna zip. W tabeli ratings funkcjö klucza naturalnego mogäaby peäniè kolumna rating_name. Obie te tabele sñ o tyle äatwiejsze w obsäudze — pod wzglödem niestandardowych kluczy gäównych — Ĕe sñ tabelami dziedzinowymi: ich zawartoĈè prawdopodobnie nie bödzie siö zmieniaè, a jeĔeli nawet, to na pewno bardzo rzadko. Dotyczy to szczególnie tabeli ratings, dla której w klasie modelowej zdefiniowano zestaw staäych reprezentujñcych poszczególne wartoĈci potencjalnego klucza. Dla obu tabel nie definiowaliĈmy Ĕadnego interfejsu umoĔliwiajñcego operowanie danymi, wiöc wszelkie modyfikacje i dodawanie nowych rekordów odbywaè siö bödñ poza warstwñ aplikacyjnñ, ergo — nie istnieje problem generowania ad hoc unikalnych wartoĈci dla klucza w nowych rekordach. Ostatecznie eliminujemy z tabeli ratings pole id, obsadzajñc w roli klucza gäównego kolumnö rating_name, tak jak na rysunku 8.5. Rysunek 8.5. Tabele dziedzinowe z niestandardowymi kluczami gäównymi W tabeli orders zawartoĈè pola confirmation_code, bödñcñ w istocie kodem autoryzacyjnym transakcji, równieĔ moĔna przyjñè jako niezmiennñ. JeĈli obsadzimy jñ w roli klucza gäównego, zamiast kolumny id, przejmiemy na siebie obowiñzek generowania jej unikalnej zawartoĈci — która i tak jest generowana dla kaĔdej transakcji, problem wiöc rozwiñzuje siö sam. Najbardziej odpowiednim miejscem dla dokonywania tego generowania jest metoda before_create kla- sy modelowej Order; nowa wartoĈè bödzie po prostu odwzorowaniem mieszajñcym (hash) 114 _ Rozdziaĥ 8. Klucze zĥoŜone i postaë normalna DKNF kolejnej wartoĈci sekwencyjnej, jaka zostaäaby przypisana polu id w nowym rekordzie1. Przy okazji zwracamy uwagö na kolejny, niezwykle istotny fakt: mimo Ĕe kolumna tworzñca klucz gäówny nie nosi juĔ nazwy id, na poziomie klasy modelowej nadal jest ona reprezentowana przez wäaĈciwoĈè id, stñd wyraĔenie self.id w poniĔszym fragmencie, stanowiñce w istocie odwo- äanie do kolumny confirmation_code, nie jest pomyäkñ. class Order ActiveRecord::Base set_primary_key :confirmation_code has_many :purchased_tickets, :foreign_key = order_confirmation_code def before_create next_ordinal_id = Order.connection.select_value( select nextval( orders_id_seq ) ) self.id = next_ordinal_id.crypt( CONF_CODE ) end end ãatwo siö przekonaè, Ĕe dodawanie nowych rekordów do tabeli orders odbywa siö caäkowicie poprawnie — w polu confirmation_code zapisywany jest maäo czytelny, lecz unikalny kod: o = Order.create({:movie_showtime_id = 1, :purchaser_name = JaŁ Fasola }) = # Order:0x2553af0 o.id = CotW6pp1X6z7o Tworzenie rekordów zaleĔnych takĔe nie stanowi problemu: o.purchased_tickets PurchasedTicket.new(:purchase_price_cents = 650) = # PurchasedTicket:0x25166c8 o.confirmation_code = CotW6pp1X6z7o Zdefiniowanie klucza gäównego jako klucza naturalnego, zamiast standardowego klucza id, daje wiele dodatkowych korzyĈci, z których jednñ wyjaĈnimy na przykäadzie konkretnego powiñzania. Na rysunku 8.6 widzimy tabele orders i purchased-tickets, powiñzane na dwa róĔne sposoby: za pomocñ klucza id tabeli orders (w lewej czöĈci) oraz przy uĔyciu klucza naturalnego tej tabeli (z prawej). W tym drugim przypadku klucz gäówny tej tabeli jest nie tylko unikalnñ, beznamiötnñ wartoĈciñ zapewniajñcñ jednoznaczne identyfikowanie rekor- dów, lecz równieĔ niesie ze sobñ konkretnñ informacjö, jakñ jest kod autoryzacyjny transakcji. PoniewaĔ klucz gäówny tabeli orders ma swój odpowiednik w postaci klucza obcego, jakim jest pole order_confirmation_code tabeli purchased_tickets, wiöc owa konkretna infor- macja „przemycona” zostaäa mimowolnie do tejĔe tabeli. W efekcie dla konkretnego rekordu reprezentujñcego sprzedany bilet informacja o kodzie autoryzacyjnym transakcji sprzedaĔy obecna jest wprost w tymĔe rekordzie; w ukäadzie z lewej strony rysunku informacja ta do- stöpna jest tylko poĈrednio, poprzez klucz obcy order_id odsyäajñcy do odpowiedniego re- kordu w tabeli orders. Dla tabel theatres i movies nie istniejñ Ĕadne przesäanki do definiowania kluczy naturalnych, dla nich pozostawiamy zatem standardowe klucze gäówne id. 1 Zwracamy uwagö, Ĕe klauzula nextval, wyznaczajñca kolejnñ wartoĈè wynikajñcñ ze zdefiniowanej sekwen- cji, jest klauzulñ specyficznñ dla PostgreSQL. Na poziomie klasy modelowej generowanie kolejnych wartoĈci sekwencyjnych wykonywane jest przez metodö next_sequence_value, dziaäajñcñ niezaleĔnie od konkretnego systemu bazy danych; niestety, poprawne dziaäanie tej metody w kontekĈcie PostgreSQL wymaga zainstalo- wania poprawki do Rails, dostöpnej na stronie http://dev.rubyonrails.org/ticket/9178. W kontekĈcie Oracle funk- cja ta natomiast spisuje siö bezproblemowo. Siedzéc juŜ na ramionach giganta… _ 115 Rysunek 8.6. Dwa róĔne powiñzania tabel orders i purchased-tickets, na podstawie klucza id oraz na podstawie klucza naturalnego Klucze wielokolumnowe i ich implementacja w Rails Mimo iĔ Rails nie zapewnia standardowo obsäugi gäównych kluczy wielokolumnowych, ist- niejñ dwa sposoby osiñgniöcia korzyĈci wynikajñcych z uĔywania takich kluczy. Pierwszy z nich sprowadza siö do wykorzystania pewnej dedykowanej wtyczki, drugi zasadza siö na wspóäistnieniu gäównych kluczy wielokolumnowych ze standardowymi kluczami id. Obsĥuga kluczy zĥoŜonych za pomocé dedykowanej wtyczki Tytuäowa wtyczka dostöpna jest na stronie http://compositekeys.rubyforge.org, wraz z niezbödnñ dokumentacjñ. Zainstalowanie wtyczki jako gemu odbywa siö w zwykäy sposób: ruby gem install composite_primary_keys NaleĔy jeszcze doäñczyè na koþcu pliku config/environment.rb instrukcjö: require composite_primary_keys Na gruncie klasy modelowej metoda definiujñca zäoĔony klucz gäówny nosi nazwö set_pri- mary_keys — co jest liczbñ mnogñ set_primary_key: class Auditorium ActiveRecord::Base # musimy jawnie zdefiniowað nazwĂ tabeli, bo reguĪy infleksji Rails # zawodzî w tym przypadku set table_name auditoriums set_primary_keys :room :theatre_id belongs_to :theatre has_many :movie_showtimes, :dependent = :destroy end W powiñzanej klasie modelowej wielokolumnowy klucz obcy reprezentowany jest w postaci tablicy kolumn, przekazywanej jako wartoĈè parametru :foreign_key: 116 _ Rozdziaĥ 8. Klucze zĥoŜone i postaë normalna DKNF class MovieShowtime ActiveRecord::Base belongs_to :movie belongs_to :theatre belongs_to :auditorium, :foreign_key = [:room, :theatre_id] end Klasy modelowe korzystajñce z wielokolumnowych kluczy gäównych nie wymagajñ Ĕadnego specjalnego traktowania. Tak jak w poniĔszym przykäadzie, tworzenie obiektu klasy Audito- rium odbywa siö w zwykäy sposób, podobnie piszñc obiekt klasy MovieShowtime, nie musi- my jawnie specyfikowaè poszczególnych elementów klucza obcego, wystarczy powoäanie siö na powiñzany obiekt, resztö zaäatwiñ mechanizmy zainstalowanej wtyczki. m = Movie.create!( :name = Casablanca , :length_minutes = 120, :rating = Rating::PG13) t = Theatre.create!( :name = Kendall Cinema :phone_number = 5555555555 ) a = Auditorium.create!( :theatre = t, :room = 1 :seats_available = 100) ms = MovieShowtime.create!( :movie - m, :theatre = t, :auditorium = a, :start_time = Time.new) Model hybrydowy „id-DKNF” Zajmijmy siö teraz tabelñ movie_showtimes. Po przeanalizowaniu przeznaczenia tabeli (kaĔ- dy rekord reprezentuje jednñ projekcjö filmu) i znaczenia poszczególnych kolumn dochodzi- my do wniosku, Ĕe kolumny (movie_id, theatre_id, room i start_time) tworzñ minimalny2 zestaw unikalnoĈci, który tym samym kwalifikuje siö do roli klucza gäównego. Teoretycznie, unikalnoĈè tego zestawu nie wyczerpuje moĔliwoĈci ograniczeþ narzuconych na zawartoĈè tabeli, nie wynika z niej bowiem oczywisty fakt, Ĕe w konkretnej sali projekcyjnej wyĈwietla- nie kolejnego filmu moĔe zaczñè siö dopiero po zakoþczeniu emisji poprzedniego; tego rodzaju ograniczeniami zajmiemy siö dopiero w nastöpnym rozdziale. UnikalnoĈè pewnego zestawu kolumn stanowi niewñtpliwie warunek konieczny, by moĔna byäo obsadziè ów zestaw w roli klucza gäównego, nie zawsze jednak jest to warunek wystar- czajñcy; jak za chwilö zobaczymy, korzystanie z kluczy naturalnych moĔe byè przyczynñ po- waĔnych problemów, niwelujñcych ewentualne korzyĈci i sprawiajñcych, Ĕe przysäowiowa skórka staje siö niewarta wyprawki. JeĔeli przyjmiemy wspomniany zestaw kolumn jako klucz gäówny tabeli movie_showtimes, w powiñzanej z niñ tabeli orders w polu start_time, wchodzñcym w skäad klucza obcego, pojawi siö informacja o czasie rozpoczöcia projekcji. Zdarza siö, Ĕe (z róĔnych przyczyn) czas ten ulega zmianie; klienci, którzy zakupili juĔ bilety na konkretnñ godzinö, z reguäy akceptu- jñ takñ zmianö, a ci, którym ona nie odpowiada, mogñ bilet zwróciè. 2 „Minimalny”, bo usuniöcie którejkolwiek kolumny z zestawu pozbawi go cechy unikalnoĈci — przyp. täum. Siedzéc juŜ na ramionach giganta… _ 117 Z perspektywy integralnoĈci referencyjnej danych zgromadzonych w bazie sprawa jednak nie wyglñda tak prosto, bowiem nie naleĔy modyfikowaè klucza rekordu, dla którego w in- nych tabelach istniejñ rekordy zaleĔne. Dla rekordu z tabeli movie_showtimes mogñ istnieè rekordy w tabeli orders, te zaĈ mogñ odwoäywaè siö do swych rekordów zaleĔnych w tabeli purchased_tickets. Niezbyt spektakularne wydarzenie, jakim jest zmiana rozpoczöcia emi- sji filmu, w przeäoĔeniu na konkretne operacje bazodanowe musiaäoby obejmowaè kolejno: 1. Usuniöcie zaleĔnych rekordów z tabeli purchased_tickets. 2. Usuniöcie zaleĔnych rekordów z tabeli orders. 3. Usuniöcie rekordu z tabeli movie_showtimes. 4. Utworzenie nowego rekordu w tabeli movie_showtimes, z nowñ wartoĈciñ w polu start_ time. 5. Odtworzenie rekordów usuniötych w punkcie 2., z nowñ wartoĈciñ w polu start_time. 6. Odtworzenie rekordów usuniötych w punkcie 1. To jeszcze nie wszystko. OtóĔ, ActiveRecord nie daje standardowo Ĕadnej moĔliwoĈci mody- fikowania klucza gäównego rekordu, modyfikacjö takñ moĔna jednak przeprowadziè za pomo- cñ bezpoĈredniego odwoäania do jözyka SQL. Istniejñ jednak programiĈci „wysokopoziomowi”, których sam skrót „SQL” przyprawia o palpitacjö serca, wiöc z myĈlñ o nich zaproponujemy teraz rozwiñzanie kompromisowe. Nie byäoby caäego zamieszania, gdyby kluczem gäównym tabeli movie_showtimes byä stan- dardowy klucz id. Pozostawimy go zatem w roli klucza gäównego, jednoczeĈnie zrzucajñc na barki warstwy aplikacyjnej (czyli klas modelowych) zadanie utrzymywania spójnoĈci miö- dzy tabelami movie_showtimes i orders na poziomie klucza naturalnego tej ostatniej, czyli (mówiñc po prostu) nadawania odpowiednich wartoĈci polom movie_id, theatre_id, room i start_time nowo tworzonych rekordów tabeli orders; sytuacjö tö przedstawiono na rysun- ku 8.7. Z punktu widzenia integralnoĈci referencyjnej, zmiana wartoĈci pola start_time w re- kordzie tabeli movie_showtimes nie stanowi w tym stanie rzeczy ingerencji w klucz gäówny i moĔe byè wykonana na poziomie Rails w zwykäy sposób; oczywiĈcie, na poziomie klasy modelowej naleĔy jednak zadbaè o stosownñ modyfikacjö pola start_time w powiñzanym rekordzie tabeli orders. Tak oto udaäo nam siö pogodziè dwie (pozorne, jak widaè) sprzecz- noĈci: zapewnienie korzyĈci wynikajñcych z uĔywania kluczy naturalnych oraz zachowanie moĔliwoĈci nieskröpowanego modyfikowania tych pól rekordu, które zawierajñ „rzeczywi- stñ” informacjö. Rysunek 8.7. Wspóäistnienie dwóch powiñzaþ miödzy tabelami movie_showtimes i orders, poprzez klucz id oraz klucz naturalny 118 _ Rozdziaĥ 8. Klucze zĥoŜone i postaë normalna DKNF Zwracamy uwagö na jeszcze jeden istotny fakt: przy definiowaniu skojarzeþ miödzy tabelami na poziomie schematu bazy danych, w instrukcji foreign key zestaw kolumn kluczowych tabeli docelowej (w parametrze references) musi byè w definicji tej tabeli objöty klauzulñ unikalnoĈci. Brak tej klauzuli moĔe stwarzaè ryzyko niejednoznacznego wiñzania — wspo- mniany zestaw identyfikowaè moĔe nie jeden, lecz kilka rekordów. Ten ewidentny bäñd lo- giczny jest jednak tolerowany przez MySQL, próba jego popeänienia w PostgreSQL powodu- je natomiast sygnalizacjö bäödu: movies_development=# alter table orders add constraint movie_showtimes_movie_theatre_room_start_time_fkey foreign key (movie_id, theatre_id, room, start_time) references movie_showtimes(movie_id, theatre_id, room, start_time) ERROR: there is no unique constraint matching given keys for referenced table movie_showtimes Ponadto zdefiniowanie pewnego zestawu kolumn jako unikalnego spowoduje, Ĕe PostgreSQL automatycznie zaäoĔy indeks na bazie tego zestawu, dziöki czemu poszukiwanie rekordu o da- nym kluczu naturalnym odbywaè siö bödzie niemal bäyskawicznie. Uzupeänijmy zatem definicjö tabel movie_showtimes i orders o niezbödne elementy — wspo- mnianñ klauzulö unikalnoĈci i definicjö klucza obcego: create sequence movie_showtimes_id_seq; create table movies_showtimes ( id integer not null default nextval( movie_showtimes_id_seq ), movie_id integer not null references movies(id), theatre_id integer not null references theatres(id), room varchar(64) not null, start_time timestamp with time zone not null, primary key (id), unique (movie_id, theatre_id, room, start_time), foreign key (theatre_id, room) references auditoriums(theatre_id, room) intially deferred ); create sequence orders_id_seq; create table orders ( confirmation_code varchar(16) not null check(length(confirmation_code) 0), movie_showtime_id integer not null references movie_showtimes(id), movie_id integer not null, room varchar(64) not null, start_time timestamp with time zone, purchaser_name varchar(128) not null check (length(purchaser_name) 0), primary key (confirmation_code), foreign key (movie_id, theatre_id, room, start_time) references movie_showtimes(movie_id, theatre_id, room, start_time) ) inherits (addresses); Uproszczenie dziýki nowej metodzie create! Jednñ z uciñĔliwoĈci opisanego modelu hybrydowego jest koniecznoĈè jawnego przypisywa- nia wartoĈci kolumnom wchodzñcym w skäad klucza naturalnego w nowo tworzonych rekor- dach zaleĔnych. Pozornie poprawny kod: o = Order.create!( :movie_showtime = ms, :purchaser_name = JaŁ Fasola ) Siedzéc juŜ na ramionach giganta… _ 119 nie bödzie funkcjonowaè jak naleĔy, bo przypisanie: :movie_showtime = ms spowoduje zainicjowanie jedynie pola movie_showtime_id, poniewaĔ nie jest aktywna wtycz- ka composite_primary_keys i Rails honoruje wyäñcznie klucze id w roli kluczy gäównych. Pozostaäe kolumny wchodzñce w skäad klucza naturalnego naleĔy zainicjowaè explicite: o = Order.create!( :movie_showtime = ms, :movie = ms.movie, :auditorium = ms.auditorium, :start_time = ms.start_time, :purchaser_name = JaŁ Fasola ) ZauwaĔmy, Ĕe nie jest konieczne przypisywanie wartoĈci polu theatre_id, zostanie ono bo- wiem zainicjowane w ramach przypisania :auditorium = ms.auditorium, ze wzglödu na postaè klucza gäównego tabeli auditoriums. Na szczöĈcie, moĔna sobie nieco uäatwiè programistyczny Ĕywot, za pomocñ drobnego zabie- gu sprawiajñcego, Ĕe Rails wykona automatycznie rzeczone przypisania w ramach instrukcji :movie_showtime = ms, analogicznie jak w przypadku uĔywania wtyczki composite_pri- mary_keys. Jak siö czytelnicy zapewne domyĈlajñ, naleĔy w tym celu przedefiniowaè odpo- wiednio metodö movie_showtime=. PoniewaĔ jednak jej nowa wersja odwoäywaè siö bödzie do wersji dotychczasowej, naleĔy tö ostatniñ najpierw przemianowaè, definiujñc jej alias: class Order ActiveRecord::Base alias :old_movie_showtime= :movie_showtime= def movie_showtime=(ms) self.movie_id = ms.movie_id self.theatre_id = ms.theatre_id self.room = ms.room self.start_time = ms.start_time self.old_movie_showtime=(ms) end end Odroczona kontrola integralnoļci referencyjnej Ingerowanie w zawartoĈè kolumny wchodzñcej w skäad klucza gäównego moĔe powodowaè zerwanie relacji miödzy powiñzanymi rekordami, naruszenie integralnoĈci referencyjnej i w kon- sekwencji zgäoszenie wyjñtku przez system bazy danych. MoĔe siö tak staè np. wskutek zmia- ny godziny rozpoczöcia emisji filmu, na którñ to emisjö sprzedane zostaäy juĔ bilety: def setup @m = Movie.create!( :name = Casablanca , :length_minutes = 120, :rating = Rating::PG13) @t = Theatre.create!( :name = Kendall Cinema , :phone_number = 5555555555 ) @a = Auditorium.create!( :theatre = @t, :auditorium = @a, :seats_available = 100) @ms = MovieShowtime.create!( :movie = @m, :theatre = @t, :auditorium = @a, :start_time = Time.new) 120 _ Rozdziaĥ 8. Klucze zĥoŜone i postaë normalna DKNF @o = Order.create!( :movie_showtime = @ms, :movie = @m :theatre = @t, :auditorium = @a, :start_time = @ms.start_time, :purchaser_name = JaŁ Fasola ) end def test_deferrable_constraints MovieShowtime.transaction do @ms.start_time = @ms.start_time + 1.hour @ms.save! Order.update_all([ start_time = ? , @ms.start_time], [ movie_showtime_id = ? , @ms.id ]) end end OczywiĈcie, powyĔszy test zaäamie siö, poniewaĔ wyróĔniona instrukcja spowoduje narusze- nie integralnoĈci referencyjnej: ChakBookPro:chapter-7-dknf chak$ ruby test/unit/movie_showtime_test_case.rb Loaded suite test/unit/movie_showtime_test_case Started ... Finished in 0.657148 second. 1) Error: test_deferrable_constraints(MovieShowtimeTestCase): ActiveRecord::StatementInvalid: PGError: ERROR: update or delete on table movie_showtimes violates foreign key constraint orders_ovie_id_fkey on table orders DETAIL: Key (movie_id,theatre_id,room,start_time)= (20,20,1,2007-12-16 00:53:49.076398) is still referenced from table orders . : UPDATE movie_showtimes SET start_time = 2007-12-16 01:53:49.076398 , theatre_id = 20, movie_id = 20, room = 1 WHERE id = 20 1 tests, 0 assertions, 0 failures, 1 errors Wynika stñd, Ĕe aby naruszenie klucza naturalnego byäo w ogóle moĔliwe, baza danych musi staè siö nieco bardziej wyrozumiaäa pod wzglödem kontroli integralnoĈci referencyjnej i po- zwoliè na odstöpstwo od zasad tejĔe integralnoĈci choè na chwilö. Istotnie, moĔliwe jest uzy- skanie takiej „wyrozumiaäoĈci” — „na chwilö” oznacza w tym przypadku „do momentu za- twierdzenia transakcji”, wewnñtrz transakcji kontrola integralnoĈci referencyjnej, wynikajñcej z okreĈlonego klucza, jest zawieszona. W celu uzyskania tego stanu rzeczy naleĔy w instruk- cji foreign key (w schemacie tabeli) umieĈciè klauzulö initially deferred: create table orders ( confirmation_code varchar(16) not null check(length(confirmation_code) 0), movie_showtime_id integer not null references movie_showtimes(id), movie_id integer not null, room varchar(64) not null, start_time timestamp with time zone, purchaser_name varchar(128) not null check (length(purchaser_name) 0), primary key (confirmation_code), foreign key (movie_id, theatre_id, room, start_time) references movie_showtimes(movie_id, theatre_id, room, start_time) initially deferred ) inherits (addresses); Po tej drobnej, acz istotnej modyfikacji test zostanie zaliczony: Siedzéc juŜ na ramionach giganta… _ 121 ChakBookPro:chapter-7-dknf chak$ ruby test/unit/movie_showtime_test_case.rb Loaded suite test/unit/movie_showtime_test_case Started ... Finished in 0.657148 second. 1 tests, 0 assertions, 0 failures, 0 errors Odroczenie kontroli integralnoĈci referencyjnej umoĔliwia wiöc chwilowe naruszenie reguä, ko- nieczne do wykonania pewnych operacji; naruszenie to odbywa siö w ramach trwajñcej trans- akcji, nie moĔe zatem powodowaè trwaäych skutków w istniejñcych danych. Przed zatwierdze- niem transakcji konieczne jest przywrócenie absolutnej zgodnoĈci ze wspomnianymi reguäami. Pewna trudnoĈè w testowaniu opisanego odroczenia wiñĔe siö z faktem, Ĕe (ewentualne) zwiñ- zane z nim bäödy ujawniajñ siö dopiero w momencie zatwierdzania (commit) transakcji. I tu mamy problem, bowiem kaĔdy przypadek testowy weryfikowany jest w ramach transakcji, która ostatecznie zostaje anulowana (rollback) — wszystko po to, by przypadek testowy nie powodowaä zmian w danych bödñcych przedmiotem zainteresowania kolejnego przypadku testowego. W rezultacie, dla odroczonej kontroli integralnoĈci referencyjnej nie da siö skonstru- owaè testów negatywnych. Coļ za coļ… OpisaliĈmy trzy róĔne sposoby zapewnienia integralnoĈci referencyjnej. Podstawñ pierwszego z nich sñ standardowe dla Rails klucze id. Kolumna id peäni wyäñcznie rolö kluczowñ i nie reprezentuje Ĕadnych treĈci merytorycznych, wskutek czego relacje miödzy rekordami po- wiñzanych tabel, caäkowicie poprawne z punktu widzenia zgodnoĈci kluczy, niekoniecznie sñ poprawne z perspektywy rozwiñzywanego problemu, ergo — klucze id nie zawsze sñ wy- starczajñce do zapewnienia integralnoĈci referencyjnej, co stanowi przesäankö do definiowa- nia i uĔywania kluczy naturalnych. OpisaliĈmy dwa sposoby implementacji kluczy naturalnych na gruncie Rails. Pierwszy z nich opiera siö na zastosowaniu dedykowanej wtyczki o nazwie composite_primary_keys, istotñ drugiego jest jawna obsäuga (zdefiniowanych w schemacie bazy) kluczy naturalnych na po- ziomie klasy modelowej, z zachowaniem standardowego dla Rails wiñzania tabel na podsta- wie kolumn id. Krótkñ charakterystykö wszystkich trzech rodzajów kluczy zamieszczamy w tabeli 8.1. Tabela 8.1. Podstawowe cechy trzech implementacji kluczy gäównych Wyĥécznie klucze naturalne, obsĥugiwane za poļrednictwem wtyczki Wyĥécznie klucze id Model hybrydowy — wspóĥistnienie kluczy id z kluczami naturalnymi Obsĥuga standardowa Zapewnienie integralnoļci referencyjnej na poziomie dziedziny problemowej MoŜliwoļë zmiany klucza gĥównego za poļrednictwem API Rails Efektywne wykorzystywanie indeksów Komplikacja kodu aplikacji Tak Nie Nie Tak Nie Tak Nie Tak Nie 122 _ Rozdziaĥ 8. Klucze zĥoŜone i postaë normalna DKNF Czýļciowo Tak Tak Nie zawsze Tak PoĈwiöcimy nieco uwagi dwóm ostatnim z wymienionych cech: efektywnemu korzystaniu z indeksów oraz komplikacji warstwy aplikacyjnej. Efektywny uŜytek z indeksów Jest oczywiste, Ĕe dla efektywnego dziaäania modelu hybrydowego konieczne jest istnienie dwóch (co najmniej) indeksów, opartych na (odpowiednio) kolumnie id oraz zestawie kolumn tworzñcych klucz naturalny. Oba te indeksy muszñ byè aktualizowane po kaĔdej operacji dodania, usuniöcia lub zmodyfikowania rekordu. Weryfikacja integralnoĈci referencyjnej po wstawieniu lub zmodyfikowaniu rekordu w tabeli powiñzanej wymaga sprawdzenia dwóch indeksów, o ile w ogóle dopuszczamy modyfikowanie klucza naturalnego — alternatywñ dla modyfikacji kolumn wchodzñcych w skäad klucza naturalnego rekordu jest usuniöcie przed- miotowego rekordu i utworzenie nowego ze zmodyfikowanymi wartoĈciami pól, co opisy- waliĈmy na przykäadzie tabeli movie_showtimes. Czy ta alternatywa jest lepszym rozwiñza- niem — zaleĔne jest to od konkretnego problemu. Zmiana klucza naturalnego, w sytuacji gdy istniejñ rekordy zaleĔne od przedmiotowego rekordu, zawsze jest posuniöciem ryzykownym, naleĔy wiöc zastanowiè siö, czy faktycznie znajduje uzasadnienie w realiach problemu, z któ- rym zwiñzane sñ przetwarzane dane. Komplikacja kodu aplikacji Jednñ z najistotniejszych cech jözyka Ruby, i w konsekwencji Rails, jest zwiözäoĈè, czyli moĔ- liwoĈè wyraĔenia treĈciwej informacji w kilku zaledwie wierszach kodu. Wszystko, co odby- wa siö ze szkodñ dla tej zwiözäoĈci, traktowane bywa przez programistów jak przekleþstwo. Jak ma siö to do opisywanych implementacji kluczy naturalnych? Jak widzieliĈmy, korzysta- nie z wtyczki composite_primary_keys wymaga niewielkiego narzutu ze strony kodowania — wszystko sprowadza siö do wywoäania metody set_primary_keys w przedmiotowej kla- sie modelowej i specyfikacji dodatkowego parametru (:foreign_key) w klasach powiñzanych. W modelu hybrydowym nie pojawiajñ siö Ĕadne dodatkowe definicje, za to wobec faktu, Ĕe Rails nie jest w ogóle Ĉwiadom istnienia klucza naturalnego, musimy we wäasnym zakresie implementowaè powiñzania miödzy rekordami na poziomie tego klucza. Wymaga to dodat- kowego kodu, którego rozmiary moĔna jednak zredukowaè, nadpisujñc metodö create! two- rzñcñ obiekt klasy modelowej — co zaprezentowaliĈmy na przykäadzie klasy Order. I choè nie jest to naddatek spektakularny, warto wziñè go pod uwagö, decydujñc siö na zastosowa- nie modelu hybrydowego zamiast „czystych” kluczy naturalnych. Aktualna postaè naszego modelu, po modyfikacjach opisanych w niniejszym rozdziale, wi- doczna jest na rysunku 8.8. êwiczenia 1. Spróbuj doprowadziè do anomalii opisywanej na poczñtku rozdziaäu (odwoäanie do sali projekcyjnej nieistniejñcej w konkretnym kinie) i postaraj siö udowodniè, Ĕe przy odpo- wiednio wybranym kluczu naturalnym jej wystñpienie jest niemoĔliwe. 2. Podaj przykäady zapytaþ, których realizacja staje siö efektywniejsza wskutek powiñzania tabel movie_showtimes i orders za pomocñ klucza naturalnego. êwiczenia _ 123 Rysunek 8.8. Rezultat wprowadzenia kluczy naturalnych do modelu Refaktoryzacja 1. Przeanalizuj kaĔdñ tabelö i zastanów siö, czy istniejñ w niej kolumny niewchodzñce w skäad klucza gäównego, dla których zdefiniowana jest klauzula unikalnoĈci bñdĒ teĔ klauzulö takñ powinno siö zdefiniowaè ze wzglödu na charakter danych reprezentowanych przez te kolumny. 2. JeĔeli jakaĈ kolumna (lub zestaw kolumn) kwalifikuje siö do opatrzenia klauzulñ unikal- noĈci, zrób to: create unique index concurrently nazwa tabeli , nazwa kolumny1 _ nazwa kolumny2 _... nazwa kolumnyN _uniq_idx on nazwa tabeli( nazwa kolumny1 , nazwa kolumny2 , ... , nazwa kolumnyN ); 3. ZaleĔnie od tego, czy w punkcie 2. mowa jest o pojedynczej kolumnie, czy o zestawie ko- lumn, wybierz jeden z poniĔszych scenariuszy. Klucz jednokolumnowy 1. W klasie modelowej reprezentujñcej tabelö wskaĔ kolumnö stanowiñcñ kandydata na no- wy klucz gäówny: set_primary_key : nazwa kolumny kluczowej 124 _ Rozdziaĥ 8. Klucze zĥoŜone i postaë normalna DKNF 2. W kaĔdej z tabel powiñzanych z przedmiotowñ tabelñ dodaj nowñ kolumnñ stanowiñcñ klucz obcy: alter table nazwa tabeli zalešnej add column nazwa tabeli gĪównej _ nazwa kolumny kluczowej typ kolumny ; 3. Wypeänij nowñ kolumnö odpowiednimi wartoĈciami we wszystkich rekordach kaĔdej z tabel zaleĔnych: update nazwa tabeli zalešnej from nazwa tabeli gĪównej r set nazwa tabeli gĪównej w liczbie pojedynczej _ nazwa kolumny kluczowej = r. nazwa kolumny kluczowej where nazwa tabeli gĪównej w liczbie pojedynczej _id = r.id; 4. Uzupeänij schemat kaĔdej z tabel zaleĔnych o definicjö klucza obcego: alter table nazwa tabeli zalešnej add constraint nazwa tabeli gĪównej w liczbie pojedynczej _ nazwa kolumny kluczowej _fkey ( nazwa tabeli gĪównej w liczbie pojedynczej _ nazwa kolumny kluczowej ) references nazwa tabeli gĪównej ( nazwa kolumny kluczowej ); 5. Usuþ ze schematu tabeli przedmiotowej definicjö kolumny id: alter table nazwa tabeli gĪównej drop column id; 6. Ze schematu kaĔdej z tabel zaleĔnych usuþ definicjö klucza obcego powoäujñcñ siö na ko- lumnö, o której mowa w punkcie 5.: alter table nazwa tabeli zalešnej drop column nazwa tabeli gĪównej w liczbie pojedynczej _id; 7. ObsadĒ nowñ kolumnö przedmiotowej tabel w roli jej klucza gäównego: alter table nazwa tabeli gĪównej add primary key( nazwa kolumny kluczowej ); Klucz wielokolumnowy 1. Zainstaluj gem composite_primary_keys: ruby gem install composite_primary_keys 2. Doäñcz gem do aplikacji, dopisujñc w pliku environment.rb wiersz: require composite_primary_keys 3. W definicji klasy modelowej, reprezentujñcej przedmiotowñ tabelö, zmieþ definicjö klucza gäównego: set_primary_keys [: nazwa kolumny1 , : nazwa kolumny2 , ... , : nazwa kolumnyN ] 4. Wykonaj czynnoĈci analogicznie jak w punktach od 2. do 7. scenariusza dla klucza jedno- kolumnowego. Refaktoryzacja _ 125
Pobierz darmowy fragment (pdf)

Gdzie kupić całą publikację:

Rails. Projektowanie systemów klasy enterprise
Autor:

Opinie na temat publikacji:


Inne popularne pozycje z tej kategorii:


Czytaj również:


Prowadzisz stronę lub blog? Wstaw link do fragmentu tej książki i współpracuj z Cyfroteką: