forked from stefankroes/ancestry
-
Notifications
You must be signed in to change notification settings - Fork 3
Creating a selectbox for a form using ancestry
doabit edited this page Jan 10, 2013
·
12 revisions
Assuming we have a model Category
- Add a new string field: names_depth_cache
- Add a before_save to your ancestry model:
before_save :cache_ancestry
def cache_ancestry
self.names_depth_cache = path.map(&:name).join('/')
end
- In the controller:
@categories = Category.order(:names_depth_cache).map { |c| ["-" * c.depth + c.name,c.id] }
- In the form:
<%= f.select :parent_id, @categories%>
- In the form:
<%= f.select :parent_id, @categories%>
- In the controller:
Category.all.each { |c| c.ancestry = c.ancestry.to_s + (c.ancestry != nil ? "/" : '') + c.id.to_s
}.sort {|x,y| x.ancestry <=> y.ancestry
}.map{ |c| ["-" * (c.depth - 1) + c.name,c.id]
}.unshift(["-- none --", nil])
The main problem with some of the options above (at least option2) is that it doesn't keep the order of the category. The second problem I saw was multiple of SQL queries. You can bypass this by doing the following:
- In the form:
<%= f.select :parent_id, @categories%>
- In the controller:
@categories = ancestry_options(Category.scoped.arrange(:order => 'name')) {|i| "#{'-' * i.depth} #{i.name}" }
def ancestry_options(items, &block)
return ancestry_options(items){ |i| "#{'-' * i.depth} #{i.name}" } unless block_given?
result = []
items.map do |item, sub_items|
result << [yield(item), item.id]
#this is a recursive call:
result += ancestry_options(sub_items, &block)
end
result
end
The main benefit of the above's implementation is that it's a single SQL call (for efficiency) and the order remains as it should.
You can use arrange_as_array to build the list of options easily.
And then define an extra method for the select display name. You can even enforce that the current page's subtree is not displayed, if you want to present only the valid parents:
- In the form:
<%= f.collection_select :parent_id, @categories, :id, :name_for_selects, :include_blank => true %>
- In the model:
class Category < ActiveRecord::Base
has_ancestry
def self.arrange_as_array(options={}, hash=nil)
hash ||= arrange(options)
arr = []
hash.each do |node, children|
arr << node
arr += arrange_as_array(options, children) unless children.nil?
end
arr
end
def name_for_selects
"#{'-' * depth} #{name}"
end
def possible_parents
parents = Category.arrange_as_array(:order => 'name')
return new_record? ? parents : parents - subtree
end
end
- In the controller:
@categories = Category.arrange_as_array(:order => 'name', @category.possible_parents)