循环遍历嵌套对象的Ruby方式

时间:2015-06-05 10:57:23

标签: ruby

如何重写以下代码以更加Ruby-wayish?我正在考虑inject,但无法弄清楚如何做到这一点。

  def nested_page_path(page)
    path = "/#{page.slug}"
    while page.parent_id do
      path.prepend "/#{page.parent.slug}"
      page = page.parent
    end
    path
  end

输入是AR对象,具有0-5个连续父项。输出类似于'/pages/services/law'

3 个答案:

答案 0 :(得分:2)

如果您确定父育中没有周期,您可以递归地执行此操作,即。即具有调用自身的功能。 5级嵌套应该做得很好,数以千计可能会出现麻烦。

def nested_page_path(page)
  return "" if page.nil? # Or whatever that is root
  "#{nested_page_path(page.parent)}/#{page.slug}"
end

但请记住,上面的方法以及您的方法将在单独的查询中获取每个对象。当你已经获取它们时它很好,但如果没有,你会遇到一些N + 1查询问题。

一个简单的解决方法是缓存。您可以在before_save上重建此对象及其后代的嵌套路径:这是每次写入时的一些重要开销。有一个更好的方法。

通过使用nested sets,您可以在一个查询中获取对象的层次结构分支。像这样:

page.self_and_ancestors.pluck(:slug).join('/')
#             ^
#   Nested sets' goodness

该查询的作用基本上是“获取由左边界定的页面,其范围包含我自己的”。我在我的示例中使用awesome_nested_set

SELECT "pages"."slug" FROM "pages"
WHERE ("pages"."lft" <= 42) AND ("pages"."rgt" >= 88)
ORDER BY "pages"."lft"

答案 1 :(得分:1)

在不了解您的对象结构的情况下,这很困难。但像这样的递归应该这样做:

def nested_page_path(page)
  path = "/#{page.slug}"
  return path unless page.parent_id
  path.prepend "#{nested_page_path(page.parent)}/"
end

答案 2 :(得分:1)

不确定inject是一个简单的答案,因为它在Enumerable上运行,并且您没有明显的可枚举开头。

我建议这样的事情(与你的解决方案不同)

def nested_page_path(page)
  pages = [page]
  pages << pages.last.parent while pages.last.parent
  '/' + pages.reverse.map(&:slug).join('/')
end

那里有减少重复的余地,但这或多或少都是我的目标。