Возможно, но мне удалось сделать это только за две поездки в базу данных.
Основная проблема связана с невозможностью установить ограничения для дочерних узлов, что приводит либо к усечению дочерних узлов, либо к потере сирот на последующих страницах.
Пример:
id: 105, Ancestry: Null
id: 117, Ancestry: 105
id: 118, Ancestry: 105/117
id: 119, Ancestry: 105/117/118
LIMIT 0,3 (для примера выше) вернет первые три записи, которые будут отображать все, кроме id: 119. Последующий LIMIT 3,3 вернет id: 119, который не будет корректно отображаться, поскольку его родители отсутствуют.
Одно решение, которое я использовал, использует два запроса:
- Первый возвращает только корневые узлы. Они могут быть отсортированы, и именно этот запрос разбит на страницы.
- Выпускается второй запрос, основанный на первом, который возвращает всех детей родителей, разбитых на страницы. Вы должны иметь возможность сортировать детей по уровням.
В моем случае у меня есть модель Post (которая has_ancestry). Каждый пост может иметь любой уровень ответов. Также объект post имеет счетчик ответов, который является счетчиком кэша для его непосредственных потомков.
В контроллере:
roots = @topic.posts.roots_only.paginate :page => params[:page]
@posts = Post.fetch_children_for_roots(@topic, roots)
В модели Post:
named_scope :roots_only, :conditions => 'posts.ancestry is null'
def self.fetch_children_for_roots(postable, roots)
unless roots.blank?
condition = roots.select{|r|r.replies_count > 0}.collect{|r| "(ancestry like '#{r.id}%')"}.join(' or ')
unless condition.blank?
children = postable.posts.scoped(:from => 'posts FORCE INDEX (index_posts_on_ancestry)', :conditions => condition).all
roots.concat children
end
end
roots
end
Некоторые заметки:
- MySQL перестанет использовать индекс столбца предков, если будет использоваться несколько операторов LIKE. FORCE INDEX заставляет mySQL использовать индекс и предотвращает полное сканирование таблицы
- Операторы LIKE создаются только для узлов с прямыми дочерними элементами, поэтому столбец replies_count пригодится
- Метод класса добавляет дочерних элементов в root, который представляет собой WillPaginate :: Collection
Наконец, вы можете управлять ими:
=will_paginate @posts
-Post.arrange_nodes(@posts).each do |post, replies|
=do stuff here
Ключевой метод здесь - range_nodes , который смешивается с плагином предков и с вашей моделью. Это в основном берет отсортированный массив узлов и возвращает отсортированный и иерархический хэш.
Я ценю, что этот метод напрямую не отвечает на ваш вопрос, но я надеюсь, что тот же метод, с настройками, может быть применен для вашего случая.
Вероятно, есть более элегантный способ сделать это, но в целом я доволен решением (пока не появится лучший).