Ruby on Rails a revoluce ve vývoji pro web. Část druhá: Active Record
V prvním článku o Ruby on Rails jsme se zabývali základními filosofickými principy, na nichž je založen jazyk Ruby — jazyk, v němž jsou Rails napsány a v němž jsou psány Rails aplikace. Viděli jsme, že jeho úsporná syntaxe je navržena tak, aby se programátorovi nepletla do cesty, a umožnila mu co nejsnazší a nejelegantnější vyjádření „myšlenky“:
5.times { print "Hurá! " }
=> ‚Hurá! Hurá! Hurá! Hurá! Hurá!‘
V tomto článku bych se chtěl věnovat tomu, co dělá Rails tím, čím jsou. Jestliže nevíte o Rails nic nebo téměř nic, než budete číst dál, podívejte se na patnáctiminutové video v angličtině, kde autor naprogramuje kompletní blog: Creating a weblog in 15 minutes.
… hotovo? Tak můžeme jít dál. Video je samozřejmě působivé a jsou v něm jasně vidět inspirační zdroje v Ruby. Asi je vám jasnější, proč Computerworld zařadil Rails mezi pět nejvýznamnějších technologií pro rok 2007. Napsat funkční aplikaci v čistém kódu a s použitím transparentních abstraktních vrstev za čtvrt hodiny je slušný výkon.
Video také naznačuje odpověď na otázku, proč David Heinemeier Hansson (dále jen DHH) Ruby on Rails vytvořil: protože ho bavilo vidět výsledky své práce, ale nebavila ho práce samotná („Happy Programming with Ruby On Rails“). Takže po pěti letech vývoje různých aplikací v PHP vytvořil — víceméně jako „vedlejší produkt“ při práci na projektu Basecamp — framework Rails. (Pro nabízející se srovnání Ruby s PHP a celou diskusi „nechápu, proč jsi Basecamp neudělal v PHP“ čtěte archivní diskusi na toto téma.) Oproti „běžné práci“ webového vývojáře vzniká výsledek v uvedeném videu snad jakoby sám od sebe, aplikace roste jako živý organismus.
Proč je tomu tak? Protože autor nedělá a především nevymýšlí žádné „nutné a nudné“ postupy: konfigurace, mapování URL, nepíše ani SQL dotazy. Všechny tyto základní věci se dějí jakoby „samy od sebe“. Autor se zabývá jen logikou aplikace samotné. Proč je tomu tak? Protože:
„Toto je sněhová vločka. Vaše aplikace není jedna z nich. Většina věcí, které většina lidí dělá, není nijak unikátní. Vaše potřeby nejsou nijak ‚zvláštní‘.“
Konvence má přednost před konfigurací
Nejste unikátní – to je naprosto klíčový princip designu Rails: webové aplikace jsou si v podstatě podobné jako vejce vejci. Načítají data z databáze, transformují je do datových struktur programovacího jazyka (pole, hashe, objekty), provádějí s nimi manipulace (řazení, filtrování, transformace textů), vypisují je do HTML, a prostřednictvím formulářů je zase sbírají, provádějí s nimi manipulace a ukládájí je do relačních databází. Interaktivita aplikace je tvořena odkazy a formulářovými prvky, které načítají různá URL.
Problém je v tom, že začínat psát běžnou webovou aplikaci v PHP od příkazu mysql_connect a zabývat se dotazy do databáze typu SELECT * FROM articles ORDER BY published_at je absurdně vyčerpávající a nevýhodné. Proto vstupuje na scénu záležitost zvaná framework, byť by to byly jen dvě třídy na abstrakci připojení k databázi a na generování formulářů. Enterprise řešení s sebou zase většinou přinášejí takovou zátěž konfigurace, nastavování, provázání technologií nebo závislosti na platformě, že je v běžném provozu používají jen specifické projekty.
Proto se DHH rozhodnul ctít hlavní pravidlo: konvence má přednost před konfigurací. V určité analogii s Paretovým principem tvrdí, že většina frameworků selhává proto, že se snaží řešit všechno, všechny možné případy, abstrahovat až na úplně nejvyšší úroveň, a fetišizuje „teoreticky čistá řešení“. Hlavní pravidlo designu Rails pak konkrétně znamená např. to, že autor konfiguruje v aplikaci pouze to, co se liší od běžného nastavení — vytvoří-li tedy např. model Person, aplikace bude data automaticky hledat v databázové tabulce people (tedy v tabulce, která se jmenuje stejně jako model, avšak v množném čísle). Chce-li, aby aplikace načítala data z tabulky staff, musí tak učinit výslovně.
Ještě uvidíme konkrétně, jak Rails filosofii „nejjednodušší řešení je zpravidla nejsprávnější“ (viz Occamova břitva) uplatňuje prakticky ve všech svých hlavních rysech. Ale už jen díky tomuto svému přístupu: „Flexibilita je přeceňována. Omezení jsou osvobozující“ Rails vnesly do nekonečných technických debat o rychlosti interpreterů, o tom, jestli je XML lék na všechno, správném pojetí objektově orientovaného programování, a podobně, nikoliv pravdu, ale čerstvý vítr. Pojďme se tedy podívat na principy, na nichž jsou Rails postaveny.
Model-View-Controller
Vyjdeme-li z definice v prvním článku o Ruby on Rails, Rails jsou Model-View-Controller framework. Co to přesně znamená?
Model-View-Controller (dále MVC) je ustálený návrhový vzor v softwarovém vývoji, způsob, jak transparentně oddělit různé části aplikace (lhostejno či webové nebo desktopové) podle toho, jakou úlohu v ní hrají. Model se stará o čtení a zapisování dat a o manipulaci s nimi („Vrať mi seznam osob, kde název zaměstnavatele je ‚Škoda Auto‘, seřazený vzestupně podle příjmení“); data předává controlleru. Controller (česky někdy zvaný řadič) se stará o příjem vstupů ze strany uživatele aplikace („Proveď akci definovanou pro kliknutí na odkaz ‚Telefonní seznam‘“) a distribuci výstupů („Načti stránku s identifikátorem ‚telefonni-seznam‘“). View (česky zvaný též pohled, v kontextu webu též šablona) obsahuje grafické rozhraní aplikace samotné, s definovanými ovládacími prvky (tlačítka, roletky, odkazy, …) a „dírami“, do nichž se leje obsah, který do view předává controller.

