Better nested set: roots + nested children одним запросом

26 views
Skip to first unread message

Dmitry Polushkin

unread,
Oct 4, 2007, 10:16:11 AM10/4/07
to RubyOnRails to russian
Может быть кто-нибудь сталкивался?

Как выбрать все roots + nested children одним запросом?

Или лучше, если рисуешь список категорий, каждый раз в roots.each
обращаться к методу children?

Заранее благодарен,
Дмитрий

Zork

unread,
Oct 4, 2007, 11:36:25 AM10/4/07
to RubyOnRails to russian
дописываешь :order => 'lft ASC'
либо у ассоциаций в модели, либо при выборке Model.find_all(:order =>
'lft ASC')

Dmitry Polushkin

unread,
Oct 4, 2007, 6:36:58 PM10/4/07
to ror...@googlegroups.com
Если я выберу таким образом, то все доступные методы, вроде roots,
children и прочих будут недоступны.
Как мне пройтись по дереву?

Zork

unread,
Oct 5, 2007, 10:52:30 AM10/5/07
to RubyOnRails to russian
да вроде должны быть доступны. таким запросом ты выбираешь все записи
в таблице в коллекцию отсортированную с учетом вложенности, в которой
каждый элемент является узлом дерева с соответствующими методами
(level там точно есть, значит и остальные где то там). можно
прогулятся по коллекции спрашивая у узлов не руты ли они случайно и
собственно делать с ним чего надо.

Вообще от задачи зависит, соственно дерево комментариев вывести, по
дереву лазит и не надо просто отступ делаешь ориентируясь на level. ну
или в переборе учитывешь текущий level тогда можно вывести вложенные
структуры, html'ные списки например. Меню туда же в принципе.

On 5 окт, 02:36, "Dmitry Polushkin" <dmitry.polush...@gmail.com>
wrote:


> Если я выберу таким образом, то все доступные методы, вроде roots,
> children и прочих будут недоступны.
> Как мне пройтись по дереву?
>

Maxim Kulkin

unread,
Oct 5, 2007, 11:45:33 AM10/5/07
to ror...@googlegroups.com
On Friday 05 October 2007 18:52:30 Zork wrote:
> да вроде должны быть доступны. таким запросом ты выбираешь все записи
> в таблице в коллекцию отсортированную с учетом вложенности, в которой
> каждый элемент является узлом дерева с соответствующими методами
> (level там точно есть, значит и остальные где то там). можно
> прогулятся по коллекции спрашивая у узлов не руты ли они случайно и
> собственно делать с ним чего надо.
>
> Вообще от задачи зависит, соственно дерево комментариев вывести, по
> дереву лазит и не надо просто отступ делаешь ориентируясь на level. ну
> или в переборе учитывешь текущий level тогда можно вывести вложенные
> структуры, html'ные списки например. Меню туда же в принципе.
Помнится, я в рассылку с месяц назд присылал код хитрой прокси-коллекции,
которая позволяла обходить набор потомков как дерево. Сейчас под рукой ничего
нет, посмотрите в архивах рассылки.

Dmitry Polushkin

unread,
Oct 5, 2007, 11:51:27 AM10/5/07
to ror...@googlegroups.com
Отрисовать дерево - это не есть проблема. Проблемма лежит в
оптимизации. Каждрый раз при запросе к Category.roots.each {
|category| category.children } базе посылается запрос. К сожалению это
нагружает не только базу данных, но ещё и рельсовое приложение. Мне же
хочется, что бы для вызвращения всех категорий (и рутовых, и чайлдов)
был послан всего один запрос.

Zork

unread,
Oct 5, 2007, 2:18:59 PM10/5/07
to RubyOnRails to russian
Это и есть то что я написал в первом ответе. вытаскивается вся
коллекция, то есть все записи из базы. и они уже отсортированы по
левому ключу. это значит что они идут в порядке вложенности (не нужна
рекурсивная обработка что бы их правильно расположить).
Category.roots.each { |category|
category.children
}
не нужно
для примера: тут я пытаюсь один раз дернув из базы категории, вывести
их как вложенные списки

current_level = 0
Category.each do |cat|
if cat.level > current_level then
puts "<ul>\n"
puts "<li>#{cat.name}</li>\n"
current_level += 1
elsif cat.level == current_level then
puts "<li>#{cat.name}</li>\n"
elsif cat.level < current_level then
puts "</ul>\n"
puts "<li>#{cat.name}</li>\n"
current_level -= 1
end
end

может в коде есть ошибки я на запускал, но примерно так

On 5 окт, 19:51, "Dmitry Polushkin" <dmitry.polush...@gmail.com>
wrote:


