1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
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: <tt>acts_as_nested_set :scope => [:notable_id, :notable_type]</tt>
# * +: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: <tt>acts_as_nested_set :counter_cache => :children_count</tt>
# * +:order_column+ on which column to do sorting, by default it is the left_column_name
# Example: <tt>acts_as_nested_set :order_column => :position</tt>
#
# 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
|