Eager loading - ładowanie modelu z powiązanymi danymi

Wysłane przez Krzysztof Kempiński dnia 01.09.2007

Eager loading, to metoda pozwalająca załadować określone dane powiązane z modelem. Oznacza to, że jest możliwe zaczytanie danych połączonych określoną relacją tylko raz, podczas tworzenia obiektu modelu, a nie za każdym razem kiedy odwołujemy się do którejś z relacji.


Żeby to zobrazować przedstawię krótki przykład kodu. Wyobraźmy sobie, iż mamy model Product, który jest powiązany relację has_many z modelem Comment (produkt posiada wiele komentarzy)
class Product < ActiveRecord::Base
  has_many :comments
end
W kontrolerze chcąc pobrać wszystkie produkty zastosujemy konstrukcję:
@products = Product.find(:all)
Powiedzmy, iż w widoku chcemy wylistować produkty, wraz z ilością należących do nich komentarzy. Zapiszemy to tak:
<% for product in @products %>
  <%= product.name %> (komentarzy: <%= product.comments.length %>) <br />
<% end %>

Jeśli spojrzymy w logi serwera zauważymy iż dla każdego produktu z pętli, w momencie wywołania "product.comments.length" wywoływane jest zapytanie pobierające komentarze, powiązane z konkretnym produktem. W niektórych sytuacjach, takie rozwiązanie jest mało optymalne i generuje znaczną liczbę zapytań do bazy.
Zamiast tego możemy zastosować eager loading. Zmodyfikujemy nieco pobieranie produktów:
@products = Product.find(:all, :include => :comments)
Użyliśmy zatem dodatkowego parametru w funkcji find - ":include => :comments", co powoduje, że oprócz produktów, są również ładowane wszystkie komentarze, należące do poszczególnych produktów. Mamy zatem jedno zapytanie SQL, a pętla w widoku nie generuje już dodatkowych zapytań.
Jeśli nasz model Product posiadałby jeszcze inne relacje, np. byłby powiązany z Rating, moglibyśmy je również załadować automatycznie, stosując tablicę:
@products = Product.find(:all, :include => [:comments, :ratings])
Ważne jest żeby w parametrze include były podawane relacje według, których następuje złączenie. Czyli jeśli mamy w modelu
belongs_to :category
to nasze zapytanie będzie wyglądać tak:
@products = Product.find(:all, :include => [:comments, :category])