V minulém článku jsem vysvětlil co je to has_many :through a jak se používá pro reprezentaci tranzitivních relací. Dnes bych se rád dostal k samotné pointě toho miniseriálu. Jak již jsem psal minule, zajímavosti se začnou objevovat, když poslední příklad z minula torkšku rozšíříme:

class Person
has_many :likings
has_many :bands, :through => :likings
has_many :cds, :through => :bands
end
class Liking
belongs_to :person
belongs_to :band
end

Když jsem nad něcím podobným prvně přemýšlel měl jsem pochybnosti, že by Rails dokázali takovouto složitost realizovat takhle jednoduše. Nejdřív si ale objasněme o co tu jde. Jednoduše řečeno máme zde několik navazujících 1:N vztahů Person ->/likes/ Band -> Cds -> Tracks. A z těchto asociací chceme vytvořit asociaci Person -> Tracks (předpokládám, že když má rád skupinu, má rád CDčka atd.).

Toto ovšem není triviální funkčnost a jak se ukazuje Rails ji zatím nepodporují. Existuje už ovšem patch, který si můžete nainstalovat jako plugin. Diskuse o tom, proč taková samozřejmost není v přímo v Rails Core, rozhodně stojí za přečtení. Oba tábory mají svým způsobem pravdu, je na každém kam se přikloní. Jelikož jsou Rails dle mého názoru primárně o rychlosti a jednoduchosti vývoje, měl by se tento patch do core dostat co nejříve.

Po instalaci pluginu ovšem vše krásně funguje a my můžeme radostně používat HM:T asociace přes jiné HM:T asociace. Zbývá už poslední věc na, kterou se je třeba zaměřit. Jak to vlastně celé funguje? K tomuto je nejlepší spustit si Rails konzoli, zobrazit si development.log a zhodnotit vygenerované SQL.


> band.tracks # join přes tabulku cds
=> [..]
SELECT tracks.* FROM tracks
INNER JOIN cds ON tracks.cd_id = cds.id
WHERE ((cds.band_id = 1))
> person.bands # stejný typ joinu pro M:N asociaci
=> [..]
SELECT bands.* FROM bands
INNER JOIN likings ON bands.id = likings.band_id
WHERE ((likings.person_id = 1))
> person.tracks # po každou úroveň vnoření HM:T se přidá JOIN
=> [..]
SELECT cds.* FROM cds
INNER JOIN bands ON (cds.band_id = bands.id)
INNER JOIN likings ON (bands.id = likings.band_id)
WHERE (likings.person_id = 1 )
.

Jednoduchá HM:T asociace vygeneruje podle očekávání jeden JOIN navíc. Každé další HM:T zanoření pak přidává další JOIN. Toto může v některých případech být výkonnostní vada a možná je třeba uvažovat nad optimalizací databázového schématu. Ve většině případů to ničemu nevadí (pokud máte správně zaindexováno) a je to krásná feature, která vás ochrání od psaní dlouhých SQL v modelech.

Komentáře

K tomuto postu je 1 komentářů. Přidej vlastní →
EskiMag přidal 15.09.2008 19:34

Dakujem za tip. Tato feature mi dost chybala. Teraz je praca Rails este o kusok prijemnejsia :-)

Přidej komentář

Povinná pole jsou vyznačena tučně.