PageRenderTime 64ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 1ms

/vendor/plugins/better_nested_set/lib/better_nested_set.rb

https://github.com/craigambrose/playfulbent
Ruby | 1129 lines | 810 code | 130 blank | 189 comment | 179 complexity | 45b584fb7cefd4971e408bd43bee5856 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. module SymetrieCom
  2. module Acts #:nodoc:
  3. module NestedSet #:nodoc:
  4. def self.included(base)
  5. base.extend(ClassMethods)
  6. end
  7. # This module provides an enhanced acts_as_nested_set mixin for ActiveRecord.
  8. # Please see the README for background information, examples, and tips on usage.
  9. module ClassMethods
  10. # Configuration options are:
  11. # * +dependent+ - behaviour for cascading destroy operations (default: :delete_all)
  12. # * +parent_column+ - Column name for the parent/child foreign key (default: +parent_id+).
  13. # * +left_column+ - Column name for the left index (default: +lft+).
  14. # * +right_column+ - Column name for the right index (default: +rgt+). NOTE:
  15. # Don't use +left+ and +right+, since these are reserved database words.
  16. # * +scope+ - Restricts what is to be considered a tree. Given a symbol, it'll attach "_id"
  17. # (if it isn't there already) and use that as the foreign key restriction. It's also possible
  18. # to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
  19. # Example: <tt>acts_as_nested_set :scope => 'tree_id = #{tree_id} AND completed = 0'</tt>
  20. # * +text_column+ - Column name for the title field (optional). Used as default in the
  21. # {your-class}_options_for_select helper method. If empty, will use the first string field
  22. # of your model class.
  23. def acts_as_nested_set(options = {})
  24. extend(SingletonMethods) unless respond_to?(:find_in_nestedset)
  25. options[:scope] = "#{options[:scope]}_id".intern if options[:scope].is_a?(Symbol) && options[:scope].to_s !~ /_id$/
  26. write_inheritable_attribute(:acts_as_nested_set_options,
  27. { :parent_column => (options[:parent_column] || 'parent_id'),
  28. :left_column => (options[:left_column] || 'lft'),
  29. :right_column => (options[:right_column] || 'rgt'),
  30. :scope => (options[:scope] || '1 = 1'),
  31. :text_column => (options[:text_column] || columns.collect{|c| (c.type == :string) ? c.name : nil }.compact.first),
  32. :class => self, # for single-table inheritance
  33. :dependent => (options[:dependent] || :delete_all) # accepts :delete_all and :destroy
  34. } )
  35. class_inheritable_reader :acts_as_nested_set_options
  36. base_set_class.class_inheritable_accessor :acts_as_nested_set_scope_enabled
  37. base_set_class.acts_as_nested_set_scope_enabled = true
  38. if acts_as_nested_set_options[:scope].is_a?(Symbol)
  39. scope_condition_method = %(
  40. def scope_condition
  41. if #{acts_as_nested_set_options[:scope].to_s}.nil?
  42. self.class.use_scope_condition? ? "#{table_name}.#{acts_as_nested_set_options[:scope].to_s} IS NULL" : "(1 = 1)"
  43. else
  44. self.class.use_scope_condition? ? "#{table_name}.#{acts_as_nested_set_options[:scope].to_s} = \#{#{acts_as_nested_set_options[:scope].to_s}}" : "(1 = 1)"
  45. end
  46. end
  47. )
  48. else
  49. scope_condition_method = "def scope_condition(); self.class.use_scope_condition? ? \"#{acts_as_nested_set_options[:scope]}\" : \"(1 = 1)\"; end"
  50. end
  51. # skip recursive destroy calls
  52. attr_accessor :skip_before_destroy
  53. # no bulk assignment
  54. attr_protected acts_as_nested_set_options[:left_column].intern,
  55. acts_as_nested_set_options[:right_column].intern,
  56. acts_as_nested_set_options[:parent_column].intern
  57. # no assignment to structure fields
  58. class_eval <<-EOV
  59. before_create :set_left_right
  60. before_destroy :destroy_descendants
  61. include SymetrieCom::Acts::NestedSet::InstanceMethods
  62. def #{acts_as_nested_set_options[:left_column]}=(x)
  63. raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{acts_as_nested_set_options[:left_column]}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead."
  64. end
  65. def #{acts_as_nested_set_options[:right_column]}=(x)
  66. raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{acts_as_nested_set_options[:right_column]}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead."
  67. end
  68. def #{acts_as_nested_set_options[:parent_column]}=(x)
  69. raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{acts_as_nested_set_options[:parent_column]}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead."
  70. end
  71. #{scope_condition_method}
  72. EOV
  73. end
  74. module SingletonMethods
  75. # Most query methods are wrapped in with_scope to provide further filtering
  76. # find_in_nested_set(what, outer_scope, inner_scope)
  77. # inner scope is user supplied, while outer_scope is the normal query
  78. # this way the user can override most scope attributes, except :conditions
  79. # which is merged; use :reverse => true to sort result in reverse direction
  80. def find_in_nested_set(*args)
  81. what, outer_scope, inner_scope = case args.length
  82. when 3 then [args[0], args[1], args[2]]
  83. when 2 then [args[0], nil, args[1]]
  84. when 1 then [args[0], nil, nil]
  85. else [:all, nil, nil]
  86. end
  87. if inner_scope && outer_scope && inner_scope.delete(:reverse) && outer_scope[:order] == "#{prefixed_left_col_name}"
  88. outer_scope[:order] = "#{prefixed_right_col_name} DESC"
  89. end
  90. acts_as_nested_set_options[:class].with_scope(:find => (outer_scope || {})) do
  91. acts_as_nested_set_options[:class].find(what, inner_scope || {})
  92. end
  93. end
  94. # Count wrapped in with_scope
  95. def count_in_nested_set(*args)
  96. outer_scope, inner_scope = case args.length
  97. when 2 then [args[0], args[1]]
  98. when 1 then [nil, args[0]]
  99. else [nil, nil]
  100. end
  101. acts_as_nested_set_options[:class].with_scope(:find => (outer_scope || {})) do
  102. acts_as_nested_set_options[:class].count(inner_scope || {})
  103. end
  104. end
  105. # Loop through set using block
  106. # pass :nested => false when result is not fully parent-child relational
  107. # for example with filtered result sets
  108. # Set options[:sort_on] to the name of a column you want to sort on (optional).
  109. def recurse_result_set(result, options = {}, &block)
  110. return result unless block_given?
  111. inner_recursion = options.delete(:inner_recursion)
  112. result_set = inner_recursion ? result : result.dup
  113. parent_id = (options.delete(:parent_id) || result_set.first[result_set.first.parent_col_name]) rescue nil
  114. options[:level] ||= 0
  115. options[:nested] = true unless options.key?(:nested)
  116. siblings = options[:nested] ? result_set.select { |s| s.parent_id == parent_id } : result_set
  117. siblings.sort! {|a,b| a.send(options[:sort_on]) <=> b.send(options[:sort_on])} if options[:sort_on]
  118. siblings.each do |sibling|
  119. result_set.delete(sibling)
  120. block.call(sibling, options[:level])
  121. opts = { :parent_id => sibling.id, :level => options[:level] + 1, :inner_recursion => true, :sort_on => options[:sort_on]}
  122. recurse_result_set(result_set, opts, &block) if options[:nested]
  123. end
  124. result_set.each { |orphan| block.call(orphan, options[:level]) } unless inner_recursion
  125. end
  126. # Loop and create a nested array of hashes (with children property)
  127. # pass :nested => false when result is not fully parent-child relational
  128. # for example with filtered result sets
  129. def result_to_array(result, options = {}, &block)
  130. array = []
  131. inner_recursion = options.delete(:inner_recursion)
  132. result_set = inner_recursion ? result : result.dup
  133. parent_id = (options.delete(:parent_id) || result_set.first[result_set.first.parent_col_name]) rescue nil
  134. level = options[:level] || 0
  135. options[:children] ||= 'children'
  136. options[:methods] ||= []
  137. options[:nested] = true unless options.key?(:nested)
  138. options[:symbolize_keys] = true unless options.key?(:symbolize_keys)
  139. if options[:only].blank? && options[:except].blank?
  140. options[:except] = [:left_column, :right_column, :parent_column].inject([]) do |ex, opt|
  141. column = acts_as_nested_set_options[opt].to_sym
  142. ex << column unless ex.include?(column)
  143. ex
  144. end
  145. end
  146. siblings = options[:nested] ? result_set.select { |s| s.parent_id == parent_id } : result_set
  147. siblings.each do |sibling|
  148. result_set.delete(sibling)
  149. node = block_given? ? block.call(sibling, level) : sibling.attributes(:only => options[:only], :except => options[:except])
  150. options[:methods].inject(node) { |enum, m| enum[m.to_s] = sibling.send(m) if sibling.respond_to?(m); enum }
  151. if options[:nested]
  152. opts = options.merge(:parent_id => sibling.id, :level => level + 1, :inner_recursion => true)
  153. childnodes = result_to_array(result_set, opts, &block)
  154. node[ options[:children] ] = childnodes if !childnodes.empty? && node.respond_to?(:[]=)
  155. end
  156. array << (options[:symbolize_keys] && node.respond_to?(:symbolize_keys) ? node.symbolize_keys : node)
  157. end
  158. unless inner_recursion
  159. result_set.each do |orphan|
  160. node = (block_given? ? block.call(orphan, level) : orphan.attributes(:only => options[:only], :except => options[:except]))
  161. options[:methods].inject(node) { |enum, m| enum[m.to_s] = orphan.send(m) if orphan.respond_to?(m); enum }
  162. array << (options[:symbolize_keys] && node.respond_to?(:symbolize_keys) ? node.symbolize_keys : node)
  163. end
  164. end
  165. array
  166. end
  167. # Loop and create an xml structure. The following options are available
  168. # :root sets the root tag, :children sets the siblings tag
  169. # :record sets the node item tag, if given
  170. # see also: result_to_array and ActiveRecord::XmlSerialization
  171. def result_to_xml(result, options = {}, &block)
  172. inner_recursion = options.delete(:inner_recursion)
  173. result_set = inner_recursion ? result : result.dup
  174. parent_id = (options.delete(:parent_id) || result_set.first[result_set.first.parent_col_name]) rescue nil
  175. options[:nested] = true unless options.key?(:nested)
  176. options[:except] ||= []
  177. [:left_column, :right_column, :parent_column].each do |opt|
  178. column = acts_as_nested_set_options[opt].intern
  179. options[:except] << column unless options[:except].include?(column)
  180. end
  181. options[:indent] ||= 2
  182. options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
  183. options[:builder].instruct! unless options.delete(:skip_instruct)
  184. record = options.delete(:record)
  185. root = options.delete(:root) || :nodes
  186. children = options.delete(:children) || :children
  187. attrs = {}
  188. attrs[:xmlns] = options[:namespace] if options[:namespace]
  189. siblings = options[:nested] ? result_set.select { |s| s.parent_id == parent_id } : result_set
  190. options[:builder].tag!(root, attrs) do
  191. siblings.each do |sibling|
  192. result_set.delete(sibling) if options[:nested]
  193. procs = options[:procs] ? options[:procs].dup : []
  194. procs << Proc.new { |opts| block.call(opts, sibling) } if block_given?
  195. if options[:nested]
  196. proc = Proc.new do |opts|
  197. proc_opts = opts.merge(:parent_id => sibling.id, :root => children, :record => record, :inner_recursion => true)
  198. proc_opts[:procs] ||= options[:procs] if options[:procs]
  199. proc_opts[:methods] ||= options[:methods] if options[:methods]
  200. sibling.class.result_to_xml(result_set, proc_opts, &block)
  201. end
  202. procs << proc
  203. end
  204. opts = options.merge(:procs => procs, :skip_instruct => true, :root => record)
  205. sibling.to_xml(opts)
  206. end
  207. end
  208. options[:builder].target!
  209. end
  210. # Loop and create a nested xml representation of nodes with attributes
  211. # pass :nested => false when result is not fully parent-child relational
  212. # for example with filtered result sets
  213. def result_to_attributes_xml(result, options = {}, &block)
  214. inner_recursion = options.delete(:inner_recursion)
  215. result_set = inner_recursion ? result : result.dup
  216. parent_id = (options.delete(:parent_id) || result_set.first[result_set.first.parent_col_name]) rescue nil
  217. level = options[:level] || 0
  218. options[:methods] ||= []
  219. options[:nested] = true unless options.key?(:nested)
  220. options[:dasherize] = true unless options.key?(:dasherize)
  221. if options[:only].blank? && options[:except].blank?
  222. options[:except] = [:left_column, :right_column, :parent_column].inject([]) do |ex, opt|
  223. column = acts_as_nested_set_options[opt].to_sym
  224. ex << column unless ex.include?(column)
  225. ex
  226. end
  227. end
  228. options[:indent] ||= 2
  229. options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
  230. options[:builder].instruct! unless options.delete(:skip_instruct)
  231. parent_attrs = {}
  232. parent_attrs[:xmlns] = options[:namespace] if options[:namespace]
  233. siblings = options[:nested] ? result_set.select { |s| s.parent_id == parent_id } : result_set
  234. siblings.each do |sibling|
  235. result_set.delete(sibling)
  236. node_tag = (options[:record] || sibling[sibling.class.inheritance_column] || 'node').underscore
  237. node_tag = node_tag.dasherize unless options[:dasherize]
  238. attrs = block_given? ? block.call(sibling, level) : sibling.attributes(:only => options[:only], :except => options[:except])
  239. options[:methods].inject(attrs) { |enum, m| enum[m.to_s] = sibling.send(m) if sibling.respond_to?(m); enum }
  240. if options[:nested] && sibling.children?
  241. opts = options.merge(:parent_id => sibling.id, :level => level + 1, :inner_recursion => true, :skip_instruct => true)
  242. options[:builder].tag!(node_tag, attrs) { result_to_attributes_xml(result_set, opts, &block) }
  243. else
  244. options[:builder].tag!(node_tag, attrs)
  245. end
  246. end
  247. unless inner_recursion
  248. result_set.each do |orphan|
  249. node_tag = (options[:record] || orphan[orphan.class.inheritance_column] || 'node').underscore
  250. node_tag = node_tag.dasherize unless options[:dasherize]
  251. attrs = block_given? ? block.call(orphan, level) : orphan.attributes(:only => options[:only], :except => options[:except])
  252. options[:methods].inject(attrs) { |enum, m| enum[m.to_s] = orphan.send(m) if orphan.respond_to?(m); enum }
  253. options[:builder].tag!(node_tag, attrs)
  254. end
  255. end
  256. options[:builder].target!
  257. end
  258. # Returns the single root for the class (or just the first root, if there are several).
  259. # Deprecation note: the original acts_as_nested_set allowed roots to have parent_id = 0,
  260. # so we currently do the same. This silliness will not be tolerated in future versions, however.
  261. def root(scope = {})
  262. find_in_nested_set(:first, { :conditions => "(#{prefixed_parent_col_name} IS NULL OR #{prefixed_parent_col_name} = 0)" }, scope)
  263. end
  264. # Returns the roots and/or virtual roots of all trees. See the explanation of virtual roots in the README.
  265. def roots(scope = {})
  266. find_in_nested_set(:all, { :conditions => "(#{prefixed_parent_col_name} IS NULL OR #{prefixed_parent_col_name} = 0)", :order => "#{prefixed_left_col_name}" }, scope)
  267. end
  268. # Checks the left/right indexes of all records,
  269. # returning the number of records checked. Throws ActiveRecord::ActiveRecordError if it finds a problem.
  270. def check_all
  271. total = 0
  272. transaction do
  273. # if there are virtual roots, only call check_full_tree on the first, because it will check other virtual roots in that tree.
  274. total = roots.inject(0) {|sum, r| sum + (r[r.left_col_name] == 1 ? r.check_full_tree : 0 )}
  275. raise ActiveRecord::ActiveRecordError, "Scope problems or nodes without a valid root" unless acts_as_nested_set_options[:class].count == total
  276. end
  277. return total
  278. end
  279. # Re-calculate the left/right values of all nodes. Can be used to convert ordinary trees into nested sets.
  280. def renumber_all
  281. scopes = []
  282. # only call it once for each scope_condition (if the scope conditions are messed up, this will obviously cause problems)
  283. roots.each do |r|
  284. r.renumber_full_tree unless scopes.include?(r.scope_condition)
  285. scopes << r.scope_condition
  286. end
  287. end
  288. # Returns an SQL fragment that matches _items_ *and* all of their descendants, for use in a WHERE clause.
  289. # You can pass it a single object, a single ID, or an array of objects and/or IDs.
  290. # # if a.lft = 2, a.rgt = 7, b.lft = 12 and b.rgt = 13
  291. # Set.sql_for([a,b]) # returns "((lft BETWEEN 2 AND 7) OR (lft BETWEEN 12 AND 13))"
  292. # Returns "1 != 1" if passed no items. If you need to exclude items, just use "NOT (#{sql_for(items)})".
  293. # Note that if you have multiple trees, it is up to you to apply your scope condition.
  294. def sql_for(items)
  295. items = [items] unless items.is_a?(Array)
  296. # get objects for IDs
  297. items.collect! {|s| s.is_a?(acts_as_nested_set_options[:class]) ? s : acts_as_nested_set_options[:class].find(s)}.uniq
  298. items.reject! {|e| e.new_record?} # exclude unsaved items, since they don't have left/right values yet
  299. return "1 != 1" if items.empty? # PostgreSQL didn't like '0', and SQLite3 didn't like 'FALSE'
  300. items.map! {|e| "(#{prefixed_left_col_name} BETWEEN #{e[left_col_name]} AND #{e[right_col_name]})" }
  301. "(#{items.join(' OR ')})"
  302. end
  303. # Wrap a method with this block to disable the default scope_condition
  304. def without_scope_condition(&block)
  305. if block_given?
  306. disable_scope_condition
  307. yield
  308. enable_scope_condition
  309. end
  310. end
  311. def use_scope_condition?#:nodoc:
  312. base_set_class.acts_as_nested_set_scope_enabled == true
  313. end
  314. def disable_scope_condition#:nodoc:
  315. base_set_class.acts_as_nested_set_scope_enabled = false
  316. end
  317. def enable_scope_condition#:nodoc:
  318. base_set_class.acts_as_nested_set_scope_enabled = true
  319. end
  320. def left_col_name#:nodoc:
  321. acts_as_nested_set_options[:left_column]
  322. end
  323. def prefixed_left_col_name#:nodoc:
  324. "#{table_name}.#{left_col_name}"
  325. end
  326. def right_col_name#:nodoc:
  327. acts_as_nested_set_options[:right_column]
  328. end
  329. def prefixed_right_col_name#:nodoc:
  330. "#{table_name}.#{right_col_name}"
  331. end
  332. def parent_col_name#:nodoc:
  333. acts_as_nested_set_options[:parent_column]
  334. end
  335. def prefixed_parent_col_name#:nodoc:
  336. "#{table_name}.#{parent_col_name}"
  337. end
  338. def base_set_class#:nodoc:
  339. acts_as_nested_set_options[:class] # for single-table inheritance
  340. end
  341. end
  342. end
  343. # This module provides instance methods for an enhanced acts_as_nested_set mixin. Please see the README for background information, examples, and tips on usage.
  344. module InstanceMethods
  345. # convenience methods to make the code more readable
  346. def left_col_name#:nodoc:
  347. self.class.left_col_name
  348. end
  349. def prefixed_left_col_name#:nodoc:
  350. self.class.prefixed_left_col_name
  351. end
  352. def right_col_name#:nodoc:
  353. self.class.right_col_name
  354. end
  355. def prefixed_right_col_name#:nodoc:
  356. self.class.prefixed_right_col_name
  357. end
  358. def parent_col_name#:nodoc:
  359. self.class.parent_col_name
  360. end
  361. def prefixed_parent_col_name#:nodoc:
  362. self.class.prefixed_parent_col_name
  363. end
  364. alias parent_column parent_col_name#:nodoc: Deprecated
  365. def base_set_class#:nodoc:
  366. acts_as_nested_set_options[:class] # for single-table inheritance
  367. end
  368. # This takes care of valid queries when called on a root node
  369. def sibling_condition
  370. self[parent_col_name] ? "#{prefixed_parent_col_name} = #{self[parent_col_name]}" : "(#{prefixed_parent_col_name} IS NULL OR #{prefixed_parent_col_name} = 0)"
  371. end
  372. # On creation, automatically add the new node to the right of all existing nodes in this tree.
  373. def set_left_right # already protected by a transaction within #create
  374. maxright = base_set_class.maximum(right_col_name, :conditions => scope_condition) || 0
  375. self[left_col_name] = maxright+1
  376. self[right_col_name] = maxright+2
  377. end
  378. # On destruction, delete all children and shift the lft/rgt values back to the left so the counts still work.
  379. def destroy_descendants # already protected by a transaction within #destroy
  380. return if self[right_col_name].nil? || self[left_col_name].nil? || self.skip_before_destroy
  381. reloaded = self.reload rescue nil # in case a concurrent move has altered the indexes - rescue if non-existent
  382. return unless reloaded
  383. dif = self[right_col_name] - self[left_col_name] + 1
  384. if acts_as_nested_set_options[:dependent] == :delete_all
  385. base_set_class.delete_all( "#{scope_condition} AND (#{prefixed_left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]})" )
  386. else
  387. set = base_set_class.find(:all, :conditions => "#{scope_condition} AND (#{prefixed_left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]})", :order => "#{prefixed_right_col_name} DESC")
  388. set.each { |child| child.skip_before_destroy = true; remove_descendant(child) }
  389. end
  390. base_set_class.update_all("#{left_col_name} = CASE \
  391. WHEN #{left_col_name} > #{self[right_col_name]} THEN (#{left_col_name} - #{dif}) \
  392. ELSE #{left_col_name} END, \
  393. #{right_col_name} = CASE \
  394. WHEN #{right_col_name} > #{self[right_col_name]} THEN (#{right_col_name} - #{dif} ) \
  395. ELSE #{right_col_name} END",
  396. scope_condition)
  397. end
  398. # By default, records are compared and sorted using the left column.
  399. def <=>(x)
  400. self[left_col_name] <=> x[left_col_name]
  401. end
  402. # Deprecated. Returns true if this is a root node.
  403. def root?
  404. parent_id = self[parent_col_name]
  405. (parent_id == 0 || parent_id.nil?) && self[right_col_name] && self[left_col_name] && (self[right_col_name] > self[left_col_name])
  406. end
  407. # Deprecated. Returns true if this is a child node
  408. def child?
  409. parent_id = self[parent_col_name]
  410. !(parent_id == 0 || parent_id.nil?) && (self[left_col_name] > 1) && (self[right_col_name] > self[left_col_name])
  411. end
  412. # Deprecated. Returns true if we have no idea what this is
  413. def unknown?
  414. !root? && !child?
  415. end
  416. # Returns this record's root ancestor.
  417. def root(scope = {})
  418. # the BETWEEN clause is needed to ensure we get the right virtual root, if using those
  419. self.class.find_in_nested_set(:first, { :conditions => "#{scope_condition} \
  420. AND (#{prefixed_parent_col_name} IS NULL OR #{prefixed_parent_col_name} = 0) AND (#{self[left_col_name]} BETWEEN #{prefixed_left_col_name} AND #{prefixed_right_col_name})" }, scope)
  421. end
  422. # Returns the root or virtual roots of this record's tree (a tree cannot have more than one real root). See the explanation of virtual roots in the README.
  423. def roots(scope = {})
  424. self.class.find_in_nested_set(:all, { :conditions => "#{scope_condition} AND (#{prefixed_parent_col_name} IS NULL OR #{prefixed_parent_col_name} = 0)", :order => "#{prefixed_left_col_name}" }, scope)
  425. end
  426. # Returns this record's parent.
  427. def parent
  428. self.class.find_in_nested_set(self[parent_col_name]) if self[parent_col_name]
  429. end
  430. # Returns an array of all parents, starting with the root.
  431. def ancestors(scope = {})
  432. self_and_ancestors(scope) - [self]
  433. end
  434. # Returns an array of all parents plus self, starting with the root.
  435. def self_and_ancestors(scope = {})
  436. self.class.find_in_nested_set(:all, { :conditions => "#{scope_condition} AND (#{self[left_col_name]} BETWEEN #{prefixed_left_col_name} AND #{prefixed_right_col_name})", :order => "#{prefixed_left_col_name}" }, scope)
  437. end
  438. # Returns all the children of this node's parent, except self.
  439. def siblings(scope = {})
  440. self_and_siblings(scope) - [self]
  441. end
  442. # Returns all siblings to the left of self, in descending order, so the first sibling is the one closest to the left of self
  443. def previous_siblings(scope = {})
  444. self.class.find_in_nested_set(:all,
  445. { :conditions => ["#{scope_condition} AND #{sibling_condition} AND #{self.class.table_name}.id != ? AND #{prefixed_right_col_name} < ?", self.id, self[left_col_name]], :order => "#{prefixed_left_col_name} DESC" }, scope)
  446. end
  447. # Returns all siblings to the right of self, in ascending order, so the first sibling is the one closest to the right of self
  448. def next_siblings(scope = {})
  449. self.class.find_in_nested_set(:all,
  450. { :conditions => ["#{scope_condition} AND #{sibling_condition} AND #{self.class.table_name}.id != ? AND #{prefixed_left_col_name} > ?", self.id, self[right_col_name]], :order => "#{prefixed_left_col_name} ASC"}, scope)
  451. end
  452. # Returns first siblings amongst it's siblings.
  453. def first_sibling(scope = {})
  454. self_and_siblings(scope.merge(:limit => 1, :order => "#{prefixed_left_col_name} ASC")).first
  455. end
  456. def first_sibling?(scope = {})
  457. self == first_sibling(scope)
  458. end
  459. alias :first? :first_sibling?
  460. # Returns last siblings amongst it's siblings.
  461. def last_sibling(scope = {})
  462. self_and_siblings(scope.merge(:limit => 1, :order => "#{prefixed_left_col_name} DESC")).first
  463. end
  464. def last_sibling?(scope = {})
  465. self == last_sibling(scope)
  466. end
  467. alias :last? :last_sibling?
  468. # Returns previous sibling of node or nil if there is none.
  469. def previous_sibling(num = 1, scope = {})
  470. scope[:limit] = num
  471. siblings = previous_siblings(scope)
  472. num == 1 ? siblings.first : siblings
  473. end
  474. alias :higher_item :previous_sibling
  475. # Returns next sibling of node or nil if there is none.
  476. def next_sibling(num = 1, scope = {})
  477. scope[:limit] = num
  478. siblings = next_siblings(scope)
  479. num == 1 ? siblings.first : siblings
  480. end
  481. alias :lower_item :next_sibling
  482. # Returns all the children of this node's parent, including self.
  483. def self_and_siblings(scope = {})
  484. if self[parent_col_name].nil? || self[parent_col_name].zero?
  485. [self]
  486. else
  487. self.class.find_in_nested_set(:all, { :conditions => "#{scope_condition} AND #{sibling_condition}", :order => "#{prefixed_left_col_name}" }, scope)
  488. end
  489. end
  490. # Returns the level of this object in the tree, root level being 0.
  491. def level(scope = {})
  492. return 0 if self[parent_col_name].nil?
  493. self.class.count_in_nested_set({ :conditions => "#{scope_condition} AND (#{self[left_col_name]} BETWEEN #{prefixed_left_col_name} AND #{prefixed_right_col_name})" }, scope) - 1
  494. end
  495. # Returns the number of nested children of this object.
  496. def all_children_count(scope = nil)
  497. return all_children(scope).length if scope.is_a?(Hash)
  498. return (self[right_col_name] - self[left_col_name] - 1)/2
  499. end
  500. # Returns itself and all nested children.
  501. # Pass :exclude => item, or id, or [items or id] to exclude one or more items *and* all of their descendants.
  502. def full_set(scope = {})
  503. if exclude = scope.delete(:exclude)
  504. exclude_str = " AND NOT (#{base_set_class.sql_for(exclude)}) "
  505. elsif new_record? || self[right_col_name] - self[left_col_name] == 1
  506. return [self]
  507. end
  508. self.class.find_in_nested_set(:all, {
  509. :order => "#{prefixed_left_col_name}",
  510. :conditions => "#{scope_condition} #{exclude_str} AND (#{prefixed_left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]})"
  511. }, scope)
  512. end
  513. # Returns the child for the requested id within the scope of its children, otherwise nil
  514. def child_by_id(id, scope = {})
  515. children_by_id(id, scope).first
  516. end
  517. # Returns a child collection for the requested ids within the scope of its children, otherwise empty array
  518. def children_by_id(*args)
  519. scope = args.last.is_a?(Hash) ? args.pop : {}
  520. ids = args.flatten.compact.uniq
  521. self.class.find_in_nested_set(:all, {
  522. :conditions => ["#{scope_condition} AND (#{prefixed_left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]}) AND #{self.class.table_name}.#{self.class.primary_key} IN (?)", ids]
  523. }, scope)
  524. end
  525. # Returns the child for the requested id within the scope of its immediate children, otherwise nil
  526. def direct_child_by_id(id, scope = {})
  527. direct_children_by_id(id, scope).first
  528. end
  529. # Returns a child collection for the requested ids within the scope of its immediate children, otherwise empty array
  530. def direct_children_by_id(*args)
  531. scope = args.last.is_a?(Hash) ? args.pop : {}
  532. ids = args.flatten.compact.uniq
  533. self.class.find_in_nested_set(:all, {
  534. :conditions => ["#{scope_condition} AND #{prefixed_parent_col_name} = #{self.id} AND #{self.class.table_name}.#{self.class.primary_key} IN (?)", ids]
  535. }, scope)
  536. end
  537. # Tests wether self is within scope of parent
  538. def child_of?(parent, scope = {})
  539. if !scope.empty? && parent.respond_to?(:child_by_id)
  540. parent.child_by_id(self.id, scope).is_a?(self.class)
  541. else
  542. parent.respond_to?(left_col_name) && self[left_col_name] > parent[left_col_name] && self[right_col_name] < parent[right_col_name]
  543. end
  544. end
  545. # Tests wether self is within immediate scope of parent
  546. def direct_child_of?(parent, scope = {})
  547. if !scope.empty? && parent.respond_to?(:direct_child_by_id)
  548. parent.direct_child_by_id(self.id, scope).is_a?(self.class)
  549. else
  550. parent.respond_to?(parent_col_name) && self[parent_col_name] == parent.id
  551. end
  552. end
  553. # Returns all children and nested children.
  554. # Pass :exclude => item, or id, or [items or id] to exclude one or more items *and* all of their descendants.
  555. def all_children(scope = {})
  556. full_set(scope) - [self]
  557. end
  558. def children_count(scope= {})
  559. self.class.count_in_nested_set({ :conditions => "#{scope_condition} AND #{prefixed_parent_col_name} = #{self.id}" }, scope)
  560. end
  561. # Returns this record's immediate children.
  562. def children(scope = {})
  563. self.class.find_in_nested_set(:all, { :conditions => "#{scope_condition} AND #{prefixed_parent_col_name} = #{self.id}", :order => "#{prefixed_left_col_name}" }, scope)
  564. end
  565. def children?(scope = {})
  566. children_count(scope) > 0
  567. end
  568. # Deprecated
  569. alias direct_children children
  570. # Returns this record's terminal children (nodes without children).
  571. def leaves(scope = {})
  572. self.class.find_in_nested_set(:all,
  573. { :conditions => "#{scope_condition} AND (#{prefixed_left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]}) AND #{prefixed_left_col_name} + 1 = #{prefixed_right_col_name}", :order => "#{prefixed_left_col_name}" }, scope)
  574. end
  575. # Returns the count of this record's terminal children (nodes without children).
  576. def leaves_count(scope = {})
  577. self.class.count_in_nested_set({ :conditions => "#{scope_condition} AND (#{prefixed_left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]}) AND #{prefixed_left_col_name} + 1 = #{prefixed_right_col_name}" }, scope)
  578. end
  579. # All nodes between two nodes, those nodes included
  580. # in effect all ancestors until the other is reached
  581. def ancestors_and_self_through(other, scope = {})
  582. first, last = [self, other].sort
  583. self.class.find_in_nested_set(:all, { :conditions => "#{scope_condition} AND (#{last[left_col_name]} BETWEEN #{prefixed_left_col_name} AND #{prefixed_right_col_name}) AND #{prefixed_left_col_name} >= #{first[left_col_name]}",
  584. :order => "#{prefixed_left_col_name}" }, scope)
  585. end
  586. # Ancestors until the other is reached - excluding self
  587. def ancestors_through(other, scope = {})
  588. ancestors_and_self_through(other, scope) - [self]
  589. end
  590. # All children until the other is reached - excluding self
  591. def all_children_through(other, scope = {})
  592. full_set_through(other, scope) - [self]
  593. end
  594. # All children until the other is reached - including self
  595. def full_set_through(other, scope = {})
  596. first, last = [self, other].sort
  597. self.class.find_in_nested_set(:all,
  598. { :conditions => "#{scope_condition} AND (#{prefixed_left_col_name} BETWEEN #{first[left_col_name]} AND #{first[right_col_name]}) AND #{prefixed_left_col_name} <= #{last[left_col_name]}", :order => "#{prefixed_left_col_name}" }, scope)
  599. end
  600. # All siblings until the other is reached - including self
  601. def self_and_siblings_through(other, scope = {})
  602. if self[parent_col_name].nil? || self[parent_col_name].zero?
  603. [self]
  604. else
  605. first, last = [self, other].sort
  606. self.class.find_in_nested_set(:all, { :conditions => "#{scope_condition} AND #{sibling_condition} AND (#{prefixed_left_col_name} BETWEEN #{first[left_col_name]} AND #{last[right_col_name]})", :order => "#{prefixed_left_col_name}" }, scope)
  607. end
  608. end
  609. # All siblings until the other is reached - excluding self
  610. def siblings_through(other, scope = {})
  611. self_and_siblings_through(other, scope) - [self]
  612. end
  613. # Checks the left/right indexes of one node and all descendants.
  614. # Throws ActiveRecord::ActiveRecordError if it finds a problem.
  615. def check_subtree
  616. transaction do
  617. self.reload
  618. check # this method is implemented via #check, so that we don't generate lots of unnecessary nested transactions
  619. end
  620. end
  621. # Checks the left/right indexes of the entire tree that this node belongs to,
  622. # returning the number of records checked. Throws ActiveRecord::ActiveRecordError if it finds a problem.
  623. # This method is needed because check_subtree alone cannot find gaps between virtual roots, orphaned nodes or endless loops.
  624. def check_full_tree
  625. total_nodes = 0
  626. transaction do
  627. # virtual roots make this method more complex than it otherwise would be
  628. n = 1
  629. roots.each do |r|
  630. raise ActiveRecord::ActiveRecordError, "Gaps between roots in the tree containing record ##{r.id}" if r[left_col_name] != n
  631. r.check_subtree
  632. n = r[right_col_name] + 1
  633. end
  634. total_nodes = roots.inject(0) {|sum, r| sum + r.all_children_count + 1 }
  635. unless base_set_class.count(:conditions => "#{scope_condition}") == total_nodes
  636. raise ActiveRecord::ActiveRecordError, "Orphaned nodes or endless loops in the tree containing record ##{self.id}"
  637. end
  638. end
  639. return total_nodes
  640. end
  641. # Re-calculate the left/right values of all nodes in this record's tree. Can be used to convert an ordinary tree into a nested set.
  642. def renumber_full_tree
  643. indexes = []
  644. n = 1
  645. transaction do
  646. for r in roots # because we may have virtual roots
  647. n = 1 + r.calc_numbers(n, indexes)
  648. end
  649. for i in indexes
  650. base_set_class.update_all("#{left_col_name} = #{i[:lft]}, #{right_col_name} = #{i[:rgt]}", "#{self.class.primary_key} = #{i[:id]}")
  651. end
  652. end
  653. ## reload?
  654. end
  655. # Deprecated. Adds a child to this object in the tree. If this object hasn't been initialized,
  656. # it gets set up as a root node.
  657. #
  658. # This method exists only for compatibility and will be removed in future versions.
  659. def add_child(child)
  660. transaction do
  661. self.reload; child.reload # for compatibility with old version
  662. # the old version allows records with nil values for lft and rgt
  663. unless self[left_col_name] && self[right_col_name]
  664. if child[left_col_name] || child[right_col_name]
  665. raise ActiveRecord::ActiveRecordError, "If parent lft or rgt are nil, you can't add a child with non-nil lft or rgt"
  666. end
  667. base_set_class.update_all("#{left_col_name} = CASE \
  668. WHEN id = #{self.id} \
  669. THEN 1 \
  670. WHEN id = #{child.id} \
  671. THEN 3 \
  672. ELSE #{left_col_name} END, \
  673. #{right_col_name} = CASE \
  674. WHEN id = #{self.id} \
  675. THEN 2 \
  676. WHEN id = #{child.id} \
  677. THEN 4 \
  678. ELSE #{right_col_name} END",
  679. scope_condition)
  680. self.reload; child.reload
  681. end
  682. unless child[left_col_name] && child[right_col_name]
  683. maxright = base_set_class.maximum(right_col_name, :conditions => scope_condition) || 0
  684. base_set_class.update_all("#{left_col_name} = CASE \
  685. WHEN id = #{child.id} \
  686. THEN #{maxright + 1} \
  687. ELSE #{left_col_name} END, \
  688. #{right_col_name} = CASE \
  689. WHEN id = #{child.id} \
  690. THEN #{maxright + 2} \
  691. ELSE #{right_col_name} END",
  692. scope_condition)
  693. child.reload
  694. end
  695. child.move_to_child_of(self)
  696. # self.reload ## even though move_to calls target.reload, at least one object in the tests was not reloading (near the end of test_common_usage)
  697. end
  698. # self.reload
  699. # child.reload
  700. #
  701. # if child.root?
  702. # raise ActiveRecord::ActiveRecordError, "Adding sub-tree isn\'t currently supported"
  703. # else
  704. # if ( (self[left_col_name] == nil) || (self[right_col_name] == nil) )
  705. # # Looks like we're now the root node! Woo
  706. # self[left_col_name] = 1
  707. # self[right_col_name] = 4
  708. #
  709. # # What do to do about validation?
  710. # return nil unless self.save
  711. #
  712. # child[parent_col_name] = self.id
  713. # child[left_col_name] = 2
  714. # child[right_col_name]= 3
  715. # return child.save
  716. # else
  717. # # OK, we need to add and shift everything else to the right
  718. # child[parent_col_name] = self.id
  719. # right_bound = self[right_col_name]
  720. # child[left_col_name] = right_bound
  721. # child[right_col_name] = right_bound + 1
  722. # self[right_col_name] += 2
  723. # self.class.transaction {
  724. # self.class.update_all( "#{left_col_name} = (#{left_col_name} + 2)", "#{scope_condition} AND #{left_col_name} >= #{right_bound}" )
  725. # self.class.update_all( "#{right_col_name} = (#{right_col_name} + 2)", "#{scope_condition} AND #{right_col_name} >= #{right_bound}" )
  726. # self.save
  727. # child.save
  728. # }
  729. # end
  730. # end
  731. end
  732. # Insert a node at a specific position among the children of target.
  733. def insert_at(target, index = :last, scope = {})
  734. level_nodes = target.children(scope)
  735. current_index = level_nodes.index(self)
  736. last_index = level_nodes.length - 1
  737. as_first = (index == :first)
  738. as_last = (index == :last || (index.is_a?(Fixnum) && index > last_index))
  739. index = 0 if as_first
  740. index = last_index if as_last
  741. if last_index < 0
  742. move_to_child_of(target)
  743. elsif index >= 0 && index <= last_index && level_nodes[index]
  744. if as_last && index != current_index
  745. move_to_right_of(level_nodes[index])
  746. elsif (as_first || index == 0) && index != current_index
  747. move_to_left_of(level_nodes[index])
  748. elsif !current_index.nil? && index > current_index
  749. move_to_right_of(level_nodes[index])
  750. elsif !current_index.nil? && index < current_index
  751. move_to_left_of(level_nodes[index])
  752. elsif current_index.nil?
  753. move_to_left_of(level_nodes[index])
  754. end
  755. end
  756. end
  757. # Move this node to the left of _target_ (you can pass an object or just an id).
  758. # Unsaved changes in either object will be lost. Raises ActiveRecord::ActiveRecordError if it encounters a problem.
  759. def move_to_left_of(target)
  760. self.move_to target, :left
  761. end
  762. # Move this node to the right of _target_ (you can pass an object or just an id).
  763. # Unsaved changes in either object will be lost. Raises ActiveRecord::ActiveRecordError if it encounters a problem.
  764. def move_to_right_of(target)
  765. self.move_to target, :right
  766. end
  767. # Make this node a child of _target_ (you can pass an object or just an id).
  768. # Unsaved changes in either object will be lost. Raises ActiveRecord::ActiveRecordError if it encounters a problem.
  769. def move_to_child_of(target)
  770. self.move_to target, :child
  771. end
  772. # Moves a node to a certain position amongst its siblings.
  773. def move_to_position(index, scope = {})
  774. insert_at(self.parent, index, scope)
  775. end
  776. # Moves a node one up amongst its siblings. Does nothing if it's already
  777. # the first sibling.
  778. def move_lower
  779. next_sib = next_sibling
  780. move_to_right_of(next_sib) if next_sib
  781. end
  782. # Moves a node one down amongst its siblings. Does nothing if it's already
  783. # the last sibling.
  784. def move_higher
  785. prev_sib = previous_sibling
  786. move_to_left_of(prev_sib) if prev_sib
  787. end
  788. # Moves a node one to be the first amongst its siblings. Does nothing if it's already
  789. # the first sibling.
  790. def move_to_top
  791. first_sib = first_sibling
  792. move_to_left_of(first_sib) if first_sib && self != first_sib
  793. end
  794. # Moves a node one to be the last amongst its siblings. Does nothing if it's already
  795. # the last sibling.
  796. def move_to_bottom
  797. last_sib = last_sibling
  798. move_to_right_of(last_sib) if last_sib && self != last_sib
  799. end
  800. # Swaps the position of two sibling nodes preserving a sibling's descendants.
  801. # The current implementation only works amongst siblings.
  802. def swap(target, transact = true)
  803. move_to(target, :swap, transact)
  804. end
  805. # Reorder children according to an array of ids
  806. def reorder_children(*ids)
  807. transaction do
  808. ordered_ids = ids.flatten.uniq
  809. current_children = children({ :conditions => { :id => ordered_ids } })
  810. current_children_ids = current_children.map(&:id)
  811. ordered_ids = ordered_ids & current_children_ids
  812. return [] unless ordered_ids.length > 1 && ordered_ids != current_children_ids
  813. perform_reorder_of_children(ordered_ids, current_children)
  814. end
  815. end
  816. protected
  817. def move_to(target, position, transact = true) #:nodoc:
  818. raise ActiveRecord::ActiveRecordError, "You cannot move a new node" if new_record?
  819. raise ActiveRecord::ActiveRecordError, "You cannot move a node if left or right is nil" unless self[left_col_name] && self[right_col_name]
  820. with_optional_transaction(transact) do
  821. self.reload(:select => "#{left_col_name}, #{right_col_name}, #{parent_col_name}") # the lft/rgt values could be stale (target is reloaded below)
  822. if target.is_a?(base_set_class)
  823. target.reload(:select => "#{left_col_name}, #{right_col_name}, #{parent_col_name}") # could be stale
  824. else
  825. target = self.class.find_in_nested_set(target) # load object if we were given an ID
  826. end
  827. if (target[left_col_name] >= self[left_col_name]) && (target[right_col_name] <= self[right_col_name])
  828. raise ActiveRecord::ActiveRecordError, "Impossible move, target node cannot be inside moved tree."
  829. end
  830. # prevent moves between different trees
  831. if target.scope_condition != scope_condition
  832. raise ActiveRecord::ActiveRecordError, "Scope conditions do not match. Is the target in the same tree?"
  833. end
  834. if position == :swap
  835. unless self.siblings.include?(target)
  836. raise ActiveRecord::ActiveRecordError, "Impossible move, target node should be a sibling."
  837. end
  838. direction = (self[left_col_name] < target[left_col_name]) ? :down : :up
  839. i0 = (direction == :up) ? target[left_col_name] : self[left_col_name]

Large files files are truncated, but you can click here to view the full file