module SymetrieCom::Acts::NestedSet::ClassMethods::SingletonMethods
Public Instance Methods
Checks the left/right indexes of all records, returning the number of records checked. Throws ActiveRecord::ActiveRecordError if it finds a problem.
# File lib/symetrie_com/acts_as_better_nested_set.rb, line 306 def check_all total = 0 transaction do # if there are virtual roots, only call check_full_tree on the first, because it will check other virtual roots in that tree. total = roots.inject(0) {|sum, r| sum + (r[r.left_col_name] == 1 ? r.check_full_tree : 0 )} raise ActiveRecord::ActiveRecordError, "Scope problems or nodes without a valid root" unless acts_as_nested_set_options[:class].count == total end return total end
Count wrapped in with_scope
# File lib/symetrie_com/acts_as_better_nested_set.rb, line 108 def count_in_nested_set(*args) outer_scope, inner_scope = case args.length when 2 then [args[0], args[1]] when 1 then [nil, args[0]] else [nil, nil] end acts_as_nested_set_options[:class].with_scope(:find => (outer_scope || {})) do acts_as_nested_set_options[:class].count(inner_scope || {}) end end
Most query methods are wrapped in with_scope to provide further filtering find_in_nested_set
(what, outer_scope, inner_scope) inner scope is user supplied, while outer_scope is the normal query this way the user can override most scope attributes, except :conditions which is merged; use :reverse => true to sort result in reverse direction
# File lib/symetrie_com/acts_as_better_nested_set.rb, line 92 def find_in_nested_set(*args) what, outer_scope, inner_scope = case args.length when 3 then [args[0], args[1], args[2]] when 2 then [args[0], nil, args[1]] when 1 then [args[0], nil, nil] else [:all, nil, nil] end if inner_scope && outer_scope && inner_scope.delete(:reverse) && outer_scope[:order] == "#{prefixed_left_col_name}" outer_scope[:order] = "#{prefixed_right_col_name} DESC" end acts_as_nested_set_options[:class].with_scope(:find => (outer_scope || {})) do acts_as_nested_set_options[:class].find(what, inner_scope || {}) end end
Loop through set using block pass :nested => false when result is not fully parent-child relational for example with filtered result sets Set options to the name of a column you want to sort on (optional).
# File lib/symetrie_com/acts_as_better_nested_set.rb, line 123 def recurse_result_set(result, options = {}, &block) return result unless block_given? inner_recursion = options.delete(:inner_recursion) result_set = inner_recursion ? result : result.dup parent_id = (options.delete(:parent_id) || result_set.first[result_set.first.parent_col_name]) rescue nil options[:level] ||= 0 options[:nested] = true unless options.key?(:nested) siblings = options[:nested] ? result_set.select { |s| s.parent_id == parent_id } : result_set siblings.sort! {|a,b| a.send(options[:sort_on]) <=> b.send(options[:sort_on])} if options[:sort_on] siblings.each do |sibling| result_set.delete(sibling) block.call(sibling, options[:level]) opts = { :parent_id => sibling.id, :level => options[:level] + 1, :inner_recursion => true, :sort_on => options[:sort_on]} recurse_result_set(result_set, opts, &block) if options[:nested] end result_set.each { |orphan| block.call(orphan, options[:level]) } unless inner_recursion end
Re-calculate the left/right values of all nodes. Can be used to convert ordinary trees into nested sets.
# File lib/symetrie_com/acts_as_better_nested_set.rb, line 317 def renumber_all scopes = [] # only call it once for each scope_condition (if the scope conditions are messed up, this will obviously cause problems) roots.each do |r| r.renumber_full_tree unless scopes.include?(r.scope_condition) scopes << r.scope_condition end end
Loop and create a nested array of hashes (with children property) pass :nested => false when result is not fully parent-child relational for example with filtered result sets
# File lib/symetrie_com/acts_as_better_nested_set.rb, line 146 def result_to_array(result, options = {}, &block) array = [] inner_recursion = options.delete(:inner_recursion) result_set = inner_recursion ? result : result.dup parent_id = (options.delete(:parent_id) || result_set.first[result_set.first.parent_col_name]) rescue nil level = options[:level] || 0 options[:children] ||= 'children' options[:methods] ||= [] options[:nested] = true unless options.key?(:nested) options[:symbolize_keys] = true unless options.key?(:symbolize_keys) if options[:only].blank? && options[:except].blank? options[:except] = [:left_column, :right_column, :parent_column].inject([]) do |ex, opt| column = acts_as_nested_set_options[opt].to_sym ex << column unless ex.include?(column) ex end end siblings = options[:nested] ? result_set.select { |s| s.parent_id == parent_id } : result_set siblings.each do |sibling| result_set.delete(sibling) node = block_given? ? block.call(sibling, level) : sibling.attributes(:only => options[:only], :except => options[:except]) options[:methods].inject(node) { |enum, m| enum[m.to_s] = sibling.send(m) if sibling.respond_to?(m); enum } if options[:nested] opts = options.merge(:parent_id => sibling.id, :level => level + 1, :inner_recursion => true) childnodes = result_to_array(result_set, opts, &block) node[ options[:children] ] = childnodes if !childnodes.empty? && node.respond_to?(:[]=) end array << (options[:symbolize_keys] && node.respond_to?(:symbolize_keys) ? node.symbolize_keys : node) end unless inner_recursion result_set.each do |orphan| node = (block_given? ? block.call(orphan, level) : orphan.attributes(:only => options[:only], :except => options[:except])) options[:methods].inject(node) { |enum, m| enum[m.to_s] = orphan.send(m) if orphan.respond_to?(m); enum } array << (options[:symbolize_keys] && node.respond_to?(:symbolize_keys) ? node.symbolize_keys : node) end end array end
Loop and create a nested xml representation of nodes with attributes pass :nested => false when result is not fully parent-child relational for example with filtered result sets
# File lib/symetrie_com/acts_as_better_nested_set.rb, line 241 def result_to_attributes_xml(result, options = {}, &block) inner_recursion = options.delete(:inner_recursion) result_set = inner_recursion ? result : result.dup parent_id = (options.delete(:parent_id) || result_set.first[result_set.first.parent_col_name]) rescue nil level = options[:level] || 0 options[:methods] ||= [] options[:nested] = true unless options.key?(:nested) options[:dasherize] = true unless options.key?(:dasherize) if options[:only].blank? && options[:except].blank? options[:except] = [:left_column, :right_column, :parent_column].inject([]) do |ex, opt| column = acts_as_nested_set_options[opt].to_sym ex << column unless ex.include?(column) ex end end options[:indent] ||= 2 options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent]) options[:builder].instruct! unless options.delete(:skip_instruct) parent_attrs = {} parent_attrs[:xmlns] = options[:namespace] if options[:namespace] siblings = options[:nested] ? result_set.select { |s| s.parent_id == parent_id } : result_set siblings.each do |sibling| result_set.delete(sibling) node_tag = (options[:record] || sibling[sibling.class.inheritance_column] || 'node').underscore node_tag = node_tag.dasherize unless options[:dasherize] attrs = block_given? ? block.call(sibling, level) : sibling.attributes(:only => options[:only], :except => options[:except]) options[:methods].inject(attrs) { |enum, m| enum[m.to_s] = sibling.send(m) if sibling.respond_to?(m); enum } if options[:nested] && sibling.children? opts = options.merge(:parent_id => sibling.id, :level => level + 1, :inner_recursion => true, :skip_instruct => true) options[:builder].tag!(node_tag, attrs) { result_to_attributes_xml(result_set, opts, &block) } else options[:builder].tag!(node_tag, attrs) end end unless inner_recursion result_set.each do |orphan| node_tag = (options[:record] || orphan[orphan.class.inheritance_column] || 'node').underscore node_tag = node_tag.dasherize unless options[:dasherize] attrs = block_given? ? block.call(orphan, level) : orphan.attributes(:only => options[:only], :except => options[:except]) options[:methods].inject(attrs) { |enum, m| enum[m.to_s] = orphan.send(m) if orphan.respond_to?(m); enum } options[:builder].tag!(node_tag, attrs) end end options[:builder].target! end
Loop and create an xml structure. The following options are available :root sets the root tag, :children sets the siblings tag :record sets the node item tag, if given see also: result_to_array
and ActiveRecord::XmlSerialization
# File lib/symetrie_com/acts_as_better_nested_set.rb, line 192 def result_to_xml(result, options = {}, &block) inner_recursion = options.delete(:inner_recursion) result_set = inner_recursion ? result : result.dup parent_id = (options.delete(:parent_id) || result_set.first[result_set.first.parent_col_name]) rescue nil options[:nested] = true unless options.key?(:nested) options[:except] ||= [] [:left_column, :right_column, :parent_column].each do |opt| column = acts_as_nested_set_options[opt].intern options[:except] << column unless options[:except].include?(column) end options[:indent] ||= 2 options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent]) options[:builder].instruct! unless options.delete(:skip_instruct) record = options.delete(:record) root = options.delete(:root) || :nodes children = options.delete(:children) || :children attrs = {} attrs[:xmlns] = options[:namespace] if options[:namespace] siblings = options[:nested] ? result_set.select { |s| s.parent_id == parent_id } : result_set options[:builder].tag!(root, attrs) do siblings.each do |sibling| result_set.delete(sibling) if options[:nested] procs = options[:procs] ? options[:procs].dup : [] procs << Proc.new { |opts| block.call(opts, sibling) } if block_given? if options[:nested] proc = Proc.new do |opts| proc_opts = opts.merge(:parent_id => sibling.id, :root => children, :record => record, :inner_recursion => true) proc_opts[:procs] ||= options[:procs] if options[:procs] proc_opts[:methods] ||= options[:methods] if options[:methods] sibling.class.result_to_xml(result_set, proc_opts, &block) end procs << proc end opts = options.merge(:procs => procs, :skip_instruct => true, :root => record) sibling.to_xml(opts) end end options[:builder].target! end
Returns the single root for the class (or just the first root, if there are several). Deprecation note: the original acts_as_nested_set allowed roots to have parent_id = 0, so we currently do the same. This silliness will not be tolerated in future versions, however.
# File lib/symetrie_com/acts_as_better_nested_set.rb, line 295 def root(scope = {}) find_in_nested_set(:first, { :conditions => "(#{prefixed_parent_col_name} IS NULL OR #{prefixed_parent_col_name} = 0)" }, scope) end
Returns the roots and/or virtual roots of all trees. See the explanation of virtual roots in the README.
# File lib/symetrie_com/acts_as_better_nested_set.rb, line 300 def roots(scope = {}) find_in_nested_set(:all, { :conditions => "(#{prefixed_parent_col_name} IS NULL OR #{prefixed_parent_col_name} = 0)", :order => "#{prefixed_left_col_name}" }, scope) end
Returns an SQL fragment that matches items and all of their descendants, for use in a WHERE clause. You can pass it a single object, a single ID, or an array of objects and/or IDs.
# if a.lft = 2, a.rgt = 7, b.lft = 12 and b.rgt = 13 Set.sql_for([a,b]) # returns "((lft BETWEEN 2 AND 7) OR (lft BETWEEN 12 AND 13))"
Returns “1 != 1” if passed no items. If you need to exclude items, just use “NOT (#{sql_for(items)})”. Note that if you have multiple trees, it is up to you to apply your scope condition.
# File lib/symetrie_com/acts_as_better_nested_set.rb, line 332 def sql_for(items) items = [items] unless items.is_a?(Array) # get objects for IDs items.collect! {|s| s.is_a?(acts_as_nested_set_options[:class]) ? s : acts_as_nested_set_options[:class].find(s)}.uniq items.reject! {|e| e.new_record?} # exclude unsaved items, since they don't have left/right values yet return "1 != 1" if items.empty? # PostgreSQL didn't like '0', and SQLite3 didn't like 'FALSE' items.map! {|e| "(#{prefixed_left_col_name} BETWEEN #{e[left_col_name]} AND #{e[right_col_name]})" } "(#{items.join(' OR ')})" end
Wrap a method with this block to disable the default scope_condition
# File lib/symetrie_com/acts_as_better_nested_set.rb, line 344 def without_scope_condition(&block) if block_given? disable_scope_condition yield enable_scope_condition end end