> Отрисовать дерево - это не есть проблема. Проблемма лежит в
> оптимизации. Каждрый раз при запросе к Category.roots.each {
> |category| category.children } базе посылается запрос. К сожалению это
> нагружает не только базу данных, но ещё и рельсовое приложение. Мне же
> хочется, что бы для вызвращения всех категорий (и рутовых, и чайлдов)
> был послан всего один запрос.
>

Maxim Kulkin

unread,
Oct 5, 2007, 3:07:37 PM10/5/07
to ror...@googlegroups.com
On 10/5/07, Zork <zo...@nwgsm.ru> wrote:
> current_level = 0
> Category.each do |cat|
> if cat.level > current_level then
> puts "<ul>\n"
> puts "<li>#{cat.name}</li>\n"
> current_level += 1
> elsif cat.level == current_level then
> puts "<li>#{cat.name}</li>\n"
> elsif cat.level < current_level then
> puts "</ul>\n"
> puts "<li>#{cat.name}</li>\n"
> current_level -= 1
> end
> end
>
> может в коде есть ошибки я на запускал, но примерно так

Разве это красота ? Вот это - красота:

<%= render :partial => 'tree', :locals => { :items => @posts } %>

а в _tree.html.erb

<ul>
<% items.each_with_children do |item, children| %>
<li>
<span class="title"><%= item.title %></span>
<%= render :partial => 'tree', :locals => { :items => children } %>
</li>
<% end %>
</ul>

Вся магия - в хитрой коллекции-обертке.

Zork

unread,
Oct 5, 2007, 3:18:49 PM10/5/07
to RubyOnRails to russian
А черт подери, действительно красота, но тогда действительно прокси
объект нужон.. :)

On 5 окт, 23:07, "Maxim Kulkin" <maxim.kul...@gmail.com> wrote:

Dmitry Polushkin

unread,
Oct 5, 2007, 3:31:31 PM10/5/07
to ror...@googlegroups.com
Вот-вот... у меня точно так же:

<%= render :partial => "layouts/categories", :locals => { :categories
=> Category.roots, :root => true } %>

и в файле части:

<ul<% if root %> id="categories"<% end %>>
<% categories.each do |category| %>
<li>
<%= category.name %>
<% if category.children_count > 0 %>
<%= render :partial => "layouts/categories", :locals => {
:categories => category.children } %>
<% end %>


</li>
<% end %>
</ul>

Но что меня тут настораживает:
category.children_count - один запрос
category.children - ещё один

И так в каждой итерации.

Пример Zork-а мне абсолютно не нравится, как то банально... тем более
откуда level берётся - не понятно. Конечно, его можно вычислить через
массив parent'ов и сравнение parent'ов... но это всё ещё сильнее
усложняет.

Вполне возможно, есть другой вариант, использовать один root элемент,
и делать с него full_set. Но быть может сделать full_set можно не с
какого-либо root node, а прямо с настоящего корня, т.е. где у всех
node'ов parent_id IS NULL?

Zork

unread,
Oct 5, 2007, 4:14:41 PM10/5/07
to RubyOnRails to russian
level это метод который подмешивается к объекту, когда обявляешь
модель acts_as_nested_set, он должен вычислятся при выборке. если ты
наченшь дергать методы типа children, то он начент обращаться к базе,
это да. типа реализация такая.

<ul<% if root %> id="categories"<% end %>> тут надо <% if root? %>,
иначе тоже обращение к базе будет.

насчет full set это конечно можно, но тут "настоящего корня" у
вложенных множеств нет, потому что может быть несколько корней и в них
parent_id = 0
и вообще parent_id это вспомогательный параметр, для дерева во
вложенных множествах достаточно левого и правого ключа.

Если делать красивую реализацию как у Maxim Kulkin, то нужно создать
прокси объект. имеющий рекурсивную структуру (то есть каждый такой
объект может содержать набор таких же объектов) и реализовать у него
методы типа children.

тогда получится что ты будешь вызывать методы не у модели которая
реализует дерево (и при вызове children лезет в базу), а у своего
объекта, который содержит в себе все дерево, и собственно в базу не
лезет...

On 5 окт, 23:31, "Dmitry Polushkin" <dmitry.polush...@gmail.com>
wrote:

> On 10/5/07, Maxim Kulkin <maxim.kul...@gmail.com> wrote:

Dmitry Polushkin

unread,
Oct 5, 2007, 4:35:20 PM10/5/07
to ror...@googlegroups.com
Чем моя реализация отличается от реализации Максима Кулкина? :))

Это то понятно, что можно... единственное что меня сдерживало от
написания подобного метода, или кеширования это то, что быть может
кто-нибудь уже сталкивался с подобной проблемой и решил её
каким-нибудь ещё более изящным способом, да или же просто, кто то уже
до меня реализовал этот метод, который сразу же грузит все ветки.

On 10/5/07, Zork <zo...@nwgsm.ru> wrote:

Zork

