Этот код является ярким примером N + 1 запроса , который снизит производительность вашего приложения. Каждая итерация вызывает Comment.where(parent_id: id)
, что создает дополнительный запрос к базе данных.
Вы должны начать с установки надлежащих ассоциаций, чтобы вы могли просто вызвать #children
для экземпляра комментария, чтобы получить вложенные комментарии, вместо того, чтобы делать Comment.where(parent_id: id)
:
class Comment
belongs_to :parent, class_name: 'Comment', optional: true
has_many :children, class_name: 'Comment', foreign_key: :parent_id
end
Это позволит вам использовать .includes
или .eager_load
для извлечения дочерних элементов в одном запросе:
<% render partial: 'comment', collection: Comment.where(parent_id: nil).includes(:children) %>
Однако это будет работать только на один уровень. Rails на самом деле не поддерживает рекурсивную загрузку ассоциаций, но вы можете подделать ее:
class Comment < ApplicationRecord
belongs_to :parent, class_name: 'Comment', optional: true
has_many :children, class_name: 'Comment', foreign_key: :parent_id
def self.deep_includes(levels = 5)
hash = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
keys = Array.new(levels, :children)
keys.inject(hash) {|h, k| h[k] }[:children] = :children
self.includes(hash)
end
end
Это действительно так:
Comment.includes({:children=>{:children=>{:children=>{:children=>{:children=>{:children=>:children}}}}}})
, который создает колоссальный SQL-запрос, объединяющий каждый уровень.
<% render partial: 'comment', collection: Comment.where(parent_id: nil).deep_includes %>
С этим позаботиться о создании рекурсивного частичного на самом деле намного проще, чем вы думаете:
# app/views/comments/_comment.html.erb
<div class="comment">
<p><%= comment.data %></p>
<% if comment.children.any? %>
<div class="children">
<%= render partial: "comment", collection: comment.children %>
</div>
<% end %>
</div>
Вам действительно не нужен вспомогательный метод и дополнительный уровень сложности.