Zaczynam przygodę z railsami (v3) chyba standardowo od przykładów blogowych.
Kwestia następująca: Jest artykuł który ma wiele kategorii.
[code]class Article < ActiveRecord::Base
has_and_belongs_to_many :categories
…
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :articles
…
end[/code]
Dla akcji show zdefiniowanej jako
def edit
@article = Article.find(params[:id])
end
w widoku mam
<% for category in Category.all %>
<%= check_box_tag 'article[category_ids][]', category.id, @article.category_ids.include?(category.id), :id => dom_id(category) %>
<%= label_tag dom_id(category), category.name, :class => "check_box_label" %>
<% end %>
To powoduje wpisy w logu
Started GET "/articles/2/edit" for 127.0.0.1 at 2011-02-04 21:10:21 +0100
Processing by ArticlesController#edit as HTML
Parameters: {"id"=>"2"}
Article Load (0.0ms) SELECT "articles".* FROM "articles" WHERE ("articles"."id" = 2) LIMIT 1
Category Load (0.0ms) SELECT "categories".* FROM "categories" ORDER BY categories.name
Category Load (0.0ms) SELECT "categories".id FROM "categories" INNER JOIN "articles_categories" ON "categories".id = "articles_categories".category_id WHERE ("articles_categories".article_id = 2 ) ORDER BY categories.name
CACHE (0.0ms) SELECT "categories".id FROM "categories" INNER JOIN "articles_categories" ON "categories".id = "articles_categories".category_id WHERE ("articles_categories".article_id = 2 ) ORDER BY categories.name
CACHE (0.0ms) SELECT "categories".id FROM "categories" INNER JOIN "articles_categories" ON "categories".id = "articles_categories".category_id WHERE ("articles_categories".article_id = 2 ) ORDER BY categories.name
CACHE (0.0ms) SELECT "categories".id FROM "categories" INNER JOIN "articles_categories" ON "categories".id = "articles_categories".category_id WHERE ("articles_categories".article_id = 2 ) ORDER BY categories.name
CACHE (0.0ms) SELECT "categories".id FROM "categories" INNER JOIN "articles_categories" ON "categories".id = "articles_categories".category_id WHERE ("articles_categories".article_id = 2 ) ORDER BY categories.name
Jeśli natomiast zrobię tak:
def edit
@article = Article.find(params[:id])
@categories = Category.all
@article_categories = @article.category_ids
end
To log wygląda sensowniej
Started GET "/articles/2/edit" for 127.0.0.1 at 2011-02-04 21:12:54 +0100
Processing by ArticlesController#edit as HTML
Parameters: {"id"=>"2"}
Article Load (0.0ms) SELECT "articles".* FROM "articles" WHERE ("articles"."id" = 2) LIMIT 1
Category Load (0.0ms) SELECT "categories".* FROM "categories" ORDER BY categories.name
Category Load (0.0ms) SELECT "categories".id FROM "categories" INNER JOIN "articles_categories" ON "categories".id = "articles_categories".category_id WHERE ("articles_categories".article_id = 2 ) ORDER BY categories.name
Pytanie od zielonego, dlaczego tak? Jak rozwiązać to inaczej niż poprzez ustawianie zmiennych w kontrolerze (dla bardziej rozbudowanych obiektów będize to mało fajne). Dlaczego w wersji pierwszej wygląda jakby ten JOIN był ciągnięty kilka razy. Co ciekawe wpisów CACHE w logu jest dokładnie o 1 mniej niż wszystkich kategorii w bazie.
Dobra, trochę ogarnąłem.
Zapytanie jest dlatego, że przy każdej iteracji po kategoriach pobieram kategorie artykulu i sprawdzam czy dana jest wsrod nich. Za 1 razem bierze z bazy a później cacheuje, dlatego jest n-1 logów CACHE. Pytanie, czy da się to inaczej zrobić, żeby minąć to cacheowanie? Bo przy dużej liczbie elementów w relacji to będize rzygało do loga jak szalone.
Dzięki za zainteresowanie tematem.
Jest tak jak mówisz, dane są brane z cache. Z tym że chciałem uniknąć tego, żeby tak brzydko nie rzygał do loga. Udało mi się to tylko przez ustawienie categories i article_categories w kontrolerze.
Teraz mam kolejny problem. Tym razem przy którejś z kolei modyfikacji Article (dokładnie przy różnych zmianach kategorii).
ActiveRecord::AssociationTypeMismatch in ArticlesController#update
Category(#13434360) expected, got Category(#12977376)
Model ten sam co wyżej. Działa za pierwszym razem, czasami też za drugim a później rzyga takim czymś.
Gdyby ktoś chciał spróbować to http://github.com/downloads/ccjr/blog/chapter08.zip są to źródła z “Beginning Rails 3” i dzieje się dokładnie to samo.
Googlałem ale nic nie wymyśliłem. Nested_attributes nie działają dla habtm.
Musisz zrozumieć kiedy danę z brane z cache Railsów bez wywoływania zapytania a kiedy są brane z cache bazy danych bo niedawno było takie samo query.
To pierwsze nie generuje wpisu do loga i jest szybkie a to drugie generuje.
wersja @article.categories.include?(category) nie powinna “rzygać do loga”. (1 query powinno iść dla @article.categories i jedno dla Category.all).
Natomiast:
Category(#13434360) expected, got Category(#12977376)
Ten problem występuje np gdy w konsoli zrobisz reload! i będziesz próbował użyć kategori po reload! na obiekcie który był utworzony przed reload!.
Kiedy jest brane z cache to wiem, zresztą widać w logach CACHE, chodzi bardziej o to, żeby wyelimnować próby sięgania do cache tak jak to robię przy ustawianiu zmiennych w kontrolerze. Pewnie to krzywe rozwiązanie ale jak widzę w logach powtarzający n razy po sobie się wiersz CACHE to jakoś tak się dziwnie czuję
@article.categories.include?(category)
właśnie wyrzuca do loga pierwszy load prawdziwy i później cache.
to domyślam się czym on jest spowodowany i przy używaniu konsoli nie zdizwiłoby mnie to pewnie, ale to się dzieje normalnie przy requestach z przeglądarki. Póki co nie wiem jak to rozwiązać. Będę wdzięczny za wszelką pomoc.
Nie, to co innego. Zamiast has_and_belongs_to_many tworzysz dodatkowy model łączący z jakąś sensowną nazwą (np. dla związku User <-> User może to być Friendship, dla User <-> Role np. Relationship itp). Ten dodatkowy model ma tę zaletę, że pozwala na przechowywanie dodatkowych informacji, np. kiedy dana relacja (przyjaźni, uprawnienia, członkostwa) została utworzona, przez kogo, jaki ma status itp. Przy has_and_belongs_to_many nie masz takiej możliwości.