Jestliže se podíváme na strukturu Rails aplikace, mělo by nám tedy být již pár věcí docela jasných:
- app
- controllers
- helpers
- models
- views
- …
Ve složce app se nacházejí složky pro controllery, modely, i view. Tyto tři složky jsou srdcem Rails aplikace. Jaké jsou tedy konkrétní součásti Rails z pohledu MVC?
- Převedení dat z relační databáze na objekty Ruby (object-relational mapping), které zajišťuje Active Record (M)
- Směrování (routing) — mapování URL na vnitřní řídící prvky: controllery a jejich metody (funkce) a předávání dat mezi controllery a views (HTML šablonami) (C a V)
- Pomocné metody pro práci s HTML formuláři, Ajaxem, formátování řetězců, data a času a rozšíření Ruby tříd (M a V)
Další klíčové součásti Rails jsou:
- Skripty pro generování kódu, tedy např. šablon pro modely nebo controllery, tzv. lešení (scaffolding) aplikace, a dalších (script/generate)
- Konzole pro přímou interakci s modely běžící aplikace na příkazové řádce (script/console)
- Komunita a sdílení kódu — nejlepší kód je ten, který nemusíte psát. Tedy pokud nejste sněhová vločka. :)
Těchto šest oblastí nám dává dobrou představu o tom, jak jsou Rails uspořádány, a zároveň na nich lze dobře demonstrovat, v čem spočívá jedinečnost a elegance Rails. V tomto článku si projdeme pouze bod první, tedy Active Record — ostatním bodům se bude věnovat článek následující.
Active Record
Active Record je srdcem každé Rails aplikace. Tvoří jádro frameworku, a dokonale demonstruje intelektuální principy, na nichž jsou Rails založené, proto se u něj zdržíme nejprve a nejdéle.
Jak již bylo řečeno, Active Record je návrhový vzor, který mapuje databázové tabulky na třídy a převádí řádky na objekty (tedy instance tříd) a sloupce na jejich atributy: z názvu třídy (Article) odhadne název tabulky (articles). (Pro tento odhad obsahuje jednoduchou implementaci anglického slovníku, která je snadno rozšiřitelná.) Zároveň v implementaci Rails obsahuje výkonné nástroje na práci s daty (čtení, zápis, validace) a pro vztahy mezi tabulkami–objekty (ono Post has_many :comments, známé z podcastu).
To znamená, že odstiňuje vývojáře od „surového SQL“ (SELECT * FROM articles WHERE id = 1), od „vlastnoručního“ převádění výsledků dotazu na nativní datové entity a umožňuje efektivní práci na daleko vyšší úrovni. Zde je velmi dobře zřetelný intelektuální přínos Rails: 90% práce práce webového vývojáře spočívá právě v nekonečném psaní databázových dotazů SELECT ..., SELECT ... LEFT JOIN ..., UPDATE ... WHERE ..., případně v jejich konfiguraci pro nějaké obalující třídy či metody. To je samozřejmě z hlediska efektivity absurdní: pamatujeme si Matzovo diktum „čím více kódu, tím více chyb“. Jestliže se v 80% procentech případů nazývá tabulka v databázi stejně jako model, pouze v množném čísle (což dává logiku, obsahuje totiž více objektů, zatímco model reprezentuje povětšinou jeden objekt), nemá smysl tento fakt dokola a dokola potvrzovat v konfiguraci.
Proto místo dotazu SELECT * FROM articles WHERE id = 1, který nám k vrátí data:
+----+-------------+--------------------+--------+---------------------+
| id | title | body | author | created_at |
+----+-------------+--------------------+--------+---------------------+
| 1 | Lorem Ipsum | Dolor sit amet ... | Cicero | 0000-00-00 00:00:00 |
+----+-------------+--------------------+--------+---------------------+
v Rails vytvoříme třídu Article (ano, následující kód je úplný, nejsou potřeba inicializační ani žádné jiné metody):
class Article < ActiveRecord::Base
end
a napíšeme Article.find(1). Dostaneme objekt s příslušnými atributy:
--- !ruby/object:Article
attributes:
id: "1"
title: Lorem Ipsum
body: Dolor sit amet ...
author: Cicero
created_at: 0000-00-00 00:00:00
Pokud bychom mermomocí chtěli, aby model používal tabulku clanky, myimportantprefix_articles, nebo jinou, není nic snazšího:
class Article < ActiveRecord::Base
set_table_name "clanky"
end
Tím však možnosti, které nám Active Record pro efektivní práci při získávání dat dává, nekončí. Kromě prostého find, které hledá záznam dle sloupce id, můžeme využít jeho varianty:
Article.find(:all, :order => 'created_at DESC')nám vrátí všechny záznamy v tabulce articles seřazené sestupně dle dataArticle.find_all_by_author('Cicero', :order => 'title')nám vrátí všechny záznamy konkrétního autora seřazené vzestupně podle názvu (tyto metody se v Rails nazývají dynamické dotazy, dynamic finders)Article.find_all_by_author_and_title('Cicero', 'Lorem Ipsum')ukazuje, jak můžeme dynamické dotazy kombinovat
Bylo by asi ztrátou času vypisovat zde příslušné SQL dotazy, abychom si ukázali, kolik znaků (a tím i příležitostí k chybám) a energie jsme si ušetřili. Dynamické dotazy navíc krásně ukazují sílu objektově-relačního mapování.
Řekli jsme ale, že Active Record nám dává nástroje nejenom pro získávání dat, ale i pro manipulaci s nimi. Co když tedy chceme změnit některý atribut daného záznamu/objektu?
article = Article.find(1)
article.author = 'Seneca'
article.save
Jednodušší to být nemůže. Změním atribut objektu a ten uložím. Vypisovat zde ukázkové dotazy 'SELECT FROM ... UPDATE ... SET author='.$author.' WHERE id='.$id.', důvěrně známé všem webovým vývojářům, je pravděpodobně opět zbytečné: absolutní rozdíl v úrovni přístupu je zřejmý sám od sebe. Třídy odvozené od Active Record obsahují pochopitelně i metody create a destroy pro vytváření a mazání záznamů. (Active Record v Rails obsahuje také metodu find_by_sql pro specifické případy složitých dotazů, nebo například dotazů, které je třeba silně optimalizovat. Princip „konvence má přednost před konfigurací“ neznamená, že „konfigurace“, tedy odchylka od obvyklého, není možná.)
To ale stále není všechno, co nám Active Record poskytuje. Mapovat data z tabulek na „pole“ koneckonců umí kdekterá vlastnoručně stvořená knihovna pro PHP obalující připojení k databázi. Detailní popis Active Recordu přesahuje tento článek, ale pro naše ilustrační potřeby zmíníme dvě vlastnosti: validaci vstupních dat a vztahy mezi objekty.
Validace vstupních dat je jednou ze základních podmínek bezpečnosti a správné funkce webových aplikací. Přesto je přístup mnoha programátorů k ní více než laxní. Implementace Active Record v Ruby on Rails má funkce pro validaci zabudované. Vezměme si tedy definici třídy Article:
class Article < ActiveRecord::Base
validates_presence_of :author
end
V prostém jazyce říká, že atribut author nesmí být prázdný (article validates presence of author), tedy jinými slovy: článek musí mít autora — což je logické. Co se tedy stane, když se pokusíme uložit článek, který nemá autora, tedy: jehož atribut author je prázdný řetězec?
article = Article.find(1)
article.author = ''
article.save!
=> ActiveRecord::RecordInvalid: Validation failed: Author can't be blank
Boom! Autor nesmí být prázdný. (Vysvětlení pro nadprůměrně všímavé: zatímco metoda save z prvního příkladu vrací hodnoty true nebo false podle toho, zda se povedlo záznam uložit, bang-metoda save! z druhého příkladu při neúspěchu operace vyvolá definovanou výjimku. K bang-metodám se ještě v rámci výkladu o Ruby vrátíme v některém z příštích článků.) Platí totéž, co pro ukládání záznamu výše: jednodušší to být nemůže. Připočteme-li k tomu to, že Active Record je v Rails implementovaný tak, že formuláře jsou předvyplněny hodnotami objektu, že při neúspěšném uložení Active Record vrací chybu pro každý atribut (a můžeme ji tedy snadno vypsat k příslušnému poli) a že mezi další validační funkce patří např. validates_length_of, validates_uniqueness_of, validates_format_of (využívající regulární výrazy), je opět zjevné, kolik kódu nám Rails šetří, kolik kódu vůbec nemusíme psát.
Poslední vlastností Active Record, kterou se v našem úvodu budeme zabývat, je podpora pro vztahy mezi objekty. Náš příklad s třídou Article totiž neodpovídá vývoji ve skutečném světě. Těžko bychom ve skutečné aplikaci měli autory článků uložené ve sloupečku tabulky articles. Ne, vyčlenili bychom pro ně zvláštní tabulku, pravděpodobně tabulku s názvem authors. Článek může mít více autorů, autor může napsat více článků, jde tedy o vztah n ku m. Rails je, jak dobře víme, framework pro skutečný svět, ne pro zjednodušené tutoriály, a proto nám poskytuje nástroje i pro relativně složité vztahy mezi entitami podobného typu. Stačí nám jen dobře definovat vztahy v deklaraci tříd (tento kód je opět úplný, pro pouhé vzájemné propojení tabulek-objektů více nepotřebujeme):
class Article < ActiveRecord::Base
has_and_belongs_to_many :authors
end
class Author < ActiveRecord::Base
has_and_belongs_to_many :articles
end
Nyní tedy vytvoříme nový článek, nového autora, a autora k článku přiřadíme:
article = Article.new
article.title = "Lorem Ipsum"
cicero = Author.new
cicero.name = "Cicero"
cicero.email = "cicero@senatus.gov"
cicero.save!
article.authors << cicero
article.save!
To nebylo těžké. Když se nyní podíváme na objekt article, vidíme, že obsahuje pole s autory:
>> print article.to_yaml
--- &id002 !ruby/object:Article
attributes:
body:
title: Lorem Ipsum
id: 2
created_at: 2007-06-16 02:23:40.124124 +02:00
authors:
- &id001 !ruby/object:Author
attributes:
name: Cicero
id: 3
email: cicero@senatus.gov
Zrovna tak z druhé strany, pokud bychom chtěli dostat seznam Ciceronových článků:
>> print cicero.articles.to_yaml
---
- !ruby/object:Article
attributes:
body:
title: Lorem Ipsum
id: "2"
author_id: "3"
article_id: "2"
created_at: 2007-06-16 02:23:40
(Vysvětlení: metoda to_yaml, kterou v uvedených příkladech používáme, je upomínkou další silné zbraně Rails: formátu YAML, tedy „XML pro lidi“: způsobu čitelného zápisu strukturovaných dat. Rails jej využívají jednak pro konfigurace — např. připojení k databázi (/config/database.yml) —, tak pro čitelné výpisy datových struktur — např. pro ladění programu.)
A jak nyní vypadají tabulky (articles, authors, articles_authors) v databázi? Žádné velké překvapení nás nečeká:
| id | title | body | created_at |
|---|---|---|---|
| 2 | Lorem Ipsum | NULL | 0000-00-00 00:00:00 |
| id | name | |
|---|---|---|
| 3 | Cicero | cicero@senatus.gov |
| article_id | author_id |
|---|---|
| 2 | 3 |
V grafickém vyjádření pak vztahy vypadají takto:

