Hi i would use some gem to do this
i.e.
Question
I have a hierarchical structure of pages when one page can be a parent of another. Pages are stored in a DB table with the following structure:
Now I need to output them is a hierarchical list like:
Also there is no limit to a number of levels.
What would be the best approach to achieve that in RoR? Thanks in advance.
Solution 2
OTHER TIPS
Here are the best practices to handle - https://blog.francium.tech/best-practices-for-handling-hierarchical-data-structure-in-ruby-on-rails-b5830c5ea64d
A Simple table
Table Emp
id: Integer
name: String
parent_id: Integer
Associations
app/models/emp.rb
class Emp < ApplicationRecord
has_many :subs, class_name: 'Emp', foreign_key: :parent_id
belongs_to :superior, class_name: 'Emp', foreign_key: :parent_id
end
Scope Definition
class Emp < ApplicationRecord
----
----
scope :roots, -> { where(parent_id: nil) }
end
Fetching data
def tree_data
output = []
Emp.roots.each do |emp|
output << data(emp)
end
output.to_json
end
def data(employee)
subordinates = []
unless employee.subs.blank?
employee.subs.each do |emp|
subordinates << data(emp)
end
end
{name: employee.name, subordinates: subordinates}
end
Eager Loading
def tree_data
output = []
Emp.roots.includes(subs: {subs: {subs: subs}}}.each do |emp|
output << data(emp)
end
output.to_json
end
Hypothetical code, not verified.
class Page < ActiveRecord::Base
has_many :children, class_name: 'Page', foreign_key: 'parent_id'
belongs_to :parent, class_name: 'Page'
end
# Helper
def tree_list(collection)
content_tag :ul do
collection.each do |item|
if item.children.blank?
content_tag :li do
item.name
end
else
tree_list(item)
end
end
end
end
# View
tree_list(Page.all)
The basic idea is the tree_list
method will make a recursive list until there is no children for a certain page.
As another user suggested, you could use ancestry
gem but it will require changes to your database. If you can't do that you should create a directed acyclic graph and set depth for each node.
class Page < ActiveRecord::Base
attr_accessor :depth
def children
@children ||= []
end
end
# Loading required pages in single query, add predicate if needed
pages = Page.includes(:parent).all
# Setting up a hash table to find pages in constant time
page_index = pages.reduce({}){|acc, page| acc[page.id] = page}
# Setting up children
pages.each do |page|
if page.parent_id
page_index[page.parent_id].children << page
end
end
# Basic depth-first search routine
def dfs(page, visited, depth)
page.depth = depth
visited[page.id] = true
page.children.select{|p| visited[p] == nil}.each{|p| dfs(p, visited, depth + 1)}
end
# Performing depth-first search for each connected component
visited = {}
pages.each do |page|
if visited[page.id] == nil
dfs(page, visited, 0)
end
end
So, now each page has a depth attribute and you can print the list however you like.