better_nested_set 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +12 -0
- data/Gemfile.lock +18 -0
- data/LICENSE.txt +21 -0
- data/README.rdoc +224 -0
- data/Rakefile +59 -0
- data/VERSION +1 -0
- data/app/helpers/better_nested_set_helper.rb +121 -0
- data/better_nested_set.gemspec +75 -0
- data/lib/better_nested_set.rb +16 -0
- data/lib/symetrie_com/acts_as_better_nested_set.rb +1130 -0
- data/pkg/better_nested_set-0.1.0.gem +0 -0
- data/test/RUNNING_UNIT_TESTS +1 -0
- data/test/abstract_unit.rb +25 -0
- data/test/acts_as_nested_set_test.rb +1368 -0
- data/test/database.yml +15 -0
- data/test/fixtures/mixin.rb +33 -0
- data/test/fixtures/mixins.yml +66 -0
- data/test/mysql.rb +2 -0
- data/test/postgresql.rb +2 -0
- data/test/schema.rb +12 -0
- data/test/sqlite3.rb +2 -0
- metadata +141 -0
data/Gemfile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "bundler", "~> 1.0.0"
|
10
|
+
gem "jeweler", "~> 1.5.2"
|
11
|
+
gem "rcov", ">= 0"
|
12
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
git (1.2.5)
|
5
|
+
jeweler (1.5.2)
|
6
|
+
bundler (~> 1.0.0)
|
7
|
+
git (>= 1.2.5)
|
8
|
+
rake
|
9
|
+
rake (0.8.7)
|
10
|
+
rcov (0.9.9)
|
11
|
+
|
12
|
+
PLATFORMS
|
13
|
+
ruby
|
14
|
+
|
15
|
+
DEPENDENCIES
|
16
|
+
bundler (~> 1.0.0)
|
17
|
+
jeweler (~> 1.5.2)
|
18
|
+
rcov
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2006 Jean-Christophe Michel, Symétrie
|
2
|
+
|
3
|
+
The MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE
|
data/README.rdoc
ADDED
@@ -0,0 +1,224 @@
|
|
1
|
+
= Better nested set
|
2
|
+
|
3
|
+
This plugin provides an enhanced acts_as_nested_set mixin for ActiveRecord, the
|
4
|
+
object-relational mapping layer of the framework Ruby on Rails. The original
|
5
|
+
nested set in Rails lacks many important features, such as moving branches within a tree.
|
6
|
+
|
7
|
+
= Installation
|
8
|
+
|
9
|
+
script/plugin install svn://rubyforge.org/var/svn/betternestedset/trunk
|
10
|
+
|
11
|
+
== Details
|
12
|
+
|
13
|
+
A nested set is a smart way to implement an _ordered_ tree that allows for
|
14
|
+
fast, non-recursive queries. For example, you can fetch all descendants of
|
15
|
+
a node in a single query, no matter how deep the tree. The drawback is that
|
16
|
+
insertions/moves/deletes require complex SQL, but that is handled behind
|
17
|
+
the curtains by this plugin!
|
18
|
+
|
19
|
+
Nested sets are appropriate for ordered trees
|
20
|
+
(e.g. menus, commercial categories) and big trees that must be queried
|
21
|
+
efficiently (e.g. threaded posts).
|
22
|
+
|
23
|
+
See http://www.dbmsmag.com/9603d06.html for nested sets theory, and a tutorial here:
|
24
|
+
http://threebit.net/tutorials/nestedset/tutorial1.html
|
25
|
+
|
26
|
+
== Small nested set theory reminder
|
27
|
+
|
28
|
+
An easy way to visualize how a nested set works is to think of a parent entity surrounding all
|
29
|
+
of its children, and its parent surrounding it, etc. So this tree:
|
30
|
+
root
|
31
|
+
|_ Child 1
|
32
|
+
|_ Child 1.1
|
33
|
+
|_ Child 1.2
|
34
|
+
|_ Child 2
|
35
|
+
|_ Child 2.1
|
36
|
+
|_ Child 2.2
|
37
|
+
|
38
|
+
Could be visualized like this:
|
39
|
+
___________________________________________________________________
|
40
|
+
| Root |
|
41
|
+
| ____________________________ ____________________________ |
|
42
|
+
| | Child 1 | | Child 2 | |
|
43
|
+
| | __________ _________ | | __________ _________ | |
|
44
|
+
| | | C 1.1 | | C 1.2 | | | | C 2.1 | | C 2.2 | | |
|
45
|
+
1 2 3_________4 5________6 7 8 9_________10 11_______12 13 14
|
46
|
+
| |___________________________| |___________________________| |
|
47
|
+
|___________________________________________________________________|
|
48
|
+
|
49
|
+
The numbers represent the left and right boundaries. The table then might
|
50
|
+
look like this:
|
51
|
+
id | parent_id | lft | rgt | data
|
52
|
+
1 | | 1 | 14 | root
|
53
|
+
2 | 1 | 2 | 7 | Child 1
|
54
|
+
3 | 2 | 3 | 4 | Child 1.1
|
55
|
+
4 | 2 | 5 | 6 | Child 1.2
|
56
|
+
5 | 1 | 8 | 13 | Child 2
|
57
|
+
6 | 5 | 9 | 10 | Child 2.1
|
58
|
+
7 | 5 | 11 | 12 | Child 2.2
|
59
|
+
|
60
|
+
To get all children of an entry +parent+, you
|
61
|
+
SELECT * WHERE lft IS BETWEEN parent.lft AND parent.rgt
|
62
|
+
|
63
|
+
To get the number of children, it's
|
64
|
+
(right - left - 1)/2
|
65
|
+
|
66
|
+
To get a node and all its ancestors going back to the root, you
|
67
|
+
SELECT * WHERE node.lft IS BETWEEN lft AND rgt
|
68
|
+
|
69
|
+
As you can see, queries that would be recursive and prohibitively slow on ordinary trees are suddenly quite fast. Nifty, isn't it? There are instance methods for each of the above, plus many others.
|
70
|
+
|
71
|
+
|
72
|
+
= API
|
73
|
+
Method names are mostly the same as in acts_as_tree, to make replacment from one
|
74
|
+
by another easier, except for object creation:
|
75
|
+
|
76
|
+
in acts_as_tree:
|
77
|
+
|
78
|
+
my_item.children.create(:name => "child1")
|
79
|
+
|
80
|
+
in acts_as_nested_set:
|
81
|
+
|
82
|
+
# adds a new item at the "end" of the tree, i.e. with child.left = max(tree.right) + 1
|
83
|
+
child = MyClass.create(:name => "child1")
|
84
|
+
# now move the item to its desired location
|
85
|
+
child.move_to_child_of my_item
|
86
|
+
|
87
|
+
You can use:
|
88
|
+
* <tt>move_to_child_of</tt>
|
89
|
+
* <tt>move_to_right_of</tt>
|
90
|
+
* <tt>move_to_left_of</tt>
|
91
|
+
and pass them an id or an object.
|
92
|
+
|
93
|
+
Other instance methods added by this plugin include:
|
94
|
+
* <tt>root</tt> - root item of the tree (the one that has a nil parent)
|
95
|
+
* <tt>roots</tt> - root items, in case of multiple roots (the ones that have a nil parent)
|
96
|
+
* <tt>level</tt> - number indicating the level, a root being level 0
|
97
|
+
* <tt>ancestors</tt> - array of all parents, with root as first item
|
98
|
+
* <tt>self_and_ancestors</tt> - array of all parents and self
|
99
|
+
* <tt>siblings</tt> - array of all siblings (items sharing the same parent)
|
100
|
+
* <tt>self_and_siblings</tt> - array of itself and all siblings
|
101
|
+
* <tt>children_count</tt> - count of all direct children
|
102
|
+
* <tt>children</tt> - array of all immediate children
|
103
|
+
* <tt>all_children</tt> - array of all children and nested children
|
104
|
+
* <tt>all_children_count</tt> - count of all nested children
|
105
|
+
* <tt>full_set</tt> - array of itself and all children and nested children
|
106
|
+
* <tt>leaves</tt> - array of the children of this node who do not have children
|
107
|
+
* <tt>leaves_count</tt> - the number of leaves
|
108
|
+
* <tt>check_subtree</tt> - check the left/right indexes of this node and all descendants
|
109
|
+
* <tt>check_full_tree</tt> - check the whole tree this node belongs to
|
110
|
+
* <tt>renumber_full_tree</tt> - recreate the left/right indexes for the whole tree
|
111
|
+
|
112
|
+
These should not be of interest, unless you want to write schema-independent SQL:
|
113
|
+
* <tt>left_col_name</tt> - name of the left column passed on the declaration line
|
114
|
+
* <tt>right_col_name</tt> - name of the right column passed on the declaration line
|
115
|
+
* <tt>parent_col_name</tt> - name of the parent column passed on the declaration line
|
116
|
+
|
117
|
+
Please see the generated RDoc files in doc/ for the full API (run 'rake rdoc' if they need to be created).
|
118
|
+
|
119
|
+
== Concurrency and callbacks
|
120
|
+
|
121
|
+
ActiveRecord does not yet provide a way to treat columns as read-only, which causes problems for
|
122
|
+
nested sets and other things (http://dev.rubyonrails.org/ticket/6896). As a workaround, we have overridden
|
123
|
+
ActiveRecord::Base#update to prevent it from writing to the left/right columns. This protects the left/right
|
124
|
+
values from corruption under concurrent usage, but it breaks the update-related callbacks (before_update and friends).
|
125
|
+
If you need the callbacks and aren't worried about concurrency, you can comment out the update method and the two
|
126
|
+
methods below it (all at the very bottom of better_nested_set.rb).
|
127
|
+
|
128
|
+
If this situation bugs you as much as it does us, leave a comment on the above ticket asking the core team to
|
129
|
+
please apply the patch soon.
|
130
|
+
|
131
|
+
|
132
|
+
== Scopes and roots
|
133
|
+
|
134
|
+
Scope separates trees from each other, and roots are nodes without a parent. The complication is that a tree can
|
135
|
+
have multiple ("virtual") roots.
|
136
|
+
|
137
|
+
Virtual roots?! In some situations, such as a menu, the root of the tree is ignored, and becomes a nuisance to the programmer.
|
138
|
+
In that case it makes sense to remove the root, turning each of its children into a 'virtual root'. These virtual roots
|
139
|
+
are still members of the same tree, sharing a single continuous left/right index.
|
140
|
+
|
141
|
+
Here's an example that demonstrates scopes, roots and virtual roots:
|
142
|
+
class Set < ActiveRecord::Base
|
143
|
+
acts_as_nested_set :scope => :tree_id
|
144
|
+
end
|
145
|
+
|
146
|
+
# This will create two trees, each with a single (real) root.
|
147
|
+
a = Set.create(:tree_id => 1)
|
148
|
+
b = Set.create(:tree_id => 2)
|
149
|
+
|
150
|
+
# This will add a second root to tree #2, so it will have two (virtual) roots.
|
151
|
+
# New objects are by default created as virtual roots at the right side of the tree.
|
152
|
+
c = Set.create(:tree_id => 2) # c.lft is 3, c.rgt is 4 -- the lft/rgt values are contiguous between the two roots
|
153
|
+
|
154
|
+
# When we move c to be a child of b, tree #2 will have a single (real) root again.
|
155
|
+
c.move_to_child_of(b)
|
156
|
+
|
157
|
+
# The table would now look like this:
|
158
|
+
id | parent_id | tree_id | lft | rgt | data
|
159
|
+
1 | NULL | 1 | 1 | 2 | a
|
160
|
+
2 | NULL | 2 | 1 | 4 | b
|
161
|
+
3 | 2 | 2 | 2 | 3 | c
|
162
|
+
|
163
|
+
== Recommendations
|
164
|
+
|
165
|
+
Don't name your left and right columns 'left' and 'right', since most databases reserve these words.
|
166
|
+
Use something like 'lft' and 'rgt' instead.
|
167
|
+
|
168
|
+
If you have a choice between multiple separate trees or one large tree with multiple roots, separate trees will
|
169
|
+
offer better performance when altering tree structure (inserts/moves/deletes).
|
170
|
+
|
171
|
+
= Where to find better_nested_set
|
172
|
+
|
173
|
+
This plugin is provided by Jean-Christophe Michel from Symétrie, and the home page is:
|
174
|
+
|
175
|
+
http://opensource.symetrie.com/trac/better_nested_set/
|
176
|
+
|
177
|
+
= What databases?
|
178
|
+
|
179
|
+
The code has so far been tested on MySQL 5, SQLite3 and PostgreSQL 8, but is thought to work on others.
|
180
|
+
Databases featuring transactions will help protect the left/right indexes from corruption during concurrent usage.
|
181
|
+
|
182
|
+
= Compatibility
|
183
|
+
|
184
|
+
Future versions of this code will break compatibility with the original acts_as_nested_set, but this version
|
185
|
+
is intended to be (almost completely) compatible. Differences include:
|
186
|
+
* New records automatically have their left/right values set to place them at the far right of the tree.
|
187
|
+
* Very minor changes to the deprecated method #root?.
|
188
|
+
|
189
|
+
|
190
|
+
= Running the unit tests
|
191
|
+
|
192
|
+
1) Set up a test database as specified in database.yml. Example for MySQL:
|
193
|
+
|
194
|
+
create database acts_as_nested_set_plugin_test;
|
195
|
+
grant all on acts_as_nested_set_plugin_test.* to 'rails'@'localhost' identified by '';
|
196
|
+
|
197
|
+
2) The tests must be run with the plugin installed in a Rails project, so do that if you haven't already.
|
198
|
+
|
199
|
+
3) Run 'rake test_mysql' (or test_sqlite3 or test_postgresql) in plugins/betternestedset. The default rake task attempts to use
|
200
|
+
all three adapters.
|
201
|
+
|
202
|
+
= License
|
203
|
+
|
204
|
+
Copyright (c) 2006 Jean-Christophe Michel, Symétrie
|
205
|
+
|
206
|
+
The MIT License
|
207
|
+
|
208
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
209
|
+
of this software and associated documentation files (the "Software"), to deal
|
210
|
+
in the Software without restriction, including without limitation the rights
|
211
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
212
|
+
copies of the Software, and to permit persons to whom the Software is
|
213
|
+
furnished to do so, subject to the following conditions:
|
214
|
+
|
215
|
+
The above copyright notice and this permission notice shall be included in
|
216
|
+
all copies or substantial portions of the Software.
|
217
|
+
|
218
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
219
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
220
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
221
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
222
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
223
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
224
|
+
THE SOFTWARE
|
data/Rakefile
ADDED
@@ -0,0 +1,59 @@
|
|
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 = "better_nested_set"
|
16
|
+
gem.homepage = "http://github.com/railsbros-dirk/better_nested_set"
|
17
|
+
gem.license = "MIT"
|
18
|
+
gem.summary = %Q{This plugin provides an ehanced acts_as_nested_set mixin for ActiveRecord}
|
19
|
+
gem.description = %Q{This plugin provides an enhanced acts_as_nested_set mixin for ActiveRecord, the object-relational mapping layer of the framework Ruby on Rails. The original nested set in Rails lacks many important features, such as moving branches within a tree.}
|
20
|
+
gem.email = "dirk.breuer@gmail.com"
|
21
|
+
gem.authors = ["Chris Bailey", "Jean-Christophe Michel", "Dirk Breuer"]
|
22
|
+
gem.files += FileList['lib/**/*.rb', 'app/**/*.rb'].to_a
|
23
|
+
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
24
|
+
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
25
|
+
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
26
|
+
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
27
|
+
end
|
28
|
+
Jeweler::RubygemsDotOrgTasks.new
|
29
|
+
|
30
|
+
require 'rake/testtask'
|
31
|
+
desc 'Run tests on all database adapters. See README.'
|
32
|
+
task :default => [:test_mysql, :test_sqlite3, :test_postgresql]
|
33
|
+
|
34
|
+
%w(mysql postgresql sqlite3).each do |adapter|
|
35
|
+
Rake::TestTask.new("test_#{adapter}") { |t|
|
36
|
+
t.libs << 'lib'
|
37
|
+
t.pattern = "test/#{adapter}.rb"
|
38
|
+
t.verbose = true
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
require 'rcov/rcovtask'
|
43
|
+
Rcov::RcovTask.new do |test|
|
44
|
+
test.libs << 'test'
|
45
|
+
test.pattern = 'test/**/test_*.rb'
|
46
|
+
test.verbose = true
|
47
|
+
end
|
48
|
+
|
49
|
+
task :default => :test
|
50
|
+
|
51
|
+
require 'rake/rdoctask'
|
52
|
+
Rake::RDocTask.new do |rdoc|
|
53
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
54
|
+
|
55
|
+
rdoc.rdoc_dir = 'rdoc'
|
56
|
+
rdoc.title = "better_nested_set #{version}"
|
57
|
+
rdoc.rdoc_files.include('README*')
|
58
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
59
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.1
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# This module provides some helpers for the model classes using acts_as_nested_set.
|
2
|
+
# It is included by default in all views. If you need to remove it, edit the last line
|
3
|
+
# of init.rb.
|
4
|
+
#
|
5
|
+
module BetterNestedSetHelper
|
6
|
+
|
7
|
+
# Prints a line of ancestors with links, on the form
|
8
|
+
# root > parent > item
|
9
|
+
#
|
10
|
+
# == Usage
|
11
|
+
# Default is to use links to {your_cotroller}/show with the first string column of your model.
|
12
|
+
# You can tweak this by passing your parameters, or better, pass a block that will receive
|
13
|
+
# an item from your nested set tree and a boolean flag (true for current item) and that
|
14
|
+
# should return the line with the link.
|
15
|
+
#
|
16
|
+
# == Examples
|
17
|
+
#
|
18
|
+
# nested_set_full_outline(category)
|
19
|
+
#
|
20
|
+
# # non standard actions and separators
|
21
|
+
# nested_set_full_outline(category, :action => :search, :separator => ' | ')
|
22
|
+
#
|
23
|
+
# # with a block that will return the link to the item
|
24
|
+
# # note that the current item will lead to another action
|
25
|
+
# nested_set_full_outline(category) { |item, current?|
|
26
|
+
# if current?
|
27
|
+
# link_to "#{item.name} (#{item.})", product_url(:action => :show_category, :category => item.whole_url)
|
28
|
+
# else
|
29
|
+
# link_to "#{item.name} (#{item.})", category_url(:action => :browse, :criteria => item.whole_url)
|
30
|
+
# end
|
31
|
+
# }
|
32
|
+
#
|
33
|
+
# == Params are:
|
34
|
+
# +item+ - the object to display
|
35
|
+
# +hash+ - containing :
|
36
|
+
# * +text_column+ - the title column, defaults to the first string column of the model
|
37
|
+
# * +:action+ - the action to be called (defaults to :show)
|
38
|
+
# * +:controller+ - the controller name (defaults to the model name)
|
39
|
+
# * +:separator+ - the separator (defaults to >)
|
40
|
+
# * +&block+ - a block { |item, current?| ... item.name }
|
41
|
+
#
|
42
|
+
def nested_set_full_outline(item, options={})
|
43
|
+
return if item.nil?
|
44
|
+
raise 'Not a nested set model !' unless item.respond_to?(:acts_as_nested_set_options)
|
45
|
+
|
46
|
+
options = {
|
47
|
+
:text_column => options[:text_column] || item.acts_as_nested_set_options[:text_column],
|
48
|
+
:action => options[:action] || :show,
|
49
|
+
:controller => options[:controller] || item.class.to_s.underscore,
|
50
|
+
:separator => options[:separator] || ' > ' }
|
51
|
+
|
52
|
+
s = ''
|
53
|
+
for it in item.ancestors
|
54
|
+
if block_given?
|
55
|
+
s += yield(it) + options[:separator]
|
56
|
+
else
|
57
|
+
s += link_to( it[options[:text_column]], { :controller => options[:controller], :action => options[:action], :id => it }) + options[:separator]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
if block_given?
|
61
|
+
s + yield(item)
|
62
|
+
else
|
63
|
+
s + h(item[options[:text_column]])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns options for select.
|
68
|
+
# You can exclude some items from the tree.
|
69
|
+
# You can pass a block receiving an item and returning the string displayed in the select.
|
70
|
+
#
|
71
|
+
# == Usage
|
72
|
+
# Default is to use the whole tree and to print the first string column of your model.
|
73
|
+
# You can tweak this by passing your parameters, or better, pass a block that will receive
|
74
|
+
# an item from your nested set tree and that should return the line with the link.
|
75
|
+
#
|
76
|
+
# == Examples
|
77
|
+
#
|
78
|
+
# nested_set_options_for_select(Category)
|
79
|
+
#
|
80
|
+
# # show only a part of the tree, and exclude a category and its subtree
|
81
|
+
# nested_set_options_for_select(selected_category, :exclude => category)
|
82
|
+
#
|
83
|
+
# # add a custom string
|
84
|
+
# nested_set_options_for_select(Category, :exclude => category) { |item| "#{' ' * item.level}#{item.name} (#{item.url})" }
|
85
|
+
#
|
86
|
+
# == Params
|
87
|
+
# * +class_or_item+ - Class name or item or array of items to start the display with
|
88
|
+
# * +text_column+ - the title column, defaults to the first string column of the model
|
89
|
+
# * +&block+ - a block { |item| ... item.name }
|
90
|
+
# If no block passed, uses {|item| "#{'··' * item.level}#{item[text_column]}"}
|
91
|
+
def nested_set_options_for_select(class_or_item, options=nil)
|
92
|
+
# find class
|
93
|
+
if class_or_item.is_a? Class
|
94
|
+
first_item = class_or_item.roots
|
95
|
+
else
|
96
|
+
first_item = class_or_item
|
97
|
+
end
|
98
|
+
return [] if first_item.nil?
|
99
|
+
raise 'Not a nested set model !' unless class_or_item.respond_to?(:acts_as_nested_set_options)
|
100
|
+
|
101
|
+
# exclude some items and all their children
|
102
|
+
if options.is_a? Hash
|
103
|
+
text_column = options[:text_column]
|
104
|
+
options.delete_if { |key, value| key != :exclude }
|
105
|
+
else
|
106
|
+
options = nil
|
107
|
+
end
|
108
|
+
text_column ||= class_or_item.acts_as_nested_set_options[:text_column]
|
109
|
+
|
110
|
+
if first_item.is_a?(Array)
|
111
|
+
tree = first_item.collect{|i| i.full_set(options)}.flatten
|
112
|
+
else
|
113
|
+
tree = first_item.full_set(options)
|
114
|
+
end
|
115
|
+
if block_given?
|
116
|
+
tree.map{|item| [yield(item), item.id] }
|
117
|
+
else
|
118
|
+
tree.map{|item| [ "#{'··' * item.level}#{item[text_column]}", item.id]}
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|