Witam,
Powiedzmy, że mam tablicę Kategorie, które właśnie są drzewkiem. Chciałbym, aby w widoku podstawowym pojawiała się lista pierwszego pokolenia, a po kliknięciu na wybraną kategorię pojawiała się pod nią lista jej dzieci i tak dalej - no takie jak klasyczne menu.
Zrobiłem coś takiego, ale wydaje mi się to bardzo nieeleganckie i długie. Może się trochę rozbestwiłem, ale jestem pewien, że istnieje jakieś krótkie, eleganckie rozwiązanie.
A ja jeszcze nie czuję się pewnie w Railsach i ciągle mi jakieś koszmarki wychodzą :-(.
Ktoś mógłby nakierować?
Aha, najlepiej bez JS.
A przy okazji drugie pytanie, takie trochę ogólniejsze i może trochę nie na temat, ale co tam:-)
Mam dwie tabele w relacji jeden-do-wiele więc w jednej z nich mam pole “druga_id”
I to (plus definicje w modelach) wystarcza aby Railsy prawidłowo mi operowały na tych tabelach i widziały relacje. Po co mi są więc “foreign keys” skoro i bez nich wszystko działa?
Chodzi o szybsze przeszukiwanie tabel, czy o coś innego?
Z góry dzięki za wszelką pomoc i pozdrawiam,
Maciek
Odn 1 pytania.
Nie mozna ocenic rozwiazania nie widzac go.
Odn 2 pytania.
Na poczatek:
http://www.loudthinking.com/arc/000516.html
http://martinfowler.com/bliki/DatabaseStyles.html
Uwazam teraz, ze (kiedys nie miescilo mi sie to w glowie, ale czasy sie zmieniaja
)
aplikacja (w warstwie dostepu do danych) odpowada za wiezy integralnosci referencyjnej i walidacje.
Wszystkie foreign key, unique, indexy i stuff to sprawa dla production environment i jako taka
powinna byc odseparowana od development (rake task, zwykly sql). Te sprawy optymalizacyjne sa bardzo wazne, ale nie w momencie projektowania. Idealnie DBA sie tym zajmuje.
Samo foreign key nie zapewnia wzrostu wydajnosci kwerend ale ma wymoc referential integrity (podobne sprawy mozna uzyskac triggerami w sytuacjach gdy uzycie foreign key jest niemozliwe) ale zazwyczaj (mysql robi to sam) tworzy sie jednoczesnie index, wiec jest to jak najb. wskazane.
Nie jestem DBA wiec jak tu jakas glupote palnalem to prosze o poprawke.
Kwestia gustu. 
Żeby nie zostawić wątku z tylko jedną opinią, wyrażę przeciwną.
Jak najwięcej z kontroli poprawności formatu, powiązań itp. powinno znaleźć się w bazie danych, między innymi dlatego, że nie można (czytaj: jest bardzo trudno) obejść taką kontrolę.
Jeżeli zależy Ci na poprawności danych w bazie, duplikując procedury walidacji w railsach (w warstwie aplikacji) masz dwóch strażników pilnujących dobytku. Jeśli jeden zachoruje lub zostanie oszukany (sql injection przez utf-7 bugs wykorzystane w ostatniej fazie księżyca, kiedy liczniki się zerują…) to masz w odwodzie drugiego.
Napisanie i testowanie reguł walidujących w SQL może być też łatwiejsze, bo reguły na tym poziomie są zazwyczaj prostsze/mniejsze (tu mi słów brakuje - ludzka język trudna język) niż ich odpowiedniki w języku aplikacji.
W SQL masz zwykły FOREIGN KEY ON DELETE CASCADE, a jaka jest ilość kodu związanego z takim samym zadaniem w Rubym? Chodzi o kod ‘związany’, nie tylko samo ‘has_many :dependent => :delete_all’.
Czy wiesz jak się zachowa aplikacja jeśli takiemu obiektowi x zrobisz ‘x = nil’? A jak jeśli zrobisz ‘x2 = x; x.delete; x2.delete’ ? A metoda ‘save_without_validation’? A metoda ‘update_attribute’ ?
Można sobie wyobrazić ‘Strażnika Aplikacji’, który kontroluje ciężarówki przy bramie magazynu. Sprawdza im dokumenty, zagląda w kontener itp.
Za nim masz ‘Strażnika SQL’, który rozładowując w magazynie paczki z ciężarówki pilnuje żeby na półce oznaczonej etykietką ‘Zielone’ nie położyć paczki w kolorze czerwonym.
Którego łatwiej oszukać? Który łatwiej może się pomylić?
Na koniec - gdzie umieszczasz logikę programu zależy od upodobań, założeń programu i możliwości bazy danych.
Nie twierdzę, że zawsze umieszczanie walidacji w bazie danych ma sens. Przy prostych i ‘niekrytycznych’ programach może być przeszkodą.
Jednak przy złożonym programie czuję się lepiej, kiedy wiem że na ostatnim etapie składowania moich danych jeszcze ktoś czuwa.
Dzięki chłopaki za linki i wyczerpujące odpowiedzi na drugie pytanie. Teraz przynajmniej z grubsza wiem o co chodzi 
Ale nie wiem np. czy w migracji nie mogłoby być jakiejś abstrakcji tych rzeczy zamiast execute SQL ? Może to nie miałoby sensu ale jakoś tak wydaje mi się, że byłoby naturalne w Railsach. Hm.
A co do głównego pytania wątku, to wymodziłem w końcu takie rozwiązanie.
Mam drzewko z jednym elementem root, potem są kategorie główne, a w nich podkategorie do dowolnego poziomu.
W kontrolerze:
def list
@categories = Category.find(:all, :include=>[:children])
@mainlevel = Category.find(:first, :conditions=>'parent_id IS NULL')
if params[:id]
@ciag=Category.find(params[:id]).ancestors.reverse
@ciag << Category.find(params[:id])
end
end
I dwa helpery:
def show_main (categories)
ret = "<ul>"
@mainlevel.children.each do |main|
ret += "<li>"
ret += link_to main.name , :action => 'list', :id=> main.id
ret += "</li>"
end
ret += "</ul>"
end
def find_children_plus (ciag)
ret = "<ul>"
ciag.shift.children.each do |node|
ret+="<li>"
ret += link_to h(node.name) , :action => 'list', :id=> node
if node==ciag[0]
ret+=find_children_plus (ciag)
end
ret += "</li>"
end
ret += "</ul>"
end
A w widoku:
<% if params[:id] %>
<%= find_children_plus(@ciag) %>
<%else%>
<%= show_main (@categories) %>
<%end %>
Esencją jest rekurencyjny find_children_plus , którego argumentem jest ciąg przodków wybranej podkategorii plus ona sama.
Czy to jest zgrabne rozwiązanie? A może poukładać to wszystko jakoś inaczej?
Będę wdzieczny za opinie i pomysły - może to trochę durny przykład ale ponieważ łączy w sobie kilka rzeczy pozwala mi nabrać jakiegoś wyczucia w poruszaniu się w środowisku Railsów.
troche na szybko i późno robione ale u mnie wyglada to narazie tak
kontroler->
def lista_kategorii
@tree = Kategoria.find(:all, :include=> [:children])
@ciag = []
if params[:id]
Kategoria.find(params[:id]).ancestors.reverse.each {|poziom| @ciag << poziom.id}
@ciag << params[:id]
end
end
helper->
def wyswietl_drzewo_jako_lista_do_menu(tree, parent_id, ciag)
ret = ‘
- ’
- ”
ret += link_to(node.nazwa, :action => “lista_kategorii”, :id=>node.id)
if ciag[0].to_i==node.id
ciag.shift
ret += wyswietl_drzewo_jako_lista_do_menu(tree, node.id, ciag) { |n| yield n }
end
ret += “ ”
tree.each do |node|
if node.parent_id == parent_id
ret += “
end
i widoku po prostu->
<%= wyswietl_drzewo_jako_lista_do_menu(@tree, nil, @ciag) %>
w twojej metodzie (choc przyznam sie szczerze ze nie sprawdzilem) wyswietlą sie chyba tylko rodzice danego noda. tu wyswietlaja sie rodzice i rodzenstwo kazdego z nich
moim zdaniem im mniej w widoku tym lepiej
No to teraz ja 
Osobiscie strasznie nielubie act_as_tree z powodu latwego zarzniecia bazy duza iloscia zapytan przy generowaniu drzewka z 20-30 rozgalezieniami. O ile dobrze pamietam to w 1.0 produkowalo mi to okolo 20 zapytan, nie wiem jak wyglada to teraz;) Moze cos lepszego wymyslili.
Dlatego tez napisalem wlasna act_as do tego oraz przekrztalcilem go w plugin. Uzywa sie go tak samo jak act_as_tree z tym ze dodatkowo w bazie oprocz id i parent_id stosuje jeszcze position. Akurat w pluginine :order jest domyslnie ustawiony walsnie na position a nie na id.
Dostepne metody z poziomu klasy:
find_all_as_tree() - zwroci tablice zawierajaca posortowane elementy jako drzewo
find_first_as_tree() - zwroci pierwszy element jako element drzewa
find_nodes_to_root() - zwroci wszystkie elementy prowadzace do glownego korzenia od konca
Dostepne metody z poziomu instancji:
level - zwroci wartosc poziomu galezi aktualnego elementu
root_id - przechowywany jest tutaj id pierwszego elementu danej galezi
parent - wiadomo
root - …
children - …
siblings - …
ancestors - zworci wszystkie elementy kierujace sie w strone korzenia drzewa
offsprings - zwroci wszystkie elementy kierujace sie w strone korony drzewa
nodes_to_root - to samo co find_noteds_to_root z tym ze dla instancji bez argumentow
Napewno elementy instancji mozna by napisac lepiej Sam plugin po prostu dziala wiec moze sie komus przyda Osobiscie uwazam ze glowna zaleta tutaj jest to ze level oraz root_id sa niezalezne od bazy danych, nie trzeba ich przemapowywac za kazdym razme gdy zmieni sie dla danego elementu galaz, jest to bardzo wygodne.
Oceniam wersje plugina na 0.6 Po prostu dziala ale moglby byc napisany jeszcze lepiej, moze kiedys znajde checi.