Sloupce v tabulkách articles a authors odpovídají atributům našeho objektu, tabulky jsou na sobě nezávislé. Jejich propojení zajišťuje zvláštní tabulka s názvem articles_authors, která obsahuje dva sloupce pro definici vztahu mezi článkem a autorem: article_id a author_id. To, co vypadá tak logicky, samozřejmě a jednoduše, je samozřejmě výsledkem složité odhadovací logiky v Rails, nikoliv projevem nějaké inteligence stroje. Stroj neví a sám od sebe nemůže vědět, že tabulka s definicemi vztahů se jmenuje articles_authors. To, že nemusíme stroji všechny tyto „logické“ a „samozřejmé“ fakty stále dokola a dokola sdělovat, je výsledkem aplikace několika prostých pravidel:
Deklarace has_and_belongs_to_many v modelech zajistí, že Active Record je bude považovat za propojené a přidá přístupové metody k propojeným objektům, např. ono
cicero.articles, použité v příkladu. Has_and_belongs... je poměrně složitý název pro vztah n ku m. Je odvozen z direktiv pro vztah 1 ku n: has_many pro rodičovský objekt („Článek má mnoho komentářů“) a belongs_to („Komentář patří ke článku“) pro jeho děti. Ve vztahu n ku m jsou objekty ve složitějším vztahu: „Článek má mnoho autorů. Autor má mnoho článků.“.Rails se v takovém případě podívají do databáze, zda najdou tabulku, jejíž název je tvořen názvy tabulek pro dané objekty, oddělené podtržítkem, v abecedním pořádku, tedy: articles_authors. Pokud byste chtěli nebo potřebovali, aby tato tabulka měla jiný název, řekněme authors_to_articles, žádný problém, jde o jednoduchou konfiguraci vztahu v modelu:
Class Author ... has_and_belongs_to_many :articles, :join_table => "authors_to_articles".Tabulka definující vztah pak má dva sloupce: article_id a author_id (každému vztahu odpovídá jeden řádek). I tyto parametry je možné konfigurovat — chceme-li, aby se klíč vztahu jmenoval jinak, není problém:
Class Author ... has_and_belongs_to_many :articles, :foreign_key => "clanek_id".
Nejsou v tom tedy žádná kouzla. Kouzla způsobuje lidmi vymyšlený a napsaný kód v hlubinách Rails. Je to ale kód, který vy nemusíte vymýšlet a který vy nemusíte psát. Kouzla jsou způsobena důslednou aplikací principu konvence má přednost před konfigurací v implementaci Active Record. Není důvod konfigurovat název tabulky, je-li v drtivé většině případů utvořen jako množné číslo od názvu třídy. Není důvod konfigurovat tabulku vztahů, jmenuje-li se v drtivé většině případů jako název první tabulky podtržítko název druhé tabulky. Všechna data je třeba na vstupu validovat, proto je ošetření vstupních dat zabudováno přímo v Active Recordu.
To ale není všechno. V běžné webové aplikaci potřebujeme běžně ještě další nástroje na práci s daty: sčítání, výpočty průměrných hodnot, hledání pomocí LIKE, … Active Record v Ruby On Rails nám dává nástrojů mnohem více:
# Kolik článků napsal Cicero?
print cicero.articles.count
=> 1
# Vrať mi články od Cicerona, které začínají na ‚Lor‘
cicero = Author.find_by_name('Cicero')
print cicero.articles.find(:all, :conditions => "title LIKE 'Lor%'").to_yaml
=> --- !ruby/object:Article
attributes:
body:
title: Lorem Ipsum
id: "2"
author_id: "3"
article_id: "2"
created_at: 2007-06-16 02:23:40
Jak je snad z předchozího výkladu o Active Record vidět, Ruby on Rails nejsou „nějaký“ webový framework. Jeho intelektuální úroveň je zcela jedinečná: díky kombinaci promyšlené konfigurace a použití akademických vzorů, jako je Active Record, a zároveň nemilosrdně pragmatického přístupu k vývoji dávají vývojáři do ruky silný nástroj, který nemá jen tak nějaké srovnání.
V dalším článku výklad o Active Record doplníme o možnosti, které mu přidávají pluginy acts_as_attachment a acts_as_list, nejprve se však budeme věnovat zbylým slíbeným bodům.
Komplexní výklad Active Record naleznete v knize Agile Web Development with Rails, doposud nejlepší výukové a zároveň referenční knize o Rails, o níž si někdy také něco povíme.
~
19 komentářů