unread,
Oct 5, 2007, 4:56:37 PM10/5/07
to RubyOnRails to russian
Внешне ничем, если есть "Вся магия - в хитрой коллекции-обертке."
внешняя коллекция обертка, то вообще ничем. Сразу грузит все ветки
запрос с упорядовачинием по левому ключу. Кстати даже красивый
вариант, использует рекурсию через шаблоны. Вложенные множества
позволяют не использовать рекурсию (так как вложенность упорядочена).
Используя прокси объект вы сначало из линейного упорядоченого по
вложенности списка создаете набор рекурсивных объектов (по типу DOM),
используя примерно тот код, что я привел для вывода, а потом используя
рекурсивные шаблоны их выводите. Что то проразмыслив тут, сложилось у
меня мнение, что в сходной ситуации я бы воспользовался все таки своим
методом, хоть и не красивым.

On 6 окт, 00:35, "Dmitry Polushkin" <dmitry.polush...@gmail.com>
wrote:


> Чем моя реализация отличается от реализации Максима Кулкина? :))
>
> Это то понятно, что можно... единственное что меня сдерживало от
> написания подобного метода, или кеширования это то, что быть может
> кто-нибудь уже сталкивался с подобной проблемой и решил её
> каким-нибудь ещё более изящным способом, да или же просто, кто то уже
> до меня реализовал этот метод, который сразу же грузит все ветки.
>

Maxim Kulkin

unread,
Oct 5, 2007, 5:13:18 PM10/5/07
to ror...@googlegroups.com
On 10/6/07, Dmitry Polushkin <dmitry.p...@gmail.com> wrote:
> Чем моя реализация отличается от реализации
> Максима Кулкина? :))
! Если не знаете как пишется, пишите как написано (Maxim Kulkin)...

Да, тоже неплохо.

Когда я писал свою реализацию, мне a) не хотелось писать доп.
прокси-объект на каждый элемент коллекции (ровно как и подмешивать
children в существующие - мало ли, может там уже такое есть и я
перетру существующий своим); b) не хотелось на каждый элемент
порождать еще один объект-обертку - не выгодно по памяти (использовать
один и тот же объект тоже, имхо, неудобно).
У меня по одному экземпляру прокси на каждую подветку, при этом массив
используется всегда исходный, никаких копирований и т.п.

Дешево и сердито.

> Это то понятно, что можно... единственное что меня сдерживало от
> написания подобного метода, или кеширования это то, что быть может
> кто-нибудь уже сталкивался с подобной проблемой и решил её
> каким-нибудь ещё более изящным способом, да или же просто, кто то уже

Видел как кэшируют. Имхо, не айс.

> до меня реализовал этот метод, который сразу же грузит все ветки.

Ага, надо встроить внутрь AR его.

Maxim Kulkin

unread,
Oct 5, 2007, 5:18:47 PM10/5/07
to ror...@googlegroups.com
On 10/6/07, Zork <zo...@nwgsm.ru> wrote:
> Внешне ничем, если есть "Вся магия - в хитрой коллекции-обертке."
> внешняя коллекция обертка, то вообще ничем. Сразу грузит все ветки
> запрос с упорядовачинием по левому ключу. Кстати даже красивый
> вариант, использует рекурсию через шаблоны. Вложенные множества
> позволяют не использовать рекурсию (так как вложенность упорядочена).
> Используя прокси объект вы сначало из линейного упорядоченого по
> вложенности списка создаете набор рекурсивных объектов (по типу DOM),
> используя примерно тот код, что я привел для вывода, а потом используя
> рекурсивные шаблоны их выводите. Что то проразмыслив тут, сложилось у
> меня мнение, что в сходной ситуации я бы воспользовался все таки своим
> методом, хоть и не красивым.

Это известная фигня, что оно якобы будет работать медленнее. Я считаю,
что это не то место, где нужно жертвовать чистотой и красотой в пользу
производительности. По крайней мере не на тех объемах, для которых
писал это я.

Julian 'Julik' Tarkhanov

unread,
Oct 6, 2007, 1:23:20 AM10/6/07
to ror...@googlegroups.com

On 5-okt-2007, at 23:18, Maxim Kulkin wrote:

>
> Это известная фигня, что оно якобы
> будет работать медленнее. Я считаю,
> что это не то место, где нужно
> жертвовать чистотой и красотой в
> пользу
> производительности. По крайней мере
> не на тех объемах, для которых
> писал это я.

Мне конечно стыдно со своим рылом в
калашный ряд (у меня сет не better а
простой vanilla)

# Returns a set of all of its children and nested children
def all_children
self.class.find(:all,
:conditions => "#{scope_condition} AND #{root_column} = #{self
[root_column]} AND #{left_col_name} > #{self[left_col_name]} AND #
{right_col_name} < #{self[right_col_name]}",


:order => 'lft ASC' )

end

а дальше

