изменить
подождите минуту;на самом деле есть решение: использовать левое внешнее объединение.
Page
.joins("LEFT OUTER JOIN page_relations ON pages.id = page_relations.child_id")
.where("page_relations.child_id IS NULL")
это, например, найдет все страницы соединительных линий (просто объединяет все страницы с их "родительскими" ассоциациями и выбирает страницы безассоциация).
я не знаю, какое влияние это окажет на производительность;я думаю, что это должно быть разумно быстро не слишком медленно, но не для использования в качестве обычной задачи.Возможно, было бы проще использовать таблицу Ареля (но, к сожалению, я недостаточно знаком с ними).
Я не знаю, существует ли здесь какой-либо другой способ, кроме итерации методом грубой силы (и если вы найдете один, я бы хотел это знать).
Мой совет - записывать эти свойства в качестве логических атрибутов страниц и использовать обратные вызовы для поддержания их в согласованном состоянии.
Идея состоит в том, чтобы добавить before_save
, after_save
, before_destroy
... обратных вызовов на модель Page
, чтобы каждый раз, когда мы манипулировали страницей, мы проверяли, является ли она "стволом" или "лист "(проверяя, есть ли у него родители или дети через parents.exist?
и children.exists?
);затем мы изменяем логические атрибуты trunk
и leaf
на этой странице (или на соответствующей странице в случае уничтожения).
Это каким-то образом замедлит производительность при вставке / обновлении / удалении, но позволит очень быстро извлекать стволы и листья с помощью простого оператора where( trunk: true )
.Вам, вероятно, придется использовать default_scope includes( :parents, :children )
на модели Page
(или, по крайней мере, использовать includes
много), чтобы предотвратить взрыв количества попаданий в БД.
Возможно, возможно использовать ту же стратегию, но для размещения обратных вызовов на модели PageRelation
;может даже быть возможно использовать Observer
.Все зависит от ваших конкретных потребностей и стиля кодирования, и его развитие будет очень долгим.