awesome_nested_set-3.5.0/ 0000755 0000041 0000041 00000000000 14201712704 015422 5 ustar www-data www-data awesome_nested_set-3.5.0/README.md 0000644 0000041 0000041 00000020352 14201712704 016703 0 ustar www-data www-data # Awesome Nested Set
[](https://github.com/collectiveidea/awesome_nested_set/actions/workflows/ci.yml) [](https://codeclimate.com/github/collectiveidea/awesome_nested_set) [](https://hakiri.io/github/collectiveidea/awesome_nested_set/master)
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.
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.5.0/data.tar.gz.sig 0000444 0000041 0000041 00000000600 14201712704 020235 0 ustar www-data www-data ktl!rX=.0)L3>
=LS:fyǍ {RKkIs;myDgOo
צ쇄D0k!D'h[($leװ.OshƊ̈́V ῳGwYa|}ߥWls:{`;S}z&:JJ! 0E#s92@x4Okv uǵH>^aTģvҙ}!zՁFH
oख़ynR1MGJ:
Ի<0wB>BڦCB=.jG*brdQ̯oG_I awesome_nested_set-3.5.0/metadata.gz.sig 0000444 0000041 0000041 00000000600 14201712704 020317 0 ustar www-data www-data 7@9#u5NKy'5/9|8Uj)$֦(wqZ
벐;MF/dNe@8:yVTV+IeǴu#N;k݂hr3K%sƖY:IP֭}rG* FТP=\-[&(A&H!^\2 G',a @9e*k%AzO]pu5Zd3Uؤ~orŰ>/*/
n}Ih0ދD |_⯕1$Z4yS:\衋Ui磡/3yFͣӃ,z5-وR0ɀ=nc awesome_nested_set-3.5.0/checksums.yaml.gz.sig 0000444 0000041 0000041 00000000600 14201712704 021465 0 ustar www-data www-data ̗
:ǿ1@+b4{$/[_Icc7ډk&럪#Z7%$/Vz^{IwIvޞ=u\FC?ƾGG>2NʪGyHV\?( pUFoXonwRzbfM%PzvhL{rOE|td+5g!yŏy؞^
M7kAlzឞ d֛"L6{ `Xf$ay<4o,MjNFD32`aWBgFEQ-B'_s6@%s5 9dN<[X7lHCCk7B{-= awesome_nested_set-3.5.0/lib/ 0000755 0000041 0000041 00000000000 14201712704 016170 5 ustar www-data www-data awesome_nested_set-3.5.0/lib/awesome_nested_set.rb 0000644 0000041 0000041 00000000546 14201712704 022377 0 ustar www-data www-data require '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.5.0/lib/awesome_nested_set/ 0000755 0000041 0000041 00000000000 14201712704 022045 5 ustar www-data www-data awesome_nested_set-3.5.0/lib/awesome_nested_set/version.rb 0000644 0000041 0000041 00000000174 14201712704 024061 0 ustar www-data www-data # frozen_string_literal: true
module AwesomeNestedSet
VERSION = '3.5.0' unless defined?(::AwesomeNestedSet::VERSION)
end
awesome_nested_set-3.5.0/lib/awesome_nested_set/helper.rb 0000644 0000041 0000041 00000003107 14201712704 023652 0 ustar www-data www-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_item+ - Class name or top level times
# * +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_item, mover = nil)
if class_or_item.is_a? Array
items = class_or_item.reject { |e| !e.root? }
else
class_or_item = class_or_item.roots if class_or_item.respond_to?(:scope)
items = Array(class_or_item)
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.5.0/lib/awesome_nested_set/model.rb 0000644 0000041 0000041 00000021744 14201712704 023502 0 ustar www-data www-data require '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.5.0/lib/awesome_nested_set/move.rb 0000644 0000041 0000041 00000012130 14201712704 023335 0 ustar www-data www-data module 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)
raise ImpossibleMove, "Impossible move, target node cannot be inside moved tree."
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.5.0/lib/awesome_nested_set/set_validator.rb 0000644 0000041 0000041 00000003711 14201712704 025234 0 ustar www-data www-data module 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.5.0/lib/awesome_nested_set/awesome_nested_set.rb 0000644 0000041 0000041 00000014041 14201712704 026247 0 ustar www-data www-data require '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.5.0/lib/awesome_nested_set/tree.rb 0000644 0000041 0000041 00000004010 14201712704 023324 0 ustar www-data www-data module 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.5.0/lib/awesome_nested_set/iterator.rb 0000644 0000041 0000041 00000001332 14201712704 024222 0 ustar www-data www-data module 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.5.0/lib/awesome_nested_set/columns.rb 0000644 0000041 0000041 00000004760 14201712704 024061 0 ustar www-data www-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.5.0/lib/awesome_nested_set/model/ 0000755 0000041 0000041 00000000000 14201712704 023145 5 ustar www-data www-data awesome_nested_set-3.5.0/lib/awesome_nested_set/model/prunable.rb 0000644 0000041 0000041 00000006052 14201712704 025305 0 ustar www-data www-data module 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
def destroy_or_delete_descendants
if acts_as_nested_set_options[:dependent] == :destroy
descendants.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
errors.add(:base, :"restrict_dependent_destroy.#{Rails::VERSION::MAJOR < 5 ? 'many' : 'has_many'}", record: record)
return false
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.5.0/lib/awesome_nested_set/model/validatable.rb 0000644 0000041 0000041 00000005150 14201712704 025743 0 ustar www-data www-data require '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.5.0/lib/awesome_nested_set/model/rebuildable.rb 0000644 0000041 0000041 00000002402 14201712704 025742 0 ustar www-data www-data require '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.5.0/lib/awesome_nested_set/model/transactable.rb 0000644 0000041 0000041 00000002210 14201712704 026130 0 ustar www-data www-data module 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.5.0/lib/awesome_nested_set/model/movable.rb 0000644 0000041 0000041 00000011600 14201712704 025115 0 ustar www-data www-data require '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)
move_to node, :child
end
# Move the node to the child of another node with specify index
def move_to_child_with_index(node, index)
if node.children.empty?
move_to_child_of(node)
elsif node.children.count == index
move_to_right_of(node.children.last)
else
my_position = node.children.to_a.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(node.children[index])
elsif my_position && my_position == index
# do nothing. already there.
else
move_to_left_of(node.children[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.5.0/lib/awesome_nested_set/model/relatable.rb 0000644 0000041 0000041 00000007113 14201712704 025427 0 ustar www-data www-data module 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
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.5.0/awesome_nested_set.gemspec 0000644 0000041 0000041 00000010742 14201712704 022650 0 ustar www-data www-data #########################################################
# This file has been automatically generated by gem2tgz #
#########################################################
# -*- encoding: utf-8 -*-
# stub: awesome_nested_set 3.5.0 ruby lib
Gem::Specification.new do |s|
s.name = "awesome_nested_set".freeze
s.version = "3.5.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.cert_chain = ["-----BEGIN CERTIFICATE-----\nMIIEMjCCApqgAwIBAgIBATANBgkqhkiG9w0BAQsFADAjMSEwHwYDVQQDDBhnZW1z\nL0RDPXAvREM9YXJuZHQvREM9aW8wHhcNMjEwNjIzMDkyNzU2WhcNMjIwNjIzMDky\nNzU2WjAjMSEwHwYDVQQDDBhnZW1zL0RDPXAvREM9YXJuZHQvREM9aW8wggGiMA0G\nCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQD0CYnD37uLlJ3Urla2EnnUQ8S6s16k\nAGMpAzpmARo8YwSqtYMJVGyBzUeI7y93Fk9ncswhIFSH/hnh/Ouat/ki9flHlZ+w\nanv0M+9v3wCLyZSC5BQIWpoduFM/fuvLoDUJDWxL50RjwMS0qo2x2LvfQdHN8gn3\nJdSIV5WLJKIvlmIl9S3pw0JO5KRUGn1PcBO7C0S0SlbhVjRHtlao1ycWUULsX109\nhCh39MPGtnZpdCcxheh0TH/UA/jV0/du9/rQdoidkNHkaC24pPfBJ3nS+rAbWaHP\nWmP+0rjfk/XnGBu/HZpKvlnwQjP3QdK4UMtWl8zewqFMNcIiBRALQugnL/SfrP/4\nCSlha9LwkiE6ByeY4WGnNjNqpi5J3IzjEkZRAxG7u9gCB3FzTaBTyXZYI6jplYNw\nTcCJIBHuoPaa+m9brpjb3Uv94nfM97ZP+OmpGYCCAMq4TT7OOV+t8wJc0w8bb0FO\nROhmVNTxrBaNcl6MkZn88EMRCsGgoWklOG0CAwEAAaNxMG8wCQYDVR0TBAIwADAL\nBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFGu7pbmeILyHnBmannuaNRfdN8MsMBoGA1Ud\nEQQTMBGBD2dlbXNAcC5hcm5kdC5pbzAaBgNVHRIEEzARgQ9nZW1zQHAuYXJuZHQu\naW8wDQYJKoZIhvcNAQELBQADggGBANlxc4uAnkPC3zbztG7uZfBfn4HSuvv655Pa\nUaYZ6hNETFrqg78mGs3PkFe2Ru7cVWwckbmH46aq50QoNnx4ClxT03vr03n76Jg1\n8WWHkf0+rcINFlbtIFcmcFrois5Ow3n7pH+xstDtzoWcbh41WwuZStNhrIYsnjAK\n/ovz8D5JlboxceOpVLB/0NiqNEWltK+EMQHmX25Sqf/r5o5rAL9zwEKPFp1Y5X+z\nt2jBjYt2ymr1eMWxux6e+N2uKZL4MblHawxvKlI8UHsIiV9xrc4BwlwlbitcvNIL\nZykdSlpTJd0Guy92iYjCJMC09tMRUNxiVBwD3jRGSeW9YAPIZGXIcVlm6srIRDjJ\no8wB6oOvHAkRXnntOo/4bBDH+ehmgvhh/O/mI+au6C0M430fv+ooH0w08LEXLx1k\ne17ZNASZffbQRP09MH2GZ2AOlkildTX6looWRforZEZi+qamognrozd3MI5QHi1W\nUAZUzHLrrFu7gnkFvLVpxOUf4ItOkA==\n-----END CERTIFICATE-----\n".freeze]
s.date = "2022-02-08"
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.2.5".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", "< 7.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, ["~> 12"])
s.add_development_dependency(%q.freeze, ["~> 4.0.0"])
else
s.add_dependency(%q.freeze, [">= 4.0.0", "< 7.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, ["~> 12"])
s.add_dependency(%q.freeze, ["~> 4.0.0"])
end
end
awesome_nested_set-3.5.0/MIT-LICENSE 0000644 0000041 0000041 00000002050 14201712704 017053 0 ustar www-data www-data Copyright (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.5.0/CHANGELOG 0000644 0000041 0000041 00000017213 14201712704 016640 0 ustar www-data www-data Unreleased version
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