awesome_nested_set-3.8.0/0000755000004100000410000000000014741753242015440 5ustar www-datawww-dataawesome_nested_set-3.8.0/lib/0000755000004100000410000000000014741753242016206 5ustar www-datawww-dataawesome_nested_set-3.8.0/lib/awesome_nested_set.rb0000644000004100000410000000054614741753242022415 0ustar www-datawww-datarequire 'active_support/lazy_load_hooks' require 'awesome_nested_set/awesome_nested_set' ActiveSupport.on_load(:active_record) do ActiveRecord::Base.send :extend, CollectiveIdea::Acts::NestedSet end ActiveSupport.on_load(:action_view) do require 'awesome_nested_set/helper' ActionView::Base.send :include, CollectiveIdea::Acts::NestedSet::Helper end awesome_nested_set-3.8.0/lib/awesome_nested_set/0000755000004100000410000000000014741753242022063 5ustar www-datawww-dataawesome_nested_set-3.8.0/lib/awesome_nested_set/columns.rb0000644000004100000410000000476014741753242024077 0ustar www-datawww-data# Mixed into both classes and instances to provide easy access to the column names module CollectiveIdea #:nodoc: module Acts #:nodoc: module NestedSet #:nodoc: module Columns def left_column_name acts_as_nested_set_options[:left_column] end def right_column_name acts_as_nested_set_options[:right_column] end def depth_column_name acts_as_nested_set_options[:depth_column] end def parent_column_name acts_as_nested_set_options[:parent_column] end def primary_column_name acts_as_nested_set_options[:primary_column] end def order_column_name acts_as_nested_set_options[:order_column] || left_column_name end def scope_column_names Array(acts_as_nested_set_options[:scope]) end def counter_cache_column_name acts_as_nested_set_options[:counter_cache] end def quoted_left_column_name model_connection.quote_column_name(left_column_name) end def quoted_right_column_name model_connection.quote_column_name(right_column_name) end def quoted_depth_column_name model_connection.quote_column_name(depth_column_name) end def quoted_primary_column_name model_connection.quote_column_name(primary_column_name) end def quoted_parent_column_name model_connection.quote_column_name(parent_column_name) end def quoted_scope_column_names scope_column_names.collect {|column_name| connection.quote_column_name(column_name) } end def quoted_order_column_name model_connection.quote_column_name(order_column_name) end def quoted_primary_key_column_full_name "#{quoted_table_name}.#{quoted_primary_column_name}" end def quoted_order_column_full_name "#{quoted_table_name}.#{quoted_order_column_name}" end def quoted_left_column_full_name "#{quoted_table_name}.#{quoted_left_column_name}" end def quoted_right_column_full_name "#{quoted_table_name}.#{quoted_right_column_name}" end def quoted_parent_column_full_name "#{quoted_table_name}.#{quoted_parent_column_name}" end def model_connection self.is_a?(Class) ? self.connection : self.class.connection end end end end end awesome_nested_set-3.8.0/lib/awesome_nested_set/awesome_nested_set.rb0000644000004100000410000001404114741753242026265 0ustar www-datawww-datarequire 'awesome_nested_set/columns' require 'awesome_nested_set/model' module CollectiveIdea #:nodoc: module Acts #:nodoc: module NestedSet #:nodoc: # This provides Nested Set functionality. Nested Set is a smart way to implement # an _ordered_ tree, with the added feature that you can select the children and all of their # descendants with a single query. The drawback is that insertion or move need some complex # sql queries. But everything is done here by this module! # # Nested sets are appropriate each time you want either an orderd tree (menus, # commercial categories) or an efficient way of querying big trees (threaded posts). # # == API # # Methods names are aligned with acts_as_tree as much as possible to make transition from one # to another easier. # # item.children.create(:name => "child1") # # Configuration options are: # # * +:parent_column+ - specifies the column name to use for keeping the position integer (default: parent_id) # * +:primary_column+ - specifies the column name to use as the inverse of the parent column (default: id) # * +:left_column+ - column name for left boundary data, default "lft" # * +:right_column+ - column name for right boundary data, default "rgt" # * +:depth_column+ - column name for the depth data, default "depth" # * +:scope+ - restricts what is to be considered a list. Given a symbol, it'll attach "_id" # (if it hasn't been already) and use that as the foreign key restriction. You # can also pass an array to scope by multiple attributes. # Example: acts_as_nested_set :scope => [:notable_id, :notable_type] # * +:dependent+ - behavior for cascading destroy. If set to :destroy, all the # child objects are destroyed alongside this object by calling their destroy # method. If set to :delete_all (default), all the child objects are deleted # without calling their destroy method. # * +:counter_cache+ adds a counter cache for the number of children. # defaults to false. # Example: acts_as_nested_set :counter_cache => :children_count # * +:order_column+ on which column to do sorting, by default it is the left_column_name # Example: acts_as_nested_set :order_column => :position # # See CollectiveIdea::Acts::NestedSet::Model::ClassMethods for a list of class methods and # CollectiveIdea::Acts::NestedSet::Model for a list of instance methods added # to acts_as_nested_set models def acts_as_nested_set(options = {}) acts_as_nested_set_parse_options! options include Model include Columns extend Columns acts_as_nested_set_relate_parent! acts_as_nested_set_relate_children! attr_accessor :skip_before_destroy acts_as_nested_set_prevent_assignment_to_reserved_columns! acts_as_nested_set_define_callbacks! end private def acts_as_nested_set_define_callbacks! # on creation, set automatically lft and rgt to the end of the tree before_create :set_default_left_and_right before_save :store_new_parent after_save :move_to_new_parent, :set_depth! before_destroy :destroy_descendants define_model_callbacks :move end def acts_as_nested_set_relate_children! has_many_children_options = { :class_name => self.base_class.to_s, :foreign_key => parent_column_name, :primary_key => primary_column_name, :inverse_of => (:parent unless acts_as_nested_set_options[:polymorphic]), } # Add callbacks, if they were supplied.. otherwise, we don't want them. [:before_add, :after_add, :before_remove, :after_remove].each do |ar_callback| has_many_children_options.update( ar_callback => acts_as_nested_set_options[ar_callback] ) if acts_as_nested_set_options[ar_callback] end has_many :children, -> { order(order_column_name) }, **has_many_children_options end def acts_as_nested_set_relate_parent! options = { :class_name => self.base_class.to_s, :foreign_key => parent_column_name, :primary_key => primary_column_name, :counter_cache => acts_as_nested_set_options[:counter_cache], :inverse_of => (:children unless acts_as_nested_set_options[:polymorphic]), :touch => acts_as_nested_set_options[:touch] } options[:polymorphic] = true if acts_as_nested_set_options[:polymorphic] options[:optional] = true if ActiveRecord::VERSION::MAJOR >= 5 belongs_to :parent, **options end def acts_as_nested_set_default_options { :parent_column => 'parent_id', :primary_column => 'id', :left_column => 'lft', :right_column => 'rgt', :depth_column => 'depth', :dependent => :delete_all, # or :destroy :polymorphic => false, :counter_cache => false, :touch => false }.freeze end def acts_as_nested_set_parse_options!(options) options = acts_as_nested_set_default_options.merge(options) if options[:scope].is_a?(Symbol) && options[:scope].to_s !~ /_id$/ options[:scope] = "#{options[:scope]}_id".intern end class_attribute :acts_as_nested_set_options self.acts_as_nested_set_options = options end def acts_as_nested_set_prevent_assignment_to_reserved_columns! # no assignment to structure fields [left_column_name, right_column_name, depth_column_name].each do |column| module_eval <<-"end_eval", __FILE__, __LINE__ def #{column}=(x) raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{column}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead." end end_eval end end end end end awesome_nested_set-3.8.0/lib/awesome_nested_set/set_validator.rb0000644000004100000410000000371114741753242025252 0ustar www-datawww-datamodule CollectiveIdea #:nodoc: module Acts #:nodoc: module NestedSet #:nodoc: class SetValidator def initialize(model) @model = model @scope = model.all @parent = arel_table.alias('parent') end def valid? query.count == 0 end private attr_reader :model, :parent attr_accessor :scope delegate :parent_column_name, :primary_column_name, :primary_key, :left_column_name, :right_column_name, :arel_table, :quoted_table_name, :quoted_parent_column_full_name, :quoted_left_column_full_name, :quoted_right_column_full_name, :quoted_left_column_name, :quoted_right_column_name, :quoted_primary_column_name, :to => :model def query join_scope filter_scope end def join_scope join_arel = arel_table.join(parent, Arel::Nodes::OuterJoin).on(parent[primary_column_name].eq(arel_table[parent_column_name])) self.scope = scope.joins(join_arel.join_sources) end def filter_scope self.scope = scope.where( bound_is_null(left_column_name). or(bound_is_null(right_column_name)). or(left_bound_greater_than_right). or(parent_not_null.and(bounds_outside_parent)) ) end def bound_is_null(column_name) arel_table[column_name].eq(nil) end def left_bound_greater_than_right arel_table[left_column_name].gteq(arel_table[right_column_name]) end def parent_not_null arel_table[parent_column_name].not_eq(nil) end def bounds_outside_parent arel_table[left_column_name].lteq(parent[left_column_name]).or(arel_table[right_column_name].gteq(parent[right_column_name])) end end end end end awesome_nested_set-3.8.0/lib/awesome_nested_set/model/0000755000004100000410000000000014741753242023163 5ustar www-datawww-dataawesome_nested_set-3.8.0/lib/awesome_nested_set/model/prunable.rb0000644000004100000410000000665514741753242025334 0ustar www-datawww-datamodule CollectiveIdea #:nodoc: module Acts #:nodoc: module NestedSet #:nodoc: module Model module Prunable # Prunes a branch off of the tree, shifting all of the elements on the right # back to the left so the counts still work. def destroy_descendants return if right.nil? || left.nil? || skip_before_destroy in_tenacious_transaction do # Rescue from +ActiveRecord::RecordNotFound+ error as there may be a case # that an +object+ has already been destroyed by its parent, but objects that are # in memory are not aware about this. begin reload_nested_set rescue ActiveRecord::RecordNotFound self.skip_before_destroy = true return true end # select the rows in the model that extend past the deletion point and apply a lock nested_set_scope.right_of(left).select(primary_id).lock(true) return false unless destroy_or_delete_descendants # update lefts and rights for remaining nodes update_siblings_for_remaining_nodes # Reload is needed because children may have updated their parent (self) during deletion. reload # Don't allow multiple calls to destroy to corrupt the set self.skip_before_destroy = true end end # Use reverse to delete from deepest child to parent in order to respect any possible foreign keys def decendants_to_destroy_in_order descendants.reverse end def destroy_or_delete_descendants if acts_as_nested_set_options[:dependent] == :destroy decendants_to_destroy_in_order.each do |model| model.skip_before_destroy = true model.destroy end elsif acts_as_nested_set_options[:dependent] == :restrict_with_exception raise ActiveRecord::DeleteRestrictionError.new(:children) unless leaf? return true elsif acts_as_nested_set_options[:dependent] == :restrict_with_error unless leaf? record = self.class.human_attribute_name(:children).downcase if Rails::VERSION::MAJOR < 5 errors.add(:base, :"restrict_dependent_destroy.many", record: record) return false else errors.add(:base, :"restrict_dependent_destroy.has_many", record: record) throw :abort end end return true elsif acts_as_nested_set_options[:dependent] == :nullify descendants.update_all(parent_column_name => nil) else descendants.delete_all end end def update_siblings_for_remaining_nodes update_siblings(:left) update_siblings(:right) end def update_siblings(direction) full_column_name = send("quoted_#{direction}_column_full_name") column_name = send("quoted_#{direction}_column_name") nested_set_scope.where(["#{full_column_name} > ?", right]). update_all(["#{column_name} = (#{column_name} - ?)", diff]) end def diff right - left + 1 end end end end end end awesome_nested_set-3.8.0/lib/awesome_nested_set/model/transactable.rb0000644000004100000410000000221014741753242026146 0ustar www-datawww-datamodule CollectiveIdea #:nodoc: module Acts #:nodoc: module NestedSet #:nodoc: module Model module Transactable class OpenTransactionsIsNotZero < ActiveRecord::StatementInvalid end class DeadlockDetected < ActiveRecord::StatementInvalid end protected def in_tenacious_transaction(&block) retry_count = 0 begin transaction(&block) rescue CollectiveIdea::Acts::NestedSet::Move::ImpossibleMove raise rescue ActiveRecord::StatementInvalid => error raise OpenTransactionsIsNotZero.new(error.message) unless self.class.connection.open_transactions.zero? raise unless error.message =~ /[Dd]eadlock|Lock wait timeout exceeded/ raise DeadlockDetected.new(error.message) unless retry_count < 10 retry_count += 1 logger.info "Deadlock detected on retry #{retry_count}, restarting transaction" sleep(rand(retry_count)*0.1) # Aloha protocol retry end end end end end end end awesome_nested_set-3.8.0/lib/awesome_nested_set/model/rebuildable.rb0000644000004100000410000000240214741753242025760 0ustar www-datawww-datarequire 'awesome_nested_set/tree' module CollectiveIdea module Acts module NestedSet module Model module Rebuildable # Rebuilds the left & rights if unset or invalid. # Also very useful for converting from acts_as_tree. def rebuild!(validate_nodes = true) # default_scope with order may break database queries so we do all operation without scope unscoped do Tree.new(self, validate_nodes).rebuild! end end def scope_for_rebuild scope = proc {} if acts_as_nested_set_options[:scope] scope = proc {|node| scope_column_names.inject("") {|str, column_name| column_value = node.send(column_name) cond = column_value.nil? ? "IS NULL" : "= #{connection.quote(column_value)}" str << "AND #{connection.quote_column_name(column_name)} #{cond} " } } end scope end def order_for_rebuild { left_column_name => :asc, right_column_name => :asc, primary_key => :asc } end end end end end end awesome_nested_set-3.8.0/lib/awesome_nested_set/model/validatable.rb0000644000004100000410000000515014741753242025761 0ustar www-datawww-datarequire 'awesome_nested_set/set_validator' module CollectiveIdea module Acts module NestedSet module Model module Validatable def valid? left_and_rights_valid? && no_duplicates_for_columns? && all_roots_valid? end def left_and_rights_valid? SetValidator.new(self).valid? end def no_duplicates_for_columns? [quoted_left_column_full_name, quoted_right_column_full_name].all? do |column| # No duplicates select("#{scope_string}#{column}, COUNT(#{column}) as _count"). group("#{scope_string}#{column}", quoted_primary_key_column_full_name). having("COUNT(#{column}) > 1"). order(primary_column_name => :asc). first.nil? end end # Wrapper for each_root_valid? that can deal with scope. def all_roots_valid? if acts_as_nested_set_options[:scope] all_roots_valid_by_scope?(roots) else each_root_valid?(roots) end end def all_roots_valid_by_scope?(roots_to_validate) roots_grouped_by_scope(roots_to_validate).all? do |scope, grouped_roots| each_root_valid?(grouped_roots) end end def each_root_valid?(roots_to_validate) left_column = acts_as_nested_set_options[:left_column] reordered_roots = roots_reordered_by_column(roots_to_validate, left_column) left = right = 0 reordered_roots.all? do |root| (root.left > left && root.right > right).tap do left = root.left right = root.right end end end private def roots_grouped_by_scope(roots_to_group) roots_to_group.group_by {|record| scope_column_names.collect {|col| record.send(col) } } end def roots_reordered_by_column(roots_to_reorder, column) if roots_to_reorder.respond_to?(:reorder) # ActiveRecord's relation roots_to_reorder.reorder(column) elsif roots_to_reorder.respond_to?(:sort) # Array roots_to_reorder.sort { |a, b| a.send(column) <=> b.send(column) } else roots_to_reorder end end def scope_string Array(acts_as_nested_set_options[:scope]).map do |c| connection.quote_column_name(c) end.push(nil).join(", ") end end end end end end awesome_nested_set-3.8.0/lib/awesome_nested_set/model/relatable.rb0000644000004100000410000000724114741753242025447 0ustar www-datawww-datamodule CollectiveIdea module Acts module NestedSet module Model module Relatable # Returns an collection of all parents def ancestors without_self self_and_ancestors end # Returns the collection of all parents and self def self_and_ancestors nested_set_scope. where(arel_table[left_column_name].lteq(left)). where(arel_table[right_column_name].gteq(right)) end # Returns the collection of all children of the parent, except self def siblings without_self self_and_siblings end # Returns the collection of all children of the parent, including self def self_and_siblings nested_set_scope.children_of parent_id end # Returns a set of all of its nested children which do not have children def leaves descendants.where( "#{quoted_right_column_full_name} - #{quoted_left_column_full_name} = 1" ) end # Returns the level of this object in the tree # root level is 0 def level parent_id.nil? ? 0 : compute_level end # Returns a collection including all of its children and nested children def descendants without_self self_and_descendants end # Returns a collection including itself and all of its nested children def self_and_descendants # using _left_ for both sides here lets us benefit from an index on that column if one exists nested_set_scope.right_of(left).left_of(right) end def is_descendant_of?(other) within_node?(other, self) && same_scope?(other) end def is_or_is_descendant_of?(other) (other == self || within_node?(other, self)) && same_scope?(other) end def is_ancestor_of?(other) within_node?(self, other) && same_scope?(other) end def is_or_is_ancestor_of?(other) (self == other || within_node?(self, other)) && same_scope?(other) end # Check if other model is in the same scope def same_scope?(other) Array(acts_as_nested_set_options[:scope]).all? do |attr| self.send(attr) == other.send(attr) end end # Find the first sibling to the left def left_sibling siblings.left_of(left).last end # Find the first sibling to the right def right_sibling siblings.right_of(left).first end def root return self_and_ancestors.children_of(nil).first if persisted? if parent_id && current_parent = nested_set_scope.where(primary_column_name => parent_id).first! current_parent.root else self end end def roots nested_set_scope.where(parent_id: nil) end protected def compute_level node, nesting = determine_depth node == self ? ancestors.count : node.level + nesting end def determine_depth(node = self, nesting = 0) while (association = node.association(:parent)).loaded? && association.target nesting += 1 node = node.parent end if node.respond_to?(:association) [node, nesting] end def within_node?(node, within) node.left < within.left && within.left < node.right end end end end end end awesome_nested_set-3.8.0/lib/awesome_nested_set/model/movable.rb0000644000004100000410000001176514741753242025147 0ustar www-datawww-datarequire 'awesome_nested_set/move' module CollectiveIdea #:nodoc: module Acts #:nodoc: module NestedSet #:nodoc: module Model module Movable def move_possible?(target) self != target && # Can't target self same_scope?(target) && # can't be in different scopes # detect impossible move within_bounds?(target.left, target.left) && within_bounds?(target.right, target.right) end # Shorthand method for finding the left sibling and moving to the left of it. def move_left move_to_left_of left_sibling end # Shorthand method for finding the right sibling and moving to the right of it. def move_right move_to_right_of right_sibling end # Move the node to the left of another node def move_to_left_of(node) move_to node, :left end # Move the node to the right of another node def move_to_right_of(node) move_to node, :right end # Move the node to the child of another node def move_to_child_of(node) if node == :root move_to_root else move_to node, :child end end # Move the node to the child of another node with specify index def move_to_child_with_index(node, index) siblings = node == :root ? roots : node.children if siblings.empty? move_to_child_of(node) elsif siblings.count == index move_to_right_of(siblings.last) else my_position = siblings.index(self) if my_position && my_position < index # e.g. if self is at position 0 and we want to move self to position 1 then self # needs to move to the *right* of the node at position 1. That's because the node # that is currently at position 1 will be at position 0 after the move completes. move_to_right_of(siblings[index]) elsif my_position && my_position == index # do nothing. already there. else move_to_left_of(siblings[index]) end end end # Move the node to root nodes def move_to_root move_to self, :root end # Order children in a nested set by an attribute # Can order by any attribute class that uses the Comparable mixin, for example a string or integer # Usage example when sorting categories alphabetically: @new_category.move_to_ordered_child_of(@root, "name") def move_to_ordered_child_of(parent, order_attribute, ascending = true) self.move_to_root and return unless parent left_neighbor = find_left_neighbor(parent, order_attribute, ascending) self.move_to_child_of(parent) return unless parent.children.many? if left_neighbor self.move_to_right_of(left_neighbor) else # Self is the left most node. self.move_to_left_of(parent.children[0]) end end # Find the node immediately to the left of this node. def find_left_neighbor(parent, order_attribute, ascending) left = nil parent.children.each do |n| if ascending left = n if n.send(order_attribute) < self.send(order_attribute) else left = n if n.send(order_attribute) > self.send(order_attribute) end end left end def move_to(target, position) prevent_unpersisted_move run_callbacks :move do in_tenacious_transaction do target = reload_target(target, position) self.reload_nested_set Move.new(target, position, self).move update_counter_cache end after_move_to(target, position) end end protected def after_move_to(target, position) target.reload_nested_set if target self.set_depth_for_self_and_descendants! self.reload_nested_set end def move_to_new_parent if @move_to_new_parent_id.nil? move_to_root elsif @move_to_new_parent_id move_to_child_of(@move_to_new_parent_id) end end def out_of_bounds?(left_bound, right_bound) left <= left_bound && right >= right_bound end def prevent_unpersisted_move if self.new_record? raise ActiveRecord::ActiveRecordError, "You cannot move a new node" end end def within_bounds?(left_bound, right_bound) !out_of_bounds?(left_bound, right_bound) end end end end end end awesome_nested_set-3.8.0/lib/awesome_nested_set/helper.rb0000644000004100000410000000311714741753242023671 0ustar www-datawww-data# -*- coding: utf-8 -*- module CollectiveIdea #:nodoc: module Acts #:nodoc: module NestedSet #:nodoc: # This module provides some helpers for the model classes using acts_as_nested_set. # It is included by default in all views. # module Helper # Returns options for select. # You can exclude some items from the tree. # You can pass a block receiving an item and returning the string displayed in the select. # # == Params # * +class_or_items+ - Class name or top level items # * +mover+ - The item that is being move, used to exclude impossible moves # * +&block+ - a block that will be used to display: { |item| ... item.name } # # == Usage # # <%= f.select :parent_id, nested_set_options(Category, @category) {|i| # "#{'–' * i.level} #{i.name}" # }) %> # def nested_set_options(class_or_items, mover = nil) if class_or_items.is_a? Array items = class_or_items.reject { |e| !e.root? } else class_or_items = class_or_items.roots if class_or_items.respond_to?(:scope) items = Array(class_or_items) end result = [] items.each do |root| result += root.class.associate_parents(root.self_and_descendants).map do |i| if mover.nil? || mover.new_record? || mover.move_possible?(i) [yield(i), i.primary_id] end end.compact end result end end end end end awesome_nested_set-3.8.0/lib/awesome_nested_set/model.rb0000644000004100000410000002174414741753242023520 0ustar www-datawww-datarequire 'awesome_nested_set/model/prunable' require 'awesome_nested_set/model/movable' require 'awesome_nested_set/model/transactable' require 'awesome_nested_set/model/relatable' require 'awesome_nested_set/model/rebuildable' require 'awesome_nested_set/model/validatable' require 'awesome_nested_set/iterator' module CollectiveIdea #:nodoc: module Acts #:nodoc: module NestedSet #:nodoc: module Model extend ActiveSupport::Concern included do delegate :quoted_table_name, :arel_table, :to => self extend Validatable extend Rebuildable include Movable include Prunable include Relatable include Transactable end module ClassMethods def associate_parents(objects) return objects unless objects.all? {|o| o.respond_to?(:association)} id_indexed = objects.index_by(&primary_column_name.to_sym) objects.each do |object| association = object.association(:parent) parent = id_indexed[object.parent_id] if !association.loaded? && parent association.target = parent add_to_inverse_association(association, parent) end end end def add_to_inverse_association(association, record) inverse_reflection = association.send(:inverse_reflection_for, record) inverse = record.association(inverse_reflection.name) inverse.target << association.owner inverse.loaded! end def children_of(parent_id) where arel_table[parent_column_name].eq(parent_id) end # Iterates over tree elements and determines the current level in the tree. # Only accepts default ordering, odering by an other column than lft # does not work. This method is much more efficient than calling level # because it doesn't require any additional database queries. # # Example: # Category.each_with_level(Category.root.self_and_descendants) do |o, level| # def each_with_level(objects, &block) Iterator.new(objects).each_with_level(&block) end def leaves nested_set_scope.where "#{quoted_right_column_full_name} - #{quoted_left_column_full_name} = 1" end def left_of(node) where arel_table[left_column_name].lt(node) end def left_of_right_side(node) where arel_table[right_column_name].lteq(node) end def right_of(node) where arel_table[left_column_name].gteq(node) end def nested_set_scope(options = {}) order = scope_order_from_options(options) where(options[:conditions]).order(order) end def primary_key_scope(id) where arel_table[primary_column_name].eq(id) end def root roots.first end def roots nested_set_scope.children_of nil end private def scope_order_from_options(options) options.fetch(:order, order_column_name) end end # end class methods # Any instance method that returns a collection makes use of Rails 2.1's named_scope (which is bundled for Rails 2.0), so it can be treated as a finder. # # category.self_and_descendants.count # category.ancestors.find(:all, :conditions => "name like '%foo%'") # Value of the parent column def parent_id(target = self) target[parent_column_name] end def primary_id(target = self) target[primary_column_name] end # Value of the left column def left(target = self) target[left_column_name] end # Value of the right column def right(target = self) target[right_column_name] end # Returns true if this is a root node. def root? parent_id.nil? end # Returns true is this is a child node def child? !root? end # Returns true if this is the end of a branch. def leaf? persisted? && right.to_i - left.to_i == 1 end # All nested set queries should use this nested_set_scope, which # performs finds on the base ActiveRecord class, using the :scope # declared in the acts_as_nested_set declaration. def nested_set_scope(options = {}) add_scope_conditions_to_options(options) self.class.base_class.default_scoped.nested_set_scope options end # Separate an other `nested_set_scope` for unscoped model # because normal query still need activerecord `default_scope` # Only activerecord callbacks need unscoped model to handle the nested set records # And class level `nested_set_scope` seems just for query `root` `child` .. etc # I think we don't have to provide unscoped `nested_set_scope` in class level. def nested_set_scope_without_default_scope(options = {}) add_scope_conditions_to_options(options) self.class.base_class.unscoped.nested_set_scope options end def to_text self_and_descendants.map do |node| "#{'*'*(node.level+1)} #{node.primary_id} #{node.to_s} (#{node.parent_id}, #{node.left}, #{node.right})" end.join("\n") end protected def add_scope_conditions_to_options(options) scopes = scope_column_names return if scopes.empty? options[:conditions] = scopes.map { |attr| [attr, self[attr] ] }.to_h end def without_self(scope) return scope if new_record? scope.where(["#{self.class.quoted_table_name}.#{self.class.quoted_primary_column_name} != ?", self.primary_id]) end def store_new_parent @move_to_new_parent_id = send("#{parent_column_name}_changed?") ? parent_id : false true # force callback to return true end def has_depth_column? nested_set_scope.column_names.map(&:to_s).include?(depth_column_name.to_s) end def right_most_node @right_most_node ||= nested_set_scope_without_default_scope( :order => {right_column_name => :desc} ).first end def right_most_bound @right_most_bound ||= begin return 0 if right_most_node.nil? right_most_node.lock! right_most_node[right_column_name] || 0 end end def set_depth! return unless has_depth_column? in_tenacious_transaction do update_depth(level) end end def set_depth_for_self_and_descendants! return unless has_depth_column? in_tenacious_transaction do reload self_and_descendants.select(primary_column_name).lock(true) old_depth = self[depth_column_name] || 0 new_depth = level update_depth(new_depth) change_descendants_depth!(new_depth - old_depth) new_depth end end def update_depth(depth) nested_set_scope.primary_key_scope(primary_id). update_all(["#{quoted_depth_column_name} = ?", depth]) self[depth_column_name] = depth end def change_descendants_depth!(diff) if !leaf? && diff != 0 sign = "++-"[diff <=> 0] descendants.update_all("#{quoted_depth_column_name} = #{quoted_depth_column_name} #{sign} #{diff.abs}") end end def update_counter_cache return unless acts_as_nested_set_options[:counter_cache] # Decrease the counter for all old parents if old_parent = self.parent old_parent.class.decrement_counter(acts_as_nested_set_options[:counter_cache], old_parent) end # Increase the counter for all new parents if new_parent = self.reload.parent new_parent.class.increment_counter(acts_as_nested_set_options[:counter_cache], new_parent) end end def set_default_left_and_right # adds the new node to the right of all existing nodes self[left_column_name] = right_most_bound + 1 self[right_column_name] = right_most_bound + 2 end # reload left, right, and parent def reload_nested_set reload( :select => "#{quoted_left_column_full_name}, #{quoted_right_column_full_name}, #{quoted_parent_column_full_name}", :lock => true ) end def reload_target(target, position) if target.is_a? self.class.base_class target.reload elsif position != :root nested_set_scope_without_default_scope.where(primary_column_name => target).first! end end end end end end awesome_nested_set-3.8.0/lib/awesome_nested_set/version.rb0000644000004100000410000000017414741753242024077 0ustar www-datawww-data# frozen_string_literal: true module AwesomeNestedSet VERSION = '3.8.0' unless defined?(::AwesomeNestedSet::VERSION) end awesome_nested_set-3.8.0/lib/awesome_nested_set/move.rb0000644000004100000410000001233514741753242023362 0ustar www-datawww-datamodule CollectiveIdea #:nodoc: module Acts #:nodoc: module NestedSet #:nodoc: class Move attr_reader :target, :position, :instance def initialize(target, position, instance) @target = target @position = position @instance = instance end def move prevent_impossible_move bound, other_bound = get_boundaries # there would be no change return if bound == right || bound == left # we have defined the boundaries of two non-overlapping intervals, # so sorting puts both the intervals and their boundaries in order a, b, c, d = [left, right, bound, other_bound].sort lock_nodes_between! a, d nested_set_scope_without_default_scope.where(where_statement(a, d)).update_all( conditions(a, b, c, d) ) end private delegate :left, :right, :left_column_name, :right_column_name, :quoted_left_column_name, :quoted_right_column_name, :quoted_parent_column_name, :parent_column_name, :nested_set_scope_without_default_scope, :primary_column_name, :quoted_primary_column_name, :primary_id, :to => :instance delegate :arel_table, :class, :to => :instance, :prefix => true delegate :base_class, :to => :instance_class, :prefix => :instance def where_statement(left_bound, right_bound) instance_arel_table[left_column_name].between(left_bound..right_bound). or(instance_arel_table[right_column_name].between(left_bound..right_bound)) end # Before Arel 6, there was 'in' method, which was replaced # with 'between' in Arel 6 and now gives deprecation warnings # in verbose mode. This is patch to support rails 4.0 (Arel 4) # and 4.1 (Arel 5). module LegacyWhereStatementExt def where_statement(left_bound, right_bound) instance_arel_table[left_column_name].in(left_bound..right_bound). or(instance_arel_table[right_column_name].in(left_bound..right_bound)) end end prepend LegacyWhereStatementExt unless Arel::Predications.method_defined?(:between) def conditions(a, b, c, d) _conditions = case_condition_for_direction(:quoted_left_column_name) + case_condition_for_direction(:quoted_right_column_name) + case_condition_for_parent # We want the record to be 'touched' if it timestamps. if @instance.respond_to?(:updated_at) _conditions << ", updated_at = :timestamp" end [ _conditions, { :a => a, :b => b, :c => c, :d => d, :primary_id => instance.primary_id, :new_parent_id => new_parent_id, :timestamp => Time.now.utc } ] end def case_condition_for_direction(column_name) column = send(column_name) "#{column} = CASE " + "WHEN #{column} BETWEEN :a AND :b " + "THEN #{column} + :d - :b " + "WHEN #{column} BETWEEN :c AND :d " + "THEN #{column} + :a - :c " + "ELSE #{column} END, " end def case_condition_for_parent "#{quoted_parent_column_name} = CASE " + "WHEN #{quoted_primary_column_name} = :primary_id THEN :new_parent_id " + "ELSE #{quoted_parent_column_name} END" end def lock_nodes_between!(left_bound, right_bound) # select the rows in the model between a and d, and apply a lock instance_base_class.default_scoped.nested_set_scope. right_of(left_bound).left_of_right_side(right_bound). select(primary_column_name). lock(true) end def root position == :root end def new_parent_id case position when :child then target.primary_id when :root then nil else target[parent_column_name] end end def get_boundaries if (bound = target_bound) > right bound -= 1 other_bound = right + 1 else other_bound = left - 1 end [bound, other_bound] end class ImpossibleMove < ActiveRecord::StatementInvalid end def prevent_impossible_move if !root && !instance.move_possible?(target) error_msg = "Impossible move, target node (#{target.class.name},ID: #{target.id}) cannot be inside moved tree (#{instance.class.name},ID: #{instance.id})." raise ImpossibleMove, error_msg end end def target_bound case position when :child then right(target) when :left then left(target) when :right then right(target) + 1 when :root then nested_set_scope_without_default_scope.pluck(right_column_name).max + 1 else raise ActiveRecord::ActiveRecordError, "Position should be :child, :left, :right or :root ('#{position}' received)." end end end end end end awesome_nested_set-3.8.0/lib/awesome_nested_set/tree.rb0000644000004100000410000000401014741753242023342 0ustar www-datawww-datamodule CollectiveIdea #:nodoc: module Acts #:nodoc: module NestedSet #:nodoc: class Tree attr_reader :model, :validate_nodes attr_accessor :indices delegate :left_column_name, :right_column_name, :quoted_parent_column_full_name, :order_for_rebuild, :scope_for_rebuild, :counter_cache_column_name, :to => :model def initialize(model, validate_nodes) @model = model @validate_nodes = validate_nodes @indices = {} end def rebuild! # Don't rebuild a valid tree. return true if model.valid? root_nodes.each do |root_node| # setup index for this scope indices[scope_for_rebuild.call(root_node)] ||= 0 set_left_and_rights(root_node) reset_counter_cache(root_node) end end private def increment_indice!(node) indices[scope_for_rebuild.call(node)] += 1 end def set_left_and_rights(node) set_left!(node) # find node_children(node).each { |n| set_left_and_rights(n) } set_right!(node) node.save!(:validate => validate_nodes) end def node_children(node) model.where(["#{quoted_parent_column_full_name} = ? #{scope_for_rebuild.call(node)}", node.primary_id]). order(order_for_rebuild) end def root_nodes model.where("#{quoted_parent_column_full_name} IS NULL").order(order_for_rebuild) end def set_left!(node) node[left_column_name] = increment_indice!(node) end def set_right!(node) node[right_column_name] = increment_indice!(node) end def reset_counter_cache(node) return unless counter_cache_column_name node.class.reset_counters(node.id, :children) node.children.each do |child| reset_counter_cache(child) end end end end end end awesome_nested_set-3.8.0/lib/awesome_nested_set/iterator.rb0000644000004100000410000000133214741753242024240 0ustar www-datawww-datamodule CollectiveIdea #:nodoc: module Acts #:nodoc: module NestedSet #:nodoc: class Iterator attr_reader :objects def initialize(objects) @objects = objects end def each_with_level path = [nil] objects.each do |o| if o.parent_id != path.last # we are on a new level, did we descend or ascend? if path.include?(o.parent_id) # remove wrong tailing paths elements path.pop while path.last != o.parent_id else path << o.parent_id end end yield(o, path.length - 1) end end end end end end awesome_nested_set-3.8.0/awesome_nested_set.gemspec0000644000004100000410000000572214741753242022670 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: awesome_nested_set 3.8.0 ruby lib Gem::Specification.new do |s| s.name = "awesome_nested_set".freeze s.version = "3.8.0" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Brandon Keepers".freeze, "Daniel Morrison".freeze, "Philip Arndt".freeze] s.date = "2024-11-14" s.description = "An awesome nested set implementation for Active Record".freeze s.email = "info@collectiveidea.com".freeze s.extra_rdoc_files = ["README.md".freeze] s.files = ["CHANGELOG".freeze, "MIT-LICENSE".freeze, "README.md".freeze, "lib/awesome_nested_set.rb".freeze, "lib/awesome_nested_set/awesome_nested_set.rb".freeze, "lib/awesome_nested_set/columns.rb".freeze, "lib/awesome_nested_set/helper.rb".freeze, "lib/awesome_nested_set/iterator.rb".freeze, "lib/awesome_nested_set/model.rb".freeze, "lib/awesome_nested_set/model/movable.rb".freeze, "lib/awesome_nested_set/model/prunable.rb".freeze, "lib/awesome_nested_set/model/rebuildable.rb".freeze, "lib/awesome_nested_set/model/relatable.rb".freeze, "lib/awesome_nested_set/model/transactable.rb".freeze, "lib/awesome_nested_set/model/validatable.rb".freeze, "lib/awesome_nested_set/move.rb".freeze, "lib/awesome_nested_set/set_validator.rb".freeze, "lib/awesome_nested_set/tree.rb".freeze, "lib/awesome_nested_set/version.rb".freeze] s.homepage = "https://github.com/collectiveidea/awesome_nested_set".freeze s.licenses = ["MIT".freeze] s.rdoc_options = ["--main".freeze, "README.md".freeze, "--inline-source".freeze, "--line-numbers".freeze] s.required_ruby_version = Gem::Requirement.new(">= 2.0.0".freeze) s.rubygems_version = "3.3.15".freeze s.summary = "An awesome nested set implementation for Active Record".freeze if s.respond_to? :specification_version then s.specification_version = 4 end if s.respond_to? :add_runtime_dependency then s.add_runtime_dependency(%q.freeze, [">= 4.0.0", "< 8.1"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, ["~> 13"]) s.add_development_dependency(%q.freeze, [">= 4.0.0", "< 8.0"]) else s.add_dependency(%q.freeze, [">= 4.0.0", "< 8.1"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, ["~> 13"]) s.add_dependency(%q.freeze, [">= 4.0.0", "< 8.0"]) end end awesome_nested_set-3.8.0/MIT-LICENSE0000644000004100000410000000205014741753242017071 0ustar www-datawww-dataCopyright (c) 2007-2011 Collective Idea Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. awesome_nested_set-3.8.0/README.md0000644000004100000410000002014214741753242016716 0ustar www-datawww-data# Awesome Nested Set [![CI](https://github.com/collectiveidea/awesome_nested_set/actions/workflows/ci.yml/badge.svg)](https://github.com/collectiveidea/awesome_nested_set/actions/workflows/ci.yml) [![Code Climate](https://codeclimate.com/github/collectiveidea/awesome_nested_set.svg)](https://codeclimate.com/github/collectiveidea/awesome_nested_set) Awesome Nested Set is an implementation of the nested set pattern for ActiveRecord models. It is a replacement for acts_as_nested_set and BetterNestedSet, but more awesome. Older versions: Version 3.2 supports Rails 6, 3.1 supports Rails 5 & 4. Version 2 supports Rails 3. Gem versions prior to 2.0 support Rails 2. ## What makes this so awesome? This is a new implementation of nested set based off of BetterNestedSet that fixes some bugs, removes tons of duplication, adds a few useful methods, and adds STI support. ## Installation Add to your Gemfile: ```ruby gem 'awesome_nested_set' ``` ## Usage To make use of `awesome_nested_set` your model needs to have 3 fields: `lft`, `rgt`, and `parent_id`. The names of these fields are configurable. You can also have optional fields: `depth` and `children_count`. These fields are configurable. Note that the `children_count` column must have `null: false` and `default: 0` to function properly. ```ruby class CreateCategories < ActiveRecord::Migration def change create_table :categories do |t| t.string :name t.integer :parent_id, null: true, index: true t.integer :lft, null: false, index: true t.integer :rgt, null: false, index: true # optional fields t.integer :depth, null: false, default: 0 t.integer :children_count, null: false, default: 0 t.timestamps end end end ``` Enable the nested set functionality by declaring `acts_as_nested_set` on your model ```ruby class Category < ActiveRecord::Base acts_as_nested_set end ``` Run `rake rdoc` to generate the API docs and see [CollectiveIdea::Acts::NestedSet](lib/awesome_nested_set/awesome_nested_set.rb) for more information. ## Options You can pass various options to `acts_as_nested_set` macro. Configuration options are: * `parent_column`: specifies the column name to use for keeping the position integer (default: parent_id) * `primary_column`: specifies the column name to use as the inverse of the parent column (default: id) * `left_column`: column name for left boundary data (default: lft) * `right_column`: column name for right boundary data (default: rgt) * `depth_column`: column name for the depth data default (default: depth) * `scope`: restricts what is to be considered a list. Given a symbol, it'll attach `_id` (if it hasn't been already) and use that as the foreign key restriction. You can also pass an array to scope by multiple attributes. Example: `acts_as_nested_set :scope => [:notable_id, :notable_type]` * `dependent`: behavior for cascading destroy. If set to :destroy, all the child objects are destroyed alongside this object by calling their destroy method. If set to :delete_all (default), all the child objects are deleted without calling their destroy method. If set to :nullify, all child objects will become orphaned and become roots themselves. * `counter_cache`: adds a counter cache for the number of children. defaults to false. Example: `acts_as_nested_set :counter_cache => :children_count` * `order_column`: on which column to do sorting, by default it is the left_column_name. Example: `acts_as_nested_set :order_column => :position` * `touch`: If set to `true`, then the updated_at timestamp on the ancestors will be set to the current time whenever this object is saved or destroyed (default: false) See [CollectiveIdea::Acts::NestedSet::Model::ClassMethods](/lib/awesome_nested_set/model.rb#L26) for a list of class methods and [CollectiveIdea::Acts::NestedSet::Model](lib/awesome_nested_set/model.rb#L13) for a list of instance methods added to acts_as_nested_set models ## Indexes It is highly recommended that you add an index to the `rgt` column on your models. Every insertion requires finding the next `rgt` value to use and this can be slow for large tables without an index. It is probably best to index the other fields as well (`parent_id`, `lft`, `depth`). ## Callbacks There are three callbacks called when moving a node: `before_move`, `after_move` and `around_move`. ```ruby class Category < ActiveRecord::Base acts_as_nested_set after_move :rebuild_slug around_move :da_fancy_things_around private def rebuild_slug # do whatever end def da_fancy_things_around # do something... yield # actually moves # do something else... end end ``` Beside this there are also hooks to act on the newly added or removed children. ```ruby class Category < ActiveRecord::Base acts_as_nested_set :before_add => :do_before_add_stuff, :after_add => :do_after_add_stuff, :before_remove => :do_before_remove_stuff, :after_remove => :do_after_remove_stuff private def do_before_add_stuff(child_node) # do whatever with the child end def do_after_add_stuff(child_node) # do whatever with the child end def do_before_remove_stuff(child_node) # do whatever with the child end def do_after_remove_stuff(child_node) # do whatever with the child end end ``` ## Protecting attributes from mass assignment (for Rails < 4) It's generally best to "whitelist" the attributes that can be used in mass assignment: ```ruby class Category < ActiveRecord::Base acts_as_nested_set attr_accessible :name, :parent_id end ``` If for some reason that is not possible, you will probably want to protect the `lft` and `rgt` attributes: ```ruby class Category < ActiveRecord::Base acts_as_nested_set attr_protected :lft, :rgt end ``` ## Add to your existing project To make use of `awesome_nested_set`, your model needs to have 3 fields: `lft`, `rgt`, and `parent_id`. The names of these fields are configurable. You can also have optional fields, `depth` and `children_count`. Create a migration to add fields: ```ruby class AddNestedToCategories < ActiveRecord::Migration def self.up add_column :categories, :parent_id, :integer # Comment this line if your project already has this column # Category.where(parent_id: 0).update_all(parent_id: nil) # Uncomment this line if your project already has :parent_id add_column :categories, :lft, :integer add_column :categories, :rgt, :integer # optional fields add_column :categories, :depth, :integer add_column :categories, :children_count, :integer # This is necessary to update :lft and :rgt columns Category.reset_column_information Category.rebuild! end def self.down remove_column :categories, :parent_id remove_column :categories, :lft remove_column :categories, :rgt # optional fields remove_column :categories, :depth remove_column :categories, :children_count end end ``` Enable the nested set functionality by declaring `acts_as_nested_set` on your model ```ruby class Category < ActiveRecord::Base acts_as_nested_set end ``` Your project is now ready to run with the `awesome_nested_set` gem! ## Conversion from other trees Coming from acts_as_tree or another system where you only have a parent_id? No problem. Simply add the lft & rgt fields as above, and then run: ```ruby Category.rebuild! ``` Your tree will be converted to a valid nested set. Awesome! Note: You can use `Category.rebuild!(false)` to skip model validations when performing the rebuild. ## View Helper The view helper is called #nested_set_options. Example usage: ```erb <%= f.select :parent_id, nested_set_options(Category, @category) {|i| "#{'-' * i.level} #{i.name}" } %> <%= select_tag 'parent_id', options_for_select(nested_set_options(Category) {|i| "#{'-' * i.level} #{i.name}" } ) %> ``` See [CollectiveIdea::Acts::NestedSet::Helper](lib/awesome_nested_set/helper.rb) for more information about the helpers. ## How to contribute Please see the ['Contributing' document](CONTRIBUTING.md). Copyright © 2008–2015 [Collective Idea](http://collectiveidea.com), released under the MIT license. awesome_nested_set-3.8.0/CHANGELOG0000644000004100000410000002035614741753242016660 0ustar www-datawww-dataUnreleased version 3.8.0 * Support Rails 8.0 [Takuya N](https://github.com/collectiveidea/awesome_nested_set/pull/487) * Enable Rubygems Trusted Publishing 3.7.0 * Teach #move_to_child_of and #move_to_child_with_index to accept :root as a parameter [Micah Geisel](https://github.com/botandrose) * Add #roots method [Micah Geisel](https://github.com/botandrose) * Support Rails 7.2 [Ahmed A. Ibrahim](https://github.com/AhmedAliIbrahim) 3.6.0 * Support Rails 7.1 [Harshal Bhakta](https://github.com/harshalbhakta) * Improve ImpossibleMove error message [AlejandroFernandesAntunes](https://github.com/AlejandroFernandesAntunes) 3.5.0 * Support Rails 7.0.0 [Peter Berkenbosch](https://github.com/peterberkenbosch) and [Andrew Hampton](https://github.com/andrewhampton) * Make `order_column` option more flexible by removing explicit `=> :asc` [Regis Millet](https://github.com/Kulgar) * Updated README to use latest migration syntax [Justin MacCarthy](https://github.com/macarthy) * [Compare to 3.4.0](https://github.com/collectiveidea/awesome_nested_set/compare/v3.4.0...v3.5.0) 3.4.0 * Keep current scope when calling `roots` [Petrik de Heus](https://github.com/p8) * STI record now can update counter cache correctly [Issei Murasawa](http://github.com/issei-m) * [Compare to 3.3.1](https://github.com/collectiveidea/awesome_nested_set/compare/v3.3.1...v3.4.0) 3.3.1 * Add belongs_to :polymorphic key option only when used [Filippo Liverani](https://github.com/filippoliverani) * [Compare to 3.3.0](https://github.com/collectiveidea/awesome_nested_set/compare/v3.3.0...v3.3.1) 3.3.0 * Update for compatibility with Rails 6.1 which no longer inherits scoping (Messages of the form "DEPRECATION WARNING: Class level methods will no longer inherit scoping from ...") [Marc Rohloff](https://github.com/marcrohloff) * Fix ruby 2.7 keyword parameters deprecation warning [Krisdigital](https://github.com/krisdigital) * [Compare to 3.2.1](https://github.com/collectiveidea/awesome_nested_set/compare/v3.2.1...v3.3.0) 3.2.1 * Don't reload in after_save callback. [Petrik de Heus](https://github.com/p8) * Fix deprecation warning "Passing a range to `#in` is deprecated". [Konstantin Mochalov](https://github.com/kolen) * [Compare to 3.2.0](https://github.com/collectiveidea/awesome_nested_set/compare/v3.2.0...v3.2.1) 3.2.0 * Add support for Rails 6.0 [Stefan Andersen](https://github.com/stfnndrsn) and [Damian Legawiec](https://github.com/damianlegawiec) and [Jonathan Tapia](https://github.com/jtapia) and [Alex](https://github.com/a-ta-ta) * [Compare to 3.1.4](https://github.com/collectiveidea/awesome_nested_set/compare/v3.1.4...v3.2.0) 3.1.4 * Add support for Rails 5.2 [John Hawthorn](https://github.com/jhawthorn) and [marocchino](https://github.com/marocchino) * [Compare to 3.1.3](https://github.com/collectiveidea/awesome_nested_set/compare/v3.1.3...v3.1.4) 3.1.3 * Add support for Rails 5.1 [John Hawthorn](https://github.com/jhawthorn) * [Compare to 3.1.2](https://github.com/collectiveidea/awesome_nested_set/compare/v3.1.2...v3.1.3) 3.1.2 * Make belongs_to relation optional again [Jan Matusz](https://marahin.pl/) * [Compare to 3.0.0](https://github.com/collectiveidea/awesome_nested_set/compare/v3.1.1...v3.1.2) 3.1.1 * Fix a reloading bug when using default scope [Krzysztof Rybka](https://github.com/krzysiek1507) * [Compare to 3.1.0](https://github.com/collectiveidea/awesome_nested_set/compare/v3.1.0...v3.1.1) 3.1.0 * Ensure that nested_set queries respect the model's default_scope. [oesgalha](https://github.com/oesgalha) * Add Rails 5 support [Krzysztof Rybka](https://github.com/krzysiek1507) * Drop support for ruby 1.9.3 [Krzysztof Rybka](https://github.com/krzysiek1507) * Fix .all_roots_valid? method when model is ordered by default [Adam Hodowany](https://github.com/hodak) * Reuse the current model's connection when available [Tim Bugai] [#322](https://github.com/collectiveidea/awesome_nested_set/pull/322) * [Compare to 3.0.3](https://github.com/collectiveidea/awesome_nested_set/compare/v3.0.3...v3.1.0) 3.0.3 * Add :nullify option to :dependent functionality to orphan children rather than destroy them. * [Compare to 3.0.2](https://github.com/collectiveidea/awesome_nested_set/compare/v3.0.2...v3.0.3) 3.0.2 * Fix `dependent: :restrict_with_exception` not allowing a delete to occur. [Brendan Kilfoil] * Replace `Arel::SelectManager#join_sql` with `Arel::SelectManager#join_sources` as `Arel::Node#joins` accepts AST as well. [Swanand Pagnis] * Corrected nested_set_scope usage. [Finbarr Taylor] [#292](https://github.com/collectiveidea/awesome_nested_set/pull/292) * Fix bug: when model with default_scope make #lft and #rgt wrong [eddie](https://github.com/afunction) [#281](https://github.com/collectiveidea/awesome_nested_set/pull/281) * [Compare to 3.0.1](https://github.com/collectiveidea/awesome_nested_set/compare/v3.0.1...v3.0.2) 3.0.1 * Fixed `dependent: :destroy` when called from associated object. #162 [Kuldeep Aggarwal] * [Compare to 3.0.0](https://github.com/collectiveidea/awesome_nested_set/compare/v3.0.0...v3.0.1) 3.0.0 * Support Rails 4.1 [Micah Geisel] * Support dependent: restrict_with_error [Tiago Moraes] * Added information to the README regarding indexes to be added for performance [bdarfler] * Modified associate_parents to add child objects to the parent#children collection [Tiago Moraes] * Fix `dependent: :restrict_with_exception` not allowing a delete to occur. [Brendan Kilfoil] * [Compare to v2.1.6](https://github.com/collectiveidea/awesome_nested_set/compare/v2.1.6...v3.0.0) 2.1.6 * Fixed rebuild! when there is a default_scope with order [Adrian Serafin] * Testing with stable bundler, ruby 2.0, MySQL and PostgreSQL [Philip Arndt] * Optimized move_to for large trees [ericsmith66] 2.1.5 * Worked around issues where AR#association wasn't present on Rails 3.0.x. [Philip Arndt] * Adds option 'order_column' which defaults to 'left_column_name'. [gudata] * Added moving with order functionality. [Sytse Sijbrandij] * Use tablename in all select queries. [Mikhail Dieterle] * Made sure all descendants' depths are updated when moving parent, not just immediate child. [Phil Thompson] * Add documentation of the callbacks. [Tobias Maier] 2.1.4 * nested_set_options accept both Class & AR Relation. [Semyon Perepelitsa] * Reduce the number of queries triggered by the canonical usage of `i.level` in the `nested_set` helpers. [thedarkone] * Specifically require active_record [Bogdan Gusiev] * compute_level now checks for a non nil association target. [Joel Nimety] 2.1.3 * Update child depth when parent node is moved. [Amanda Wagener] * Added move_to_child_with_index. [Ben Zhang] * Optimised self_and_descendants for when there's an index on lft. [Mark Torrance] * Added support for an unsaved record to return the right 'root'. [Philip Arndt] 2.1.2 * Fixed regressions introduced. [Philip Arndt] 2.1.1 * Added 'depth' which indicates how many levels deep the node is. This only works when you have a column called 'depth' in your table, otherwise it doesn't set itself. [Philip Arndt] * Rails 3.2 support added. [Gabriel Sobrinho] * Oracle compatibility added. [Pikender Sharma] * Adding row locking to deletion, locking source of pivot values, and adding retry on collisions. [Markus J. Q. Roberts] * Added method and helper for sorting children by column. [bluegod] * Fixed .all_roots_valid? to work with Postgres. [Joshua Clayton] * Made compatible with polymorphic belongs_to. [Graham Randall] * Added in the association callbacks to the children :has_many association. [Michael Deering] * Modified helper to allow using array of objects as argument. [Rahmat Budiharso] * Fixed cases where we were calling attr_protected. [Jacob Swanner] * Fixed nil cases involving lft and rgt. [Stuart Coyle] and [Patrick Morgan] 2.0.2 * Fixed deprecation warning under Rails 3.1 [Philip Arndt] * Converted Test::Unit matchers to RSpec. [Uģis Ozols] * Added inverse_of to associations to improve performance rendering trees. [Sergio Cambra] * Added row locking and fixed some race conditions. [Markus J. Q. Roberts] 2.0.1 * Fixed a bug with move_to not using nested_set_scope [Andreas Sekine] 2.0.0.pre * Expect Rails 3 * Changed how callbacks work. Returning false in a before_move action does not block save operations. Use a validation or exception in the callback if you need that. * Switched to RSpec * Remove use of Comparable