Module: SymetrieCom::Acts::NestedSet::ClassMethods::SingletonMethods

Defined in:
lib/better_nested_set.rb

Instance Method Summary collapse

Instance Method Details

#base_set_classObject

:nodoc:



382
383
384
# File 'lib/better_nested_set.rb', line 382

def base_set_class#:nodoc:
  acts_as_nested_set_options[:class] # for single-table inheritance
end

#check_allObject

Checks the left/right indexes of all records, returning the number of records checked. Throws ActiveRecord::ActiveRecordError if it finds a problem.



306
307
308
309
310
311
312
313
314
# File 'lib/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) ⇒ Object

Count wrapped in with_scope



108
109
110
111
112
113
114
115
116
117
# File 'lib/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

#disable_scope_conditionObject

:nodoc:



356
357
358
# File 'lib/better_nested_set.rb', line 356

def disable_scope_condition#:nodoc:
  base_set_class.acts_as_nested_set_scope_enabled = false
end

#enable_scope_conditionObject

:nodoc:



360
361
362
# File 'lib/better_nested_set.rb', line 360

def enable_scope_condition#:nodoc:
  base_set_class.acts_as_nested_set_scope_enabled = true
end

#find_in_nested_set(*args) ⇒ Object

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



92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/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

#left_col_nameObject

:nodoc:



364
365
366
# File 'lib/better_nested_set.rb', line 364

def left_col_name#:nodoc:
  acts_as_nested_set_options[:left_column]
end

#parent_col_nameObject

:nodoc:



376
377
378
# File 'lib/better_nested_set.rb', line 376

def parent_col_name#:nodoc:
  acts_as_nested_set_options[:parent_column]
end

#prefixed_left_col_nameObject

:nodoc:



367
368
369
# File 'lib/better_nested_set.rb', line 367

def prefixed_left_col_name#:nodoc:
  "#{table_name}.#{left_col_name}"
end

#prefixed_parent_col_nameObject

:nodoc:



379
380
381
# File 'lib/better_nested_set.rb', line 379

def prefixed_parent_col_name#:nodoc:
  "#{table_name}.#{parent_col_name}"
end

#prefixed_right_col_nameObject

:nodoc:



373
374
375
# File 'lib/better_nested_set.rb', line 373

def prefixed_right_col_name#:nodoc:
  "#{table_name}.#{right_col_name}"
end

#recurse_result_set(result, options = {}, &block) ⇒ Object

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).



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/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_allObject

Re-calculate the left/right values of all nodes. Can be used to convert ordinary trees into nested sets.



317
318
319
320
321
322
323
324
# File 'lib/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) ⇒ Object

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



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/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) ⇒ Object

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



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'lib/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) ⇒ Object

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



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/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

#right_col_nameObject

:nodoc:



370
371
372
# File 'lib/better_nested_set.rb', line 370

def right_col_name#:nodoc:
  acts_as_nested_set_options[:right_column]
end

#root(scope = {}) ⇒ Object

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.



295
296
297
# File 'lib/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 = {}) ⇒ Object

Returns the roots and/or virtual roots of all trees. See the explanation of virtual roots in the README.



300
301
302
# File 'lib/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) ⇒ Object

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.



332
333
334
335
336
337
338
339
340
341
# File 'lib/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

#use_scope_condition?Boolean

:nodoc:

Returns:

  • (Boolean)


352
353
354
# File 'lib/better_nested_set.rb', line 352

def use_scope_condition?#:nodoc:
  base_set_class.acts_as_nested_set_scope_enabled == true
end

#without_scope_condition(&block) ⇒ Object

Wrap a method with this block to disable the default scope_condition



344
345
346
347
348
349
350
# File 'lib/better_nested_set.rb', line 344

def without_scope_condition(&block)
  if block_given?
    disable_scope_condition
    yield
    enable_scope_condition
  end
end