Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to make a Recursive function in rails,that will return all CHILDREN of a parent,and children of all CHILDREN AND SO ON....TO N-LEVELS

I have a method as shown:

def all_pages_and_its_n_level_child

     @parent_pages = Page.where(:parent_id => params[:page_id])
     #this will give me all child of this page
     for page in @parent_pages
          child_exists=Page.where(:parent_id=>page.id)
          #this will give all children of that page
          #*I want to make this loop further so that i can check to N-levels,
          #this is only for two levels*
     end


end  
like image 431
Milind Avatar asked Jul 26 '12 07:07

Milind


3 Answers

On modern RDBs, recursive CTE can be used to handle recursive structure.

class Page < ApplicationRecord; end

Page.find_by_sql(
  "WITH RECURSIVE r AS (
     #{Page.where(id: 2).to_sql}
     UNION ALL
     #{Page.joins('JOIN r').where('r.id = pages.parent_id').to_sql})
   SELECT * FROM r")

#  Page Load (0.7ms)  WITH RECURSIVE r AS (
#     SELECT `pages`.* FROM `pages` WHERE `pages`.`id` = 2
#     UNION ALL
#     SELECT `pages`.* FROM `pages` JOIN r WHERE (r.id = pages.parent_id))                                                              
#   SELECT * FROM r
#    => [#<Page id: 2, parent_id: 1, created_at: "2018-08-21 15:00:43", updated_at: "2018-08-21 15:00:43">, #<Page id: 3, parent_id: 2, created_at: "2018-08-21 15:00:50", updated_at: "2018-08-21 15:00:50">]

As far as I know, mysql, postgres, sqlite3 supports recursive CTE.


EDIT@2020/12/17

On postgresql, you need to have following:

Page.find_by_sql(
  "WITH RECURSIVE r AS (
     #{Page.where(id: 2).to_sql}
   UNION ALL
     #{Page.joins('JOIN r ON r.id = pages.parent_id').to_sql})
   SELECT * FROM r")

(Thanks to @Dan for pointing out)

like image 75
Yuki Inoue Avatar answered Oct 20 '22 11:10

Yuki Inoue


Here is some sample (it is not tested! But it will give you some clue):

def all_children(children_array = [])
  children = Page.where(parent_id: self.id)
  children_array += children.all
  children.each do |child|
    child.all_children(children_array)
  end
  children_array
end

So this is recursion that will try to find all children (nested too) from parent. I know that it is very ugly and unefficient, but I hope ii will give you a clue about finding nested elements.

like image 23
bor1s Avatar answered Oct 20 '22 11:10

bor1s


Here is a proper example. I googled this cause I was too lazy to think about it, but since I found this here I decided to do it properly. Here is a code that you can use with the default way you make Rails associations.

def all_children(children_array = [])
    children_array += self.children
    children.each do |child|
        return child.all_children(children_array)
    end
    return children_array
end

Please note the two uses of there return. If you miss the inside one you will end up with just one level deep tree.

Cheers!

like image 1
Yavor Ivanov Avatar answered Oct 20 '22 11:10

Yavor Ivanov