module SymetrieCom::Acts::NestedSet::ClassMethods::SingletonMethods

Public Instance Methods

check_all() click to toggle source

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_in_nested_set(*args) click to toggle source

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
find_in_nested_set(*args) click to toggle source

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
recurse_result_set(result, options = {}, &block) click to toggle source

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
renumber_all() click to toggle source

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
result_to_array(result, options = {}, &block) click to toggle source

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
result_to_attributes_xml(result, options = {}, &block) click to toggle source

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
result_to_xml(result, options = {}, &block) click to toggle source

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
root(scope = {}) click to toggle source

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
roots(scope = {}) click to toggle source

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
sql_for(items) click to toggle source

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
without_scope_condition() { || ... } click to toggle source

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