mongoid_nested_set 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +59 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +147 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/lib/mongoid_nested_set.rb +40 -0
- data/lib/mongoid_nested_set/base.rb +90 -0
- data/lib/mongoid_nested_set/document.rb +207 -0
- data/lib/mongoid_nested_set/fields.rb +60 -0
- data/lib/mongoid_nested_set/outline_number.rb +122 -0
- data/lib/mongoid_nested_set/rebuild.rb +41 -0
- data/lib/mongoid_nested_set/relations.rb +100 -0
- data/lib/mongoid_nested_set/remove_order_by.rb +11 -0
- data/lib/mongoid_nested_set/update.rb +233 -0
- data/lib/mongoid_nested_set/validation.rb +59 -0
- data/mongoid_nested_set.gemspec +100 -0
- data/spec/matchers/nestedset_pos.rb +46 -0
- data/spec/models/circle_node.rb +4 -0
- data/spec/models/node.rb +10 -0
- data/spec/models/node_without_nested_set.rb +6 -0
- data/spec/models/numbering_node.rb +10 -0
- data/spec/models/renamed_fields.rb +7 -0
- data/spec/models/shape_node.rb +18 -0
- data/spec/models/square_node.rb +4 -0
- data/spec/models/test_document.rb +35 -0
- data/spec/models/unscoped_node.rb +9 -0
- data/spec/mongoid_nested_set_spec.rb +723 -0
- data/spec/spec_helper.rb +44 -0
- metadata +196 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
gem "mongoid", ">= 2.0.0.beta.20"
|
4
|
+
|
5
|
+
# Add dependencies to develop your gem here.
|
6
|
+
# Include everything needed to run rake, tests, features, etc.
|
7
|
+
group :development do
|
8
|
+
gem "bundler", "~> 1.0.0"
|
9
|
+
gem "jeweler", "~> 1.5.1"
|
10
|
+
gem "rcov", ">= 0"
|
11
|
+
gem 'rspec-core'
|
12
|
+
end
|
13
|
+
|
14
|
+
group :test do
|
15
|
+
gem 'rspec-expectations'
|
16
|
+
gem 'rr'
|
17
|
+
gem 'remarkable_mongoid'
|
18
|
+
gem 'database_cleaner'
|
19
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activemodel (3.0.3)
|
5
|
+
activesupport (= 3.0.3)
|
6
|
+
builder (~> 2.1.2)
|
7
|
+
i18n (~> 0.4)
|
8
|
+
activesupport (3.0.3)
|
9
|
+
bson (1.1.4)
|
10
|
+
builder (2.1.2)
|
11
|
+
database_cleaner (0.6.0)
|
12
|
+
diff-lcs (1.1.2)
|
13
|
+
git (1.2.5)
|
14
|
+
i18n (0.5.0)
|
15
|
+
jeweler (1.5.1)
|
16
|
+
bundler (~> 1.0.0)
|
17
|
+
git (>= 1.2.5)
|
18
|
+
rake
|
19
|
+
mongo (1.1.4)
|
20
|
+
bson (>= 1.1.1)
|
21
|
+
mongoid (2.0.0.beta.20)
|
22
|
+
activemodel (~> 3.0)
|
23
|
+
mongo (~> 1.1)
|
24
|
+
tzinfo (~> 0.3.22)
|
25
|
+
will_paginate (~> 3.0.pre)
|
26
|
+
rake (0.8.7)
|
27
|
+
rcov (0.9.9)
|
28
|
+
remarkable (4.0.0.alpha4)
|
29
|
+
rspec (>= 2.0.0.alpha11)
|
30
|
+
remarkable_activemodel (4.0.0.alpha4)
|
31
|
+
remarkable (~> 4.0.0.alpha4)
|
32
|
+
rspec (>= 2.0.0.alpha11)
|
33
|
+
remarkable_mongoid (0.5.0)
|
34
|
+
remarkable_activemodel (>= 4.0.0.alpha2)
|
35
|
+
rr (1.0.2)
|
36
|
+
rspec (2.2.0)
|
37
|
+
rspec-core (~> 2.2)
|
38
|
+
rspec-expectations (~> 2.2)
|
39
|
+
rspec-mocks (~> 2.2)
|
40
|
+
rspec-core (2.2.1)
|
41
|
+
rspec-expectations (2.2.0)
|
42
|
+
diff-lcs (~> 1.1.2)
|
43
|
+
rspec-mocks (2.2.0)
|
44
|
+
tzinfo (0.3.23)
|
45
|
+
will_paginate (3.0.pre2)
|
46
|
+
|
47
|
+
PLATFORMS
|
48
|
+
ruby
|
49
|
+
|
50
|
+
DEPENDENCIES
|
51
|
+
bundler (~> 1.0.0)
|
52
|
+
database_cleaner
|
53
|
+
jeweler (~> 1.5.1)
|
54
|
+
mongoid (>= 2.0.0.beta.20)
|
55
|
+
rcov
|
56
|
+
remarkable_mongoid
|
57
|
+
rr
|
58
|
+
rspec-core
|
59
|
+
rspec-expectations
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Brandon Turner
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
Mongoid Nested Set
|
2
|
+
==================
|
3
|
+
|
4
|
+
Mongoid Nested Set is an implementation of the nested set pattern for Mongoid.
|
5
|
+
It is a port of [AwesomeNestedSet for ActiveRecord](https://github.com/galetahub/awesome_nested_set).
|
6
|
+
It supports Mongoid 2 and Rails 3.
|
7
|
+
|
8
|
+
Nested Set represents hierarchies of trees in MongoDB using references rather
|
9
|
+
than embedded documents. A tree is stored as a flat list of documents in a
|
10
|
+
collection. Nested Set allows quick, ordered queries of nodes and their
|
11
|
+
descendants. Tree modification is more costly. The nested set pattern is
|
12
|
+
ideal for models that are read more frequently than modified.
|
13
|
+
|
14
|
+
For more on the nested set pattern: <http://en.wikipedia.org/wiki/Nested_set_model>
|
15
|
+
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
Install as Gem
|
20
|
+
|
21
|
+
gem install mongoid_nested_set
|
22
|
+
|
23
|
+
via Gemfile
|
24
|
+
|
25
|
+
gem 'mongoid_nested_set', '0.1.0'
|
26
|
+
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
To start using Mongoid Nested Set, just declare `acts_as_nested_set` on your
|
31
|
+
model:
|
32
|
+
|
33
|
+
class Category
|
34
|
+
include Mongoid::Document
|
35
|
+
acts_as_nested_set
|
36
|
+
end
|
37
|
+
|
38
|
+
### Creating a root node
|
39
|
+
|
40
|
+
root = Category.create(:name => 'Root Category')
|
41
|
+
|
42
|
+
### Inserting a node
|
43
|
+
|
44
|
+
child1 = root.children.create(:name => 'Child Category #1')
|
45
|
+
|
46
|
+
child2 = Category.create(:name => 'Child Category #2')
|
47
|
+
root.children << child2
|
48
|
+
|
49
|
+
### Deleting a node
|
50
|
+
|
51
|
+
child1.destroy
|
52
|
+
|
53
|
+
Descendants of a destroyed nodes will also be deleted. By default, descendant's
|
54
|
+
`destroy` method will not be called. To enable calling descendant's `destroy`
|
55
|
+
method:
|
56
|
+
|
57
|
+
class Category
|
58
|
+
include Mongoid::Document
|
59
|
+
acts_as_nested_set :dependent => :destroy
|
60
|
+
end
|
61
|
+
|
62
|
+
### Moving a node
|
63
|
+
|
64
|
+
Several methods exist for moving nodes:
|
65
|
+
|
66
|
+
* move\_left
|
67
|
+
* move\_right
|
68
|
+
* move\_to\_left\_of(other_node)
|
69
|
+
* move\_to\_right\_of(other_node)
|
70
|
+
* move\_to\_child\_of(other_node)
|
71
|
+
* move\_to\_root
|
72
|
+
|
73
|
+
|
74
|
+
### Scopes
|
75
|
+
|
76
|
+
Scopes restrict what is considered a list. This is commonly used to represent multiple trees
|
77
|
+
(or multiple roots) in a single collection.
|
78
|
+
|
79
|
+
class Category
|
80
|
+
include Mongoid::Document
|
81
|
+
acts_as_nested_set :scope => :root_id
|
82
|
+
end
|
83
|
+
|
84
|
+
### Conversion from other trees
|
85
|
+
|
86
|
+
Coming from acts_as_tree or adjacency list system where you only have parent_id?
|
87
|
+
No problem. Simply add `acts_as_nested_set` and run:
|
88
|
+
|
89
|
+
Category.rebuild!
|
90
|
+
|
91
|
+
Your tree will be converted to a valid nested set.
|
92
|
+
|
93
|
+
|
94
|
+
### Outline Numbering
|
95
|
+
|
96
|
+
Mongoid Nested Set can manage outline numbers (e.g. 1.3.2) for your documents if
|
97
|
+
you wish. Simply add `:outline_number_field`:
|
98
|
+
|
99
|
+
acts_as_nested_set, :outline_number_field => 'number'
|
100
|
+
|
101
|
+
Your documents will now include a `number` field (you can call it anything you
|
102
|
+
wish) that will contain outline numbers.
|
103
|
+
|
104
|
+
Don't like the outline numbers format? Simply override `outline_number_seperator`,
|
105
|
+
`build_outline_number`, or `outline_number_sequence` in your model classes. For
|
106
|
+
example:
|
107
|
+
|
108
|
+
class Category
|
109
|
+
include Mongoid::Document
|
110
|
+
acts_as_nested_set :scope => :root_id, :outline_number_field => 'number'
|
111
|
+
|
112
|
+
# Use a dash instead of a dot for outline numbers
|
113
|
+
# e.g. 1-3-2
|
114
|
+
def outline_number_seperator
|
115
|
+
'-'
|
116
|
+
end
|
117
|
+
|
118
|
+
# Use 0-based indexing instead of 1-based indexing
|
119
|
+
# e.g. 1.0
|
120
|
+
def outline_number_sequence(prev_siblings)
|
121
|
+
prev_siblings.count
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
## References
|
127
|
+
|
128
|
+
You can learn more about nested sets at:
|
129
|
+
|
130
|
+
<http://en.wikipedia.org/wiki/Nested_set_model>
|
131
|
+
<http://dev.mysql.com/tech-resources/articles/hierarchical-data.html>
|
132
|
+
|
133
|
+
|
134
|
+
## Contributing to mongoid\_nested\_set
|
135
|
+
|
136
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
137
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
138
|
+
* Fork the project
|
139
|
+
* Start a feature/bugfix branch
|
140
|
+
* Commit and push until you are happy with your contribution
|
141
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
142
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
143
|
+
|
144
|
+
## Copyright
|
145
|
+
|
146
|
+
Copyright (c) 2010 Brandon Turner. See LICENSE.txt for
|
147
|
+
further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
+
gem.name = "mongoid_nested_set"
|
16
|
+
gem.homepage = "http://github.com/thinkwell/mongoid_nested_set"
|
17
|
+
gem.license = "MIT"
|
18
|
+
gem.summary = %Q{Nested set based tree implementation for Mongoid}
|
19
|
+
gem.description = %Q{Fully featured tree implementation for Mongoid using the nested set model}
|
20
|
+
gem.email = "bturner@bltweb.net"
|
21
|
+
gem.authors = ["Brandon Turner"]
|
22
|
+
|
23
|
+
gem.add_runtime_dependency('mongoid', '>= 2.0.0.beta.20')
|
24
|
+
end
|
25
|
+
Jeweler::RubygemsDotOrgTasks.new
|
26
|
+
|
27
|
+
require 'rspec/core/rake_task'
|
28
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
29
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
30
|
+
end
|
31
|
+
|
32
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
33
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
34
|
+
spec.rcov = true
|
35
|
+
end
|
36
|
+
|
37
|
+
task :default => :spec
|
38
|
+
|
39
|
+
require 'rake/rdoctask'
|
40
|
+
Rake::RDocTask.new do |rdoc|
|
41
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
42
|
+
|
43
|
+
rdoc.rdoc_dir = 'rdoc'
|
44
|
+
rdoc.title = "mongoid_nested_set #{version}"
|
45
|
+
rdoc.rdoc_files.include('README*')
|
46
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
47
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,40 @@
|
|
1
|
+
|
2
|
+
require 'mongoid_nested_set/remove_order_by'
|
3
|
+
|
4
|
+
# This acts provides Nested Set functionality. Nested Set is a smart way to implement
|
5
|
+
# an _ordered_ tree, with the added feature that you can select the children and all of
|
6
|
+
# their descendants with a single query. The drawback is that insertion or move need
|
7
|
+
# multiple queries. But everything is done here by this module!
|
8
|
+
#
|
9
|
+
# Nested sets are appropriate each time you want either an ordered tree (menus,
|
10
|
+
# commercial categories) or an efficient way of querying big trees (threaded posts).
|
11
|
+
#
|
12
|
+
# == API
|
13
|
+
#
|
14
|
+
# Method names are aligned with acts_as_tree as much as possible to make replacement
|
15
|
+
# from one by another easier.
|
16
|
+
#
|
17
|
+
# item.children.create(:name => 'child1')
|
18
|
+
#
|
19
|
+
module Mongoid
|
20
|
+
module Acts
|
21
|
+
module NestedSet
|
22
|
+
require 'mongoid_nested_set/base'
|
23
|
+
autoload :Document, 'mongoid_nested_set/document'
|
24
|
+
autoload :Fields, 'mongoid_nested_set/fields'
|
25
|
+
autoload :Rebuild, 'mongoid_nested_set/rebuild'
|
26
|
+
autoload :Relations, 'mongoid_nested_set/relations'
|
27
|
+
autoload :Update, 'mongoid_nested_set/update'
|
28
|
+
autoload :Validation, 'mongoid_nested_set/validation'
|
29
|
+
autoload :OutlineNumber, 'mongoid_nested_set/outline_number'
|
30
|
+
|
31
|
+
def self.included(base)
|
32
|
+
base.extend(Base)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
# Enable the acts_as_nested_set method
|
40
|
+
Mongoid::Document::ClassMethods.send(:include, Mongoid::Acts::NestedSet::Base)
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Mongoid::Acts::NestedSet
|
2
|
+
|
3
|
+
module Base
|
4
|
+
|
5
|
+
# Configuration options are:
|
6
|
+
#
|
7
|
+
# * +:parent_field+ - field name to use for keeping the parent id (default: parent_id)
|
8
|
+
# * +:left_field+ - field name for left boundary data, default 'lft'
|
9
|
+
# * +:right_field+ - field name for right boundary data, default 'rgt'
|
10
|
+
# * +:outline_number_field+ - field name for the number field, default nil. If set,
|
11
|
+
# the value will be used as a field name to keep track of each node's
|
12
|
+
# "outline number" (e.g. 1.2.5).
|
13
|
+
# * +:scope+ - restricts what is to be considered a list. Given a symbol, it'll attach
|
14
|
+
# "_id" (if it hasn't been already) and use that as the foreign key restriction. You
|
15
|
+
# can also pass an array to scope by multiple attributes
|
16
|
+
# * +:dependent+ - behavior for cascading destroy. If set to :destroy, all the child
|
17
|
+
# objects are destroyed alongside this object by calling their destroy method. If set
|
18
|
+
# to :delete_all (default), all the child objects are deleted without calling their
|
19
|
+
# destroy method.
|
20
|
+
# * +:klass+ - class to use for queries (defaults to self)
|
21
|
+
#
|
22
|
+
# See Mongoid::Acts::NestedSet::ClassMethods for a list of class methods and
|
23
|
+
# Mongoid::Acts::NestedSet::InstanceMethods for a list of instance methods added to
|
24
|
+
# acts_as_nested_set models
|
25
|
+
def acts_as_nested_set(options = {})
|
26
|
+
options = {
|
27
|
+
:parent_field => 'parent_id',
|
28
|
+
:left_field => 'lft',
|
29
|
+
:right_field => 'rgt',
|
30
|
+
:outline_number_field => nil,
|
31
|
+
:dependent => :delete_all, # or :destroy
|
32
|
+
:klass => self,
|
33
|
+
}.merge(options)
|
34
|
+
|
35
|
+
if options[:scope].is_a?(Symbol) && options[:scope].to_s !~ /_id$/
|
36
|
+
options[:scope] = "#{options[:scope]}_id".intern
|
37
|
+
end
|
38
|
+
|
39
|
+
class_attribute :acts_as_nested_set_options, :instance_writer => false
|
40
|
+
self.acts_as_nested_set_options = options
|
41
|
+
|
42
|
+
unless self.is_a?(Document::ClassMethods)
|
43
|
+
include Document
|
44
|
+
include OutlineNumber if outline_number_field_name
|
45
|
+
|
46
|
+
field left_field_name, :type => Integer
|
47
|
+
field right_field_name, :type => Integer
|
48
|
+
field outline_number_field_name if outline_number_field_name
|
49
|
+
field :depth, :type => Integer
|
50
|
+
|
51
|
+
references_many :children, :class_name => self.name, :foreign_key => parent_field_name, :inverse_of => :parent, :default_order => criteria.asc(left_field_name)
|
52
|
+
referenced_in :parent, :class_name => self.name, :foreign_key => parent_field_name
|
53
|
+
|
54
|
+
attr_accessor :skip_before_destroy
|
55
|
+
|
56
|
+
if accessible_attributes.blank?
|
57
|
+
attr_protected left_field_name.intern, right_field_name.intern
|
58
|
+
end
|
59
|
+
|
60
|
+
define_callbacks :move, :terminator => "result == false"
|
61
|
+
|
62
|
+
before_create :set_default_left_and_right
|
63
|
+
before_save :store_new_parent
|
64
|
+
after_save :move_to_new_parent
|
65
|
+
before_destroy :destroy_descendants
|
66
|
+
|
67
|
+
# no assignment to structure fields
|
68
|
+
[left_field_name, right_field_name].each do |field|
|
69
|
+
module_eval <<-"end_eval", __FILE__, __LINE__
|
70
|
+
def #{field}=(x)
|
71
|
+
raise NameError, "Unauthorized assignment to #{field}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead.", "#{field}"
|
72
|
+
end
|
73
|
+
end_eval
|
74
|
+
end
|
75
|
+
|
76
|
+
scope :roots, lambda {
|
77
|
+
where(parent_field_name => nil).asc(left_field_name)
|
78
|
+
}
|
79
|
+
scope :leaves, lambda {
|
80
|
+
where("this.#{quoted_right_field_name} - this.#{quoted_left_field_name} == 1").asc(left_field_name)
|
81
|
+
}
|
82
|
+
scope :with_depth, lambda { |level|
|
83
|
+
where(:depth => level).asc(left_field_name)
|
84
|
+
}
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
module Mongoid::Acts::NestedSet
|
2
|
+
|
3
|
+
module Document
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
base.send(:include, InstanceMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
include Rebuild
|
14
|
+
include Validation
|
15
|
+
include Fields
|
16
|
+
|
17
|
+
# Returns the first root
|
18
|
+
def root
|
19
|
+
roots.first
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def scope_condition_by_options(options)
|
24
|
+
h = {}
|
25
|
+
scope_string = Array(acts_as_nested_set_options[:scope]).reject{|s| !options.has_key?(s) }.each do |c|
|
26
|
+
h[c] = options[c]
|
27
|
+
end
|
28
|
+
h
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
# Iterates over tree elements and determines the current level in the tree.
|
33
|
+
# Only accepts default ordering, ordering by an other field than lft
|
34
|
+
# does not work. This method is much more efficient then calling level
|
35
|
+
# because it doesn't require any additional database queries.
|
36
|
+
# This method does not used the cached depth field.
|
37
|
+
#
|
38
|
+
# Example:
|
39
|
+
# Category.each_with_level(Category.root.self_and_descendants) do |o, level|
|
40
|
+
#
|
41
|
+
def each_with_level(objects)
|
42
|
+
offset = nil
|
43
|
+
path = [nil]
|
44
|
+
objects.each do |o|
|
45
|
+
if offset == nil
|
46
|
+
offset = o.parent_id.nil? ? 0 : o.parent.level
|
47
|
+
end
|
48
|
+
if o.parent_id != path.last
|
49
|
+
# we are on a new level, did we descend or ascend?
|
50
|
+
if path.include?(o.parent_id)
|
51
|
+
# remove wrong tailing path elements
|
52
|
+
path.pop while path.last != o.parent_id
|
53
|
+
else
|
54
|
+
path << o.parent_id
|
55
|
+
end
|
56
|
+
end
|
57
|
+
yield(o, path.length - 1 + offset)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
# Provides a chainable relation to select all descendants of a set of records,
|
63
|
+
# excluding the record set itself.
|
64
|
+
# Similar to parent.descendants, except this allows you to find all descendants
|
65
|
+
# of a set of nodes, rather than being restricted to find the descendants of only
|
66
|
+
# a single node.
|
67
|
+
#
|
68
|
+
# Example:
|
69
|
+
# parents = Category.roots.all
|
70
|
+
# parents_descendants = Category.where(:deleted => false).descendants_of(parents)
|
71
|
+
#
|
72
|
+
def descendants_of(parents)
|
73
|
+
# TODO: Add root or scope?
|
74
|
+
conditions = parents.map do |parent|
|
75
|
+
{left_field_name => {"$gt" => parent.left}, right_field_name => {"$lt" => parent.right}}
|
76
|
+
end
|
77
|
+
where("$or" => conditions)
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
def before_move(*args, &block)
|
82
|
+
set_callback :move, :before, *args, &block
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def after_move(*args, &block)
|
87
|
+
set_callback :move, :after, *args, &block
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
module InstanceMethods
|
96
|
+
|
97
|
+
include Comparable
|
98
|
+
include Relations
|
99
|
+
include Update
|
100
|
+
include Fields
|
101
|
+
|
102
|
+
# Value fo the parent field
|
103
|
+
def parent_id
|
104
|
+
self[parent_field_name]
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
# Value of the left field
|
109
|
+
def left
|
110
|
+
self[left_field_name]
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
# Value of the right field
|
115
|
+
def right
|
116
|
+
self[right_field_name]
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
# Returns true if this is a root node
|
121
|
+
def root?
|
122
|
+
parent_id.nil?
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
# Returns true if this is a leaf node
|
127
|
+
def leaf?
|
128
|
+
#!new_record? && right - left == 1
|
129
|
+
right - left == 1
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
# Returns true if this is a child node
|
134
|
+
def child?
|
135
|
+
!parent_id.nil?
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
# Returns true if depth is supported
|
140
|
+
def depth?
|
141
|
+
true
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
# Returns true if outline numbering is supported
|
146
|
+
def outline_numbering?
|
147
|
+
!!outline_number_field_name
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
# order by left field
|
152
|
+
def <=>(x)
|
153
|
+
left <=> x.left
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
# Redefine to act like active record
|
158
|
+
def ==(comparison_object)
|
159
|
+
comparison_object.equal?(self) ||
|
160
|
+
(comparison_object.instance_of?(scope_class) &&
|
161
|
+
comparison_object.id == id &&
|
162
|
+
!comparison_object.new_record?)
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
# Check if other model is in the same scope
|
167
|
+
def same_scope?(other)
|
168
|
+
Array(acts_as_nested_set_options[:scope]).all? do |attr|
|
169
|
+
self.send(attr) == other.send(attr)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
def to_text
|
175
|
+
self_and_descendants.map do |node|
|
176
|
+
"#('*'*(node.level+1)} #{node.id} #{node.to_s} (#{node.parent_id}, #{node.left}, #{node.right})"
|
177
|
+
end.join("\n")
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
# All nested set queries should use this nested_set_scope, which performs finds
|
182
|
+
# using the :scope declared in the acts_as_nested_set declaration
|
183
|
+
def nested_set_scope
|
184
|
+
scopes = Array(acts_as_nested_set_options[:scope])
|
185
|
+
conditions = scopes.inject({}) do |conditions,attr|
|
186
|
+
conditions.merge attr => self[attr]
|
187
|
+
end unless scopes.empty?
|
188
|
+
scope_class.criteria.where(conditions).asc(left_field_name)
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
|
193
|
+
protected
|
194
|
+
|
195
|
+
def without_self(scope)
|
196
|
+
scope.where(:_id.ne => self.id)
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
# reload left, right, and parent
|
201
|
+
def reload_nested_set
|
202
|
+
reload
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
end # Document
|
207
|
+
end # Mongoid::Acts::NestedSet
|