<% @categories.each do | category | %>
<h5><%= checkbox_for_category(category) %> <%= h
category.name %></h5>

<% category.all_children.each do | sub_category | %>
<div style='margin-left: <%= sub_category.depth %>em'>
<%= checkbox_for_category(sub_category) %> <%= h
sub_category.name %>
</div>
<% end %>
<% end %>

вторая часть это камень в огород
рекурсивистов
ну не будет вложенных UL и что? кто
скончается от этого?
--
Julian 'Julik' Tarkhanov
please send all personal mail to
me at julik.nl


Maxim Kulkin

unread,
Oct 6, 2007, 2:38:07 AM10/6/07
to ror...@googlegroups.com
On 10/6/07, Julian 'Julik' Tarkhanov <julian.t...@gmail.com> wrote:
> Мне конечно стыдно со своим рылом в
> калашный ряд (у меня сет не better а
> простой vanilla)
>
> <% @categories.each do | category | %>
> <h5><%= checkbox_for_category(category) %> <%= h
> category.name %></h5>
>
> <% category.all_children.each do | sub_category | %>
> <div style='margin-left: <%= sub_category.depth %>em'>
> <%= checkbox_for_category(sub_category) %> <%= h
> sub_category.name %>
> </div>
> <% end %>
> <% end %>
>
> вторая часть это камень в огород
> рекурсивистов
> ну не будет вложенных UL и что? кто
> скончается от этого?
Так
1. У тебя же дерево фиксированной глубины (глубины 2). Мы же говорили
о деревьях произвольной глубины.
2. Без вложенных UL получается не семантический HTML. И мы говорили о
случае, когда это не безразлично.

Я, кстати, тоже использовал обычный nested_set, потому что мне его
функционала вполне хватало (мне не нужно было переносить элементы из
одного места в дереве в другое, называть столбцы я мог как хотел)...

Julian 'Julik' Tarkhanov

unread,
Oct 6, 2007, 3:00:24 AM10/6/07
to ror...@googlegroups.com

On 6-okt-2007, at 8:38, Maxim Kulkin wrote:

> Так
> 1. У тебя же дерево фиксированной
> глубины (глубины 2). Мы же говорили
> о деревьях произвольной глубины.
Посмотри внимательнее. Другое дело
что я делаю различие в разметке
только между первым уровнем
и более глубокими.
> 2. Без вложенных UL получается не
> семантический HTML. И мы говорили о
> случае, когда это не безразлично.
Я показал как дернуть все дочерние
узлы разом. Дернув их можно спокойно
собрать
хелпер, который закрывает '<ul>' когда
уровень снова поднимается на единицу.
Не хаскелл, но работает :-)
Если у этого better_nested_set нету depth то не
сильно то он better

Zork

unread,
Oct 6, 2007, 7:12:37 AM10/6/07
to RubyOnRails to russian
У него (better nested set) есть level, вместо depth. Метод для вывода
вложенных списков без рекурсии я приводил выше, его признали
некрасивым :) и его действительно можно запихать в хелпер и тогда
шаблоны остануться чистыми.

On 6 окт, 11:00, Julian 'Julik' Tarkhanov <julian.tarkha...@gmail.com>
wrote:

Maxim Kulkin

unread,
Oct 7, 2007, 3:57:33 AM10/7/07
to ror...@googlegroups.com
On 10/6/07, Zork <zo...@nwgsm.ru> wrote:
> У него (better nested set) есть level, вместо depth. Метод для вывода
> вложенных списков без рекурсии я приводил выше, его признали
> некрасивым :) и его действительно можно запихать в хелпер и тогда
> шаблоны остануться чистыми.
Ну
1. запихать в хелпер можно, но мне не представляется, как его сделать
гибким (надо передавать как минимум то, что вставлять при увеличении
уровня, и то, что вставлять после уменьшения уровня) и в то же время
красивым и удобным для использования в, например, ERb шаблонах.
2. насколько я помню, обращение к #level в better nested set - это
отдельный запрос к БД. Так что, имхо, не сильно лучше чем просто
ходить по children. И получается - возвращаемся к кэшированию уровня в
отдельном аттрибуте элемента дерева.

Zork

unread,
Oct 7, 2007, 7:26:31 AM10/7/07
to RubyOnRails to russian
1. Хелперы можно создавать на каждый вариант использования. вряд ли их
очень много. тогда можно передвать только коллекцию.
2. Посмотрел в исходники точно level обращение к базе за исключеним
корней. Блин зачем так делать... странные люди... Надо будет
кешировать.

On 7 окт, 11:57, "Maxim Kulkin" <maxim.kul...@gmail.com> wrote:
> On 10/6/07, Zork <z...@nwgsm.ru> wrote:> У него (better nested set) есть level, вместо depth. Метод для вывода

Reply all
Reply to author
Forward
0 new messages