nested_set 1.5.4 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nested_set (1.5.2)
4
+ nested_set (1.5.4)
5
5
  activerecord (>= 3.0.0)
6
6
  railties (>= 3.0.0)
7
7
 
@@ -31,11 +31,20 @@ GEM
31
31
  activesupport (3.0.0)
32
32
  arel (1.0.1)
33
33
  activesupport (~> 3.0.0)
34
+ bench_press (0.3.1)
35
+ activesupport (>= 2.3.5)
36
+ facter (>= 1.5.7)
37
+ httparty (>= 0.6.1)
38
+ jeweler (>= 1.4.0)
34
39
  builder (2.1.2)
40
+ crack (0.1.8)
35
41
  erubis (2.6.6)
36
42
  abstract (>= 1.0.0)
43
+ facter (1.5.8)
37
44
  gemcutter (0.6.1)
38
45
  git (1.2.5)
46
+ httparty (0.6.1)
47
+ crack (= 0.1.8)
39
48
  i18n (0.4.1)
40
49
  jeweler (1.4.0)
41
50
  gemcutter (>= 0.1.0)
@@ -66,6 +75,7 @@ DEPENDENCIES
66
75
  actionpack (>= 3.0.0)
67
76
  activerecord (>= 3.0.0)
68
77
  activesupport (>= 3.0.0)
78
+ bench_press (>= 0.3.1)
69
79
  jeweler
70
80
  nested_set!
71
81
  railties (>= 3.0.0)
@@ -0,0 +1,113 @@
1
+ # NestedSet
2
+
3
+ Nested Set is an implementation of the nested set pattern for ActiveRecord models. It is replacement for acts_as_nested_set and BetterNestedSet, but awesomer. It supports Rails 3.0 and later.
4
+
5
+ ## See, it's Rails 3 only.
6
+
7
+ ## Installation
8
+
9
+ The plugin is available as a gem:
10
+
11
+ gem 'nested_set'
12
+
13
+ Or install as a plugin:
14
+
15
+ rails plugin install git://github.com/skyeagle/nested_set.git
16
+
17
+ ## Usage
18
+
19
+ To make use of nested_set, your model needs to have 3 fields: lft, rgt, and parent_id:
20
+
21
+ class CreateCategories < ActiveRecord::Migration
22
+ def self.up
23
+ create_table :categories do |t|
24
+ t.string :name
25
+ t.integer :parent_id
26
+ t.integer :lft
27
+ t.integer :rgt
28
+
29
+ # Uncomment it to store item level
30
+ # t.integer :depth
31
+ end
32
+ end
33
+
34
+ def self.down
35
+ drop_table :categories
36
+ end
37
+ end
38
+
39
+ Enable the nested set functionality by declaring acts_as_nested_set on your model
40
+
41
+ class Category < ActiveRecord::Base
42
+ acts_as_nested_set
43
+ end
44
+
45
+ Run `rake rdoc` to generate the API docs and see CollectiveIdea::Acts::NestedSet::Base::SingletonMethods for more info.
46
+
47
+ ### Conversion from other trees
48
+
49
+ 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
50
+
51
+ Category.rebuild!
52
+
53
+ Your tree be converted to a valid nested set.
54
+
55
+ ## View Helper
56
+
57
+ The view helper is called #nested_set_options.
58
+
59
+ Example usage:
60
+
61
+ <%= f.select :parent_id, nested_set_options(Category, @category) {|i, level| "#{'-' * level} #{i.name}" } %>
62
+
63
+ <%= select_tag 'parent_id', options_for_select(nested_set_options(Category) {|i, level| "#{'-' * level} #{i.name}" } ) %>
64
+
65
+ or sorted select:
66
+
67
+ <%= f.select :parent_id, sorted_nested_set_options(Category, lambda(&:name)) {|i, level| "#{'-' * level} #{i.name}" } %>
68
+
69
+ <% sort_method = lambda{|x, y| x.name.downcase <=> y.name.downcase} %>
70
+
71
+ NOTE: to sort UTF-8 strings you should use `x.name.mb_chars.downcase`
72
+
73
+ <%= select_tag 'parent_id', options_for_select(sorted_nested_set_options(Category, sort_method){|i, level| "#{'-' * level} #{i.name}" } ) %>
74
+
75
+ See CollectiveIdea::Acts::NestedSet::Helper for more information about the helpers.
76
+
77
+ ## Development
78
+
79
+ bundle install
80
+
81
+ Running tests
82
+
83
+ bundle exec rake test
84
+
85
+ Benchmark tests
86
+
87
+ bundle exec ruby test/benchmark.rb
88
+
89
+ ### References
90
+
91
+ You can learn more about nested sets at:
92
+
93
+ [1](http://www.dbmsmag.com/9603d06.html)
94
+ [2](http://threebit.net/tutorials/nestedset/tutorial1.html)
95
+ [3](http://api.rubyonrails.com/classes/ActiveRecord/Acts/NestedSet/ClassMethods.html)
96
+ [4](http://opensource.symetrie.com/trac/better_nested_set/)
97
+
98
+ ## How to contribute
99
+
100
+ If you find what you might think is a bug:
101
+
102
+ 1. Check the GitHub issue tracker to see if anyone else has had the same issue.
103
+ [Issues tracker](http://github.com/skyeagle/nested_set/issues)
104
+ 2. If you don't see anything, create an issue with information on how to reproduce it.
105
+
106
+ If you want to contribute an enhancement or a fix:
107
+
108
+ 1. Fork the project on github. [http://github.com/skyeagle/nested_set](http://github.com/skyeagle/nested_set)
109
+ 2. Make your changes with tests.
110
+ 3. Commit the changes without making changes to the Rakefile, VERSION, or any other files that aren't related to your enhancement or fix
111
+ 4. Send a pull request.
112
+
113
+ Copyright ©2010 Collective Idea, released under the MIT license
data/Rakefile CHANGED
@@ -16,6 +16,7 @@ begin
16
16
  gem.add_development_dependency "sqlite3-ruby"
17
17
  gem.add_development_dependency "actionpack", ['>= 3.0.0']
18
18
  gem.add_development_dependency "activesupport", ['>= 3.0.0']
19
+ gem.add_development_dependency "bench_press", ['>= 0.3.1']
19
20
  gem.add_development_dependency "jeweler"
20
21
  end
21
22
  Jeweler::GemcutterTasks.new
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.5.4
1
+ 1.6.0
@@ -99,7 +99,7 @@ module CollectiveIdea #:nodoc:
99
99
  where("#{quoted_right_column_name} - #{quoted_left_column_name} = 1").
100
100
  order(quoted_left_column_name)
101
101
  }
102
- scope :with_depth, proc {|level| where(:depth => level).order("lft") }
102
+ scope :with_depth, proc {|level| where(:depth => level).order(quoted_left_column_name) }
103
103
 
104
104
  define_callbacks :move, :terminator => "result == false"
105
105
  end
@@ -113,6 +113,42 @@ module CollectiveIdea #:nodoc:
113
113
  roots.first
114
114
  end
115
115
 
116
+ # Returns arranged nodes hash.
117
+ # I.e. you have this tree:
118
+ #
119
+ # 1
120
+ # 2
121
+ # 3
122
+ # 4
123
+ # 5
124
+ # 6
125
+ # 7
126
+ #
127
+ # Hash will looks like:
128
+ #
129
+ # {1 => {2 => {}, 3 => {4 => {5 => {}}, 6 => {}}}, 7 => {}}
130
+ #
131
+ # == Usage:
132
+ #
133
+ # Categories.arrange
134
+ # Categories.find(42).children.arrange
135
+ # Categories.find(42).descendants.arrange
136
+ # Categories.find(42).self_and_descendants.arrange
137
+ #
138
+ # This arranged hash can be rendered with recursive render_tree helper
139
+ def arrange
140
+ arranged = ActiveSupport::OrderedHash.new
141
+ insertion_points = [arranged]
142
+ depth = 0
143
+ order(quoted_left_column_name).each_with_level do |node, level|
144
+ insertion_points.push insertion_points.last.values.last if level > depth
145
+ (depth - level).times { insertion_points.pop } if level < depth
146
+ insertion_points.last.merge! node => ActiveSupport::OrderedHash.new
147
+ depth = level
148
+ end
149
+ arranged
150
+ end
151
+
116
152
  def valid?
117
153
  left_and_rights_valid? && no_duplicates_for_columns? && all_roots_valid?
118
154
  end
@@ -210,19 +246,17 @@ module CollectiveIdea #:nodoc:
210
246
  # Example:
211
247
  # Category.each_with_level(Category.root.self_and_descendants) do |o, level|
212
248
  #
213
- def each_with_level(objects)
214
- path = [nil]
215
- objects.each do |o|
216
- if o.parent_id != path.last
217
- # we are on a new level, did we decent or ascent?
218
- if path.include?(o.parent_id)
219
- # remove wrong wrong tailing paths elements
220
- path.pop while path.last != o.parent_id
221
- else
222
- path << o.parent_id
223
- end
249
+
250
+ def each_with_level(objects = nil)
251
+ levels = []
252
+ (objects || scoped).each do |i|
253
+ if level = levels.index(i.parent_id)
254
+ levels.slice!((level + 1)..-1)
255
+ else
256
+ levels << i.parent_id
257
+ level = levels.size - 1
224
258
  end
225
- yield(o, path.length - 1)
259
+ yield(i, level)
226
260
  end
227
261
  end
228
262
 
@@ -25,19 +25,13 @@ module CollectiveIdea #:nodoc:
25
25
  class_or_item = class_or_item.roots if class_or_item.is_a?(Class)
26
26
  items = Array(class_or_item)
27
27
  result = []
28
- items.each do |root|
28
+ items.each do |item|
29
29
  levels = []
30
- result += root.self_and_descendants.map do |i|
31
- if level = levels.index(i.parent_id)
32
- levels.slice!((level + 1)..-1)
33
- else
34
- levels << i.parent_id
35
- level = levels.size - 1
36
- end
30
+ item.self_and_descendants.each_with_level do |i, level|
37
31
  if mover.nil? || mover.new_record? || mover.move_possible?(i)
38
- [yield(i, level), i.id]
32
+ result.push([yield(i, level), i.id])
39
33
  end
40
- end.compact
34
+ end
41
35
  end
42
36
  result
43
37
  end
@@ -61,7 +55,7 @@ module CollectiveIdea #:nodoc:
61
55
  #
62
56
  # OR
63
57
  #
64
- # sort_method = lambda{|x,y| x.name.downcase <=> y.name.downcase}
58
+ # sort_method = lambda{|x,y| x.name.mb_chars.downcase <=> y.name.mb_chars.downcase}
65
59
  #
66
60
  # <%= f.select :parent_id, nested_set_options(Category, sort_method) {|i, level|
67
61
  # "#{'–' * level} #{i.name}"
@@ -71,25 +65,51 @@ module CollectiveIdea #:nodoc:
71
65
  class_or_item = class_or_item.roots if class_or_item.is_a?(Class)
72
66
  items = Array(class_or_item)
73
67
  result = []
74
- items.sort_by(&sort_proc).each do |root|
75
- set = root.self_and_descendants
76
- result += build_node(set[0], set, sort_proc, mover, level){|x, level| yield(x, level)}
68
+ items.sort_by(&sort_proc).each do |item|
69
+ hash = item.self_and_descendants.arrange
70
+ result += build_node(hash, sort_proc, mover, level){|x, lvl| yield(x, lvl)}
77
71
  end
78
72
  result
79
73
  end
80
74
 
81
- def build_node(node, set, sort_proc, mover = nil, level = nil)
75
+ def build_node(hash, sort_proc, mover = nil, level = nil)
82
76
  result ||= []
83
- if mover.nil? || mover.new_record? || mover.move_possible?(i)
84
- result << [yield(node, level), node.id]
85
- unless node.leaf?
86
- set.select{|i| i.parent_id == node.id}.sort_by(&sort_proc).map{ |i|
87
- result.push(*build_node(i, set, sort_proc, mover, level.to_i + 1){|x, level| yield(x, level)})
88
- }
77
+ hash.keys.sort_by(&sort_proc).each do |node|
78
+ if mover.nil? || mover.new_record? || mover.move_possible?(node)
79
+ result << [yield(node, level.to_i), node.id]
80
+ result.push(*build_node(hash[node], sort_proc, mover, level.to_i + 1){|x, lvl| yield(x, lvl)})
89
81
  end
90
- end
82
+ end if hash.present?
91
83
  result
92
84
  end
85
+
86
+ # Recursively render arranged nodes hash
87
+ #
88
+ # == Params
89
+ # * +hash+ - Hash or arranged nodes, i.e. Category.arranged
90
+ # * +options+ - HTML options for root ul node.
91
+ # Given options with ex. :sort => lambda{|x| x.name}
92
+ # you allow node sorting by analogy with sorted_nested_set_options helper method
93
+ # * +&block+ - A block that will be used to display node
94
+ #
95
+ # == Usage
96
+ #
97
+ # arranged_nodes = Category.arranged
98
+ #
99
+ # <%= render_tree arranged_nodes do |node, child| %>
100
+ # <li><%= node.name %></li>
101
+ # <%= child %>
102
+ # <% end %>
103
+ #
104
+ def render_tree hash, options = {}, &block
105
+ sort_proc = options.delete :sort
106
+ content_tag :ul, options do
107
+ hash.keys.sort_by(&sort_proc).each do |node|
108
+ block.call node, render_tree(hash[node], :sort => sort_proc, &block)
109
+ end
110
+ end if hash.present?
111
+ end
112
+
93
113
  end
94
114
  end
95
115
  end
@@ -5,55 +5,55 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{nested_set}
8
- s.version = "1.5.4"
8
+ s.version = "1.6.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Brandon Keepers", "Daniel Morrison"]
12
- s.date = %q{2010-11-12}
12
+ s.date = %q{2010-12-11}
13
13
  s.description = %q{An awesome nested set implementation for Active Record}
14
14
  s.email = %q{info@collectiveidea.com}
15
15
  s.extra_rdoc_files = [
16
- "README.rdoc"
16
+ "README.md"
17
17
  ]
18
18
  s.files = [
19
19
  ".autotest",
20
- ".gitignore",
21
- "Gemfile",
22
- "Gemfile.lock",
23
- "MIT-LICENSE",
24
- "README.rdoc",
25
- "Rakefile",
26
- "VERSION",
27
- "init.rb",
28
- "lib/nested_set.rb",
29
- "lib/nested_set/base.rb",
30
- "lib/nested_set/depth.rb",
31
- "lib/nested_set/descendants.rb",
32
- "lib/nested_set/helper.rb",
33
- "lib/nested_set/railtie.rb",
34
- "nested_set.gemspec",
35
- "rails/init.rb",
36
- "test/db/database.yml",
37
- "test/db/schema.rb",
38
- "test/fixtures/categories.yml",
39
- "test/fixtures/category.rb",
40
- "test/fixtures/departments.yml",
41
- "test/fixtures/notes.yml",
42
- "test/nested_set/helper_test.rb",
43
- "test/nested_set_test.rb",
44
- "test/test_helper.rb"
20
+ "Gemfile",
21
+ "Gemfile.lock",
22
+ "MIT-LICENSE",
23
+ "README.md",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "init.rb",
27
+ "lib/nested_set.rb",
28
+ "lib/nested_set/base.rb",
29
+ "lib/nested_set/depth.rb",
30
+ "lib/nested_set/descendants.rb",
31
+ "lib/nested_set/helper.rb",
32
+ "lib/nested_set/railtie.rb",
33
+ "nested_set.gemspec",
34
+ "rails/init.rb",
35
+ "test/benchmarks.rb",
36
+ "test/db/database.yml",
37
+ "test/db/schema.rb",
38
+ "test/fixtures/categories.yml",
39
+ "test/fixtures/category.rb",
40
+ "test/fixtures/departments.yml",
41
+ "test/fixtures/notes.yml",
42
+ "test/nested_set/helper_test.rb",
43
+ "test/nested_set_test.rb",
44
+ "test/test_helper.rb"
45
45
  ]
46
46
  s.homepage = %q{http://github.com/skyeagle/nested_set}
47
- s.rdoc_options = ["--charset=UTF-8"]
48
47
  s.require_paths = ["lib"]
49
48
  s.rubygems_version = %q{1.3.7}
50
49
  s.summary = %q{An awesome nested set implementation for Active Record}
51
50
  s.test_files = [
51
+ "test/benchmarks.rb",
52
+ "test/db/schema.rb",
53
+ "test/fixtures/category.rb",
54
+ "test/nested_set/helper_test.rb",
52
55
  "test/nested_set_test.rb",
53
- "test/nested_set/helper_test.rb",
54
- "test/test_helper.rb",
55
- "test/fixtures/category.rb",
56
- "test/db/schema.rb"
56
+ "test/test_helper.rb"
57
57
  ]
58
58
 
59
59
  if s.respond_to? :specification_version then
@@ -61,26 +61,53 @@ Gem::Specification.new do |s|
61
61
  s.specification_version = 3
62
62
 
63
63
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
64
+ s.add_runtime_dependency(%q<nested_set>, [">= 0"])
65
+ s.add_runtime_dependency(%q<railties>, [">= 3.0.0"])
66
+ s.add_runtime_dependency(%q<activerecord>, [">= 3.0.0"])
67
+ s.add_development_dependency(%q<sqlite3-ruby>, [">= 0"])
68
+ s.add_development_dependency(%q<actionpack>, [">= 3.0.0"])
69
+ s.add_development_dependency(%q<activesupport>, [">= 3.0.0"])
70
+ s.add_development_dependency(%q<bench_press>, [">= 0.3.1"])
71
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
64
72
  s.add_runtime_dependency(%q<railties>, [">= 3.0.0"])
65
73
  s.add_runtime_dependency(%q<activerecord>, [">= 3.0.0"])
66
74
  s.add_development_dependency(%q<sqlite3-ruby>, [">= 0"])
67
75
  s.add_development_dependency(%q<actionpack>, [">= 3.0.0"])
68
76
  s.add_development_dependency(%q<activesupport>, [">= 3.0.0"])
77
+ s.add_development_dependency(%q<bench_press>, [">= 0.3.1"])
69
78
  s.add_development_dependency(%q<jeweler>, [">= 0"])
70
79
  else
80
+ s.add_dependency(%q<nested_set>, [">= 0"])
71
81
  s.add_dependency(%q<railties>, [">= 3.0.0"])
72
82
  s.add_dependency(%q<activerecord>, [">= 3.0.0"])
73
83
  s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
74
84
  s.add_dependency(%q<actionpack>, [">= 3.0.0"])
75
85
  s.add_dependency(%q<activesupport>, [">= 3.0.0"])
86
+ s.add_dependency(%q<bench_press>, [">= 0.3.1"])
87
+ s.add_dependency(%q<jeweler>, [">= 0"])
88
+ s.add_dependency(%q<railties>, [">= 3.0.0"])
89
+ s.add_dependency(%q<activerecord>, [">= 3.0.0"])
90
+ s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
91
+ s.add_dependency(%q<actionpack>, [">= 3.0.0"])
92
+ s.add_dependency(%q<activesupport>, [">= 3.0.0"])
93
+ s.add_dependency(%q<bench_press>, [">= 0.3.1"])
76
94
  s.add_dependency(%q<jeweler>, [">= 0"])
77
95
  end
78
96
  else
97
+ s.add_dependency(%q<nested_set>, [">= 0"])
98
+ s.add_dependency(%q<railties>, [">= 3.0.0"])
99
+ s.add_dependency(%q<activerecord>, [">= 3.0.0"])
100
+ s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
101
+ s.add_dependency(%q<actionpack>, [">= 3.0.0"])
102
+ s.add_dependency(%q<activesupport>, [">= 3.0.0"])
103
+ s.add_dependency(%q<bench_press>, [">= 0.3.1"])
104
+ s.add_dependency(%q<jeweler>, [">= 0"])
79
105
  s.add_dependency(%q<railties>, [">= 3.0.0"])
80
106
  s.add_dependency(%q<activerecord>, [">= 3.0.0"])
81
107
  s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
82
108
  s.add_dependency(%q<actionpack>, [">= 3.0.0"])
83
109
  s.add_dependency(%q<activesupport>, [">= 3.0.0"])
110
+ s.add_dependency(%q<bench_press>, [">= 0.3.1"])
84
111
  s.add_dependency(%q<jeweler>, [">= 0"])
85
112
  end
86
113
  end
@@ -0,0 +1,55 @@
1
+ if $0 == __FILE__
2
+
3
+ plugin_test_dir = File.dirname(__FILE__)
4
+
5
+ $:.unshift(plugin_test_dir + '/../lib')
6
+
7
+ #require 'logger'
8
+ require 'active_support'
9
+ require 'active_record'
10
+ require 'nested_set'
11
+ require 'bench_press'
12
+
13
+
14
+ CollectiveIdea::Acts::NestedSet::Railtie.extend_active_record
15
+ #ActiveRecord::Base.logger = Logger.new(plugin_test_dir + "/debug.log")
16
+ ActiveRecord::Base.configurations = YAML::load(IO.read(plugin_test_dir + "/db/database.yml"))
17
+ ActiveRecord::Base.establish_connection(ENV["DB"] || "sqlite3mem")
18
+ ActiveRecord::Migration.verbose = false
19
+ ActiveRecord::Schema.define(:version => 0) do
20
+ create_table :categories, :force => true do |t|
21
+ t.column :name, :string
22
+ t.column :parent_id, :integer
23
+ t.column :lft, :integer
24
+ t.column :rgt, :integer
25
+ end
26
+ end
27
+
28
+ class Category < ActiveRecord::Base
29
+ acts_as_nested_set
30
+ end
31
+
32
+ Category.delete_all
33
+ Category.create(:name => "Root Node 1")
34
+ Category.create(:name => "Root Node 2")
35
+ 50.times do |i|
36
+ node = Category.create(:name => "Node #{i}")
37
+ set = Category.roots.map{|root| root.self_and_descendants}.flatten
38
+ random_node = set[rand(set.size-1)]
39
+ node.move_to_child_of(random_node)
40
+ end
41
+
42
+ include CollectiveIdea::Acts::NestedSet::Helper
43
+ extend BenchPress
44
+
45
+ reps 100
46
+
47
+ measure "nested_set_options" do
48
+ nested_set_options(Category){|i, level| "#{'-' * level} #{i.name}" }
49
+ end
50
+
51
+ measure "sorted_nested_set_options" do
52
+ sorted_nested_set_options(Category, lambda(&:name)){|i, level| "#{'-' * level} #{i.name}" }
53
+ end
54
+
55
+ end
@@ -5,6 +5,7 @@ ActiveRecord::Schema.define(:version => 0) do
5
5
  t.column :parent_id, :integer
6
6
  t.column :lft, :integer
7
7
  t.column :rgt, :integer
8
+ t.column :depth, :integer, :default => 0
8
9
  t.column :organization_id, :integer
9
10
  end
10
11
 
@@ -12,4 +12,5 @@ class Category < ActiveRecord::Base
12
12
  end
13
13
  }
14
14
  end
15
- end
15
+
16
+ end
@@ -1,77 +1,103 @@
1
1
  require 'test_helper'
2
2
 
3
- module CollectiveIdea
4
- module Acts #:nodoc:
5
- module NestedSet #:nodoc:
6
- class NestedSetTest < ActiveSupport::TestCase
7
- include Helper
8
- fixtures :categories
3
+ class HelperTest < ActionView::TestCase
4
+ include CollectiveIdea::Acts::NestedSet::Helper
5
+ fixtures :categories
9
6
 
10
- def test_nested_set_options
11
- expected = [
12
- [" Top Level", 1],
13
- ["- Child 1", 2],
14
- ['- Child 2', 3],
15
- ['-- Child 2.1', 4],
16
- ['- Child 3', 5],
17
- [" Top Level 2", 6]
18
- ]
19
- actual = nested_set_options(Category) do |c, level|
20
- "#{'-' * level} #{c.name}"
21
- end
22
- assert_equal expected, actual
23
- end
7
+ def test_nested_set_options
8
+ expected = [
9
+ [" Top Level", 1],
10
+ ["- Child 1", 2],
11
+ ['- Child 2', 3],
12
+ ['-- Child 2.1', 4],
13
+ ['- Child 3', 5],
14
+ [" Top Level 2", 6]
15
+ ]
16
+ actual = nested_set_options(Category) do |c, level|
17
+ "#{'-' * level} #{c.name}"
18
+ end
19
+ assert_equal expected, actual
20
+ end
24
21
 
25
- def test_nested_set_options_with_mover
26
- expected = [
27
- [" Top Level", 1],
28
- ["- Child 1", 2],
29
- ['- Child 3', 5],
30
- [" Top Level 2", 6]
31
- ]
32
- actual = nested_set_options(Category, categories(:child_2)) do |c, level|
33
- "#{'-' * level} #{c.name}"
34
- end
35
- assert_equal expected, actual
36
- end
22
+ def test_nested_set_options_with_mover
23
+ expected = [
24
+ [" Top Level", 1],
25
+ ["- Child 1", 2],
26
+ ['- Child 3', 5],
27
+ [" Top Level 2", 6]
28
+ ]
29
+ actual = nested_set_options(Category, categories(:child_2)) do |c, level|
30
+ "#{'-' * level} #{c.name}"
31
+ end
32
+ assert_equal expected, actual
33
+ end
37
34
 
38
- def test_build_node
39
- set = categories(:top_level).self_and_descendants
40
- expected = set.map{|i| [i.name, i.id]}
41
- actual = build_node(set[0], set, lambda(&:lft)){|i, level| i.name }
42
- assert_equal expected, actual
43
- end
35
+ def test_build_node
36
+ set = categories(:top_level).self_and_descendants
37
+ expected = set.map{|i| [i.name, i.id]}
44
38
 
45
- def test_build_node_with_back_id_order
46
- set = categories(:top_level).self_and_descendants
47
- expected = [
48
- ["Top Level", 1],
49
- ["Child 3", 5],
50
- ["Child 2", 3],
51
- ["Child 2.1", 4],
52
- ["Child 1", 2]
53
- ]
54
- actual = build_node(set[0], set, lambda{|x| -x.id}){|i, level| i.name }
55
- assert_equal expected, actual
56
- end
39
+ hash = set.arrange
40
+ actual = build_node(hash, lambda(&:lft)){|i, level| i.name }
41
+ assert_equal expected, actual
42
+ end
57
43
 
58
- def test_sorted_nested_set
59
- expected = [
60
- [" Top Level 2", 6],
61
- [" Top Level", 1],
62
- ['- Child 3', 5],
63
- ['- Child 2', 3],
64
- ['-- Child 2.1', 4],
65
- ["- Child 1", 2]
66
- ]
44
+ def test_build_node_with_back_id_order
45
+ expected = [
46
+ ["Top Level", 1],
47
+ ["Child 3", 5],
48
+ ["Child 2", 3],
49
+ ["Child 2.1", 4],
50
+ ["Child 1", 2]
51
+ ]
67
52
 
68
- actual = sorted_nested_set_options(Category, lambda{|x| -x.id}) do |c, level|
69
- "#{'-' * level} #{c.name}"
70
- end
71
- assert_equal expected, actual
72
- end
53
+ hash = categories(:top_level).self_and_descendants.arrange
54
+ actual = build_node(hash, lambda{|x| -x.id}){|i, level| i.name }
55
+ assert_equal expected, actual
56
+ end
57
+
58
+ def test_sorted_nested_set
59
+ expected = [
60
+ [" Top Level 2", 6],
61
+ [" Top Level", 1],
62
+ ['- Child 3', 5],
63
+ ['- Child 2', 3],
64
+ ['-- Child 2.1', 4],
65
+ ["- Child 1", 2]
66
+ ]
73
67
 
74
- end
68
+ actual = sorted_nested_set_options(Category, lambda{|x| -x.id}) do |c, level|
69
+ "#{'-' * level} #{c.name}"
75
70
  end
71
+ assert_equal expected, actual
76
72
  end
73
+
74
+ def test_sorted_nested_set_with_mover
75
+ expected = [
76
+ [" Top Level 2", 6],
77
+ [" Top Level", 1],
78
+ ['- Child 3', 5],
79
+ ["- Child 1", 2]
80
+ ]
81
+
82
+ actual = sorted_nested_set_options(Category, lambda{|x| -x.id}, categories(:child_2)) do |c, level|
83
+ "#{'-' * level} #{c.name}"
84
+ end
85
+ assert_equal expected, actual
86
+ end
87
+ def test_render_tree
88
+ html = render_tree(Category.arrange) do |category, child|
89
+ concat content_tag(:li, category)
90
+ concat child
91
+ end
92
+ assert_equal html, "<ul><li>Top Level</li><ul><li>Child 1</li><li>Child 2</li><ul><li>Child 2.1</li></ul><li>Child 3</li></ul><li>Top Level 2</li></ul>"
93
+ end
94
+
95
+ def test_sorted_render_tree
96
+ html = render_tree(Category.arrange, :sort => lambda{|x| -x.id}) do |category, child|
97
+ concat content_tag(:li, category)
98
+ concat child
99
+ end
100
+ assert_equal html, "<ul><li>Top Level 2</li><li>Top Level</li><ul><li>Child 3</li><li>Child 2</li><ul><li>Child 2.1</li></ul><li>Child 1</li></ul></ul>"
101
+ end
102
+
77
103
  end
@@ -712,6 +712,15 @@ class NestedSetTest < ActiveSupport::TestCase
712
712
  end
713
713
  end
714
714
 
715
+ def check_scoped_structure(entries, structure)
716
+ structure = structure.dup
717
+ entries.each_with_level do |category, level|
718
+ expected_level, expected_name = structure.shift
719
+ assert_equal expected_name, category.name, "wrong category"
720
+ assert_equal expected_level, level, "wrong level for #{category.name}"
721
+ end
722
+ end
723
+
715
724
  def test_each_with_level
716
725
  levels = [
717
726
  [0, "Top Level"],
@@ -721,6 +730,7 @@ class NestedSetTest < ActiveSupport::TestCase
721
730
  [1, "Child 3" ]]
722
731
 
723
732
  check_structure(Category.root.self_and_descendants, levels)
733
+ check_scoped_structure(Category.root.self_and_descendants, levels)
724
734
 
725
735
  # test some deeper structures
726
736
  category = Category.find_by_name("Child 1")
@@ -746,7 +756,7 @@ class NestedSetTest < ActiveSupport::TestCase
746
756
  [2, "Child 2.1"],
747
757
  [1, "Child 3" ]]
748
758
 
749
- check_structure(Category.root.self_and_descendants, levels)
759
+ check_scoped_structure(Category.root.self_and_descendants, levels)
750
760
  end
751
761
 
752
762
  def test_model_with_attr_accessible
@@ -801,4 +811,55 @@ class NestedSetTest < ActiveSupport::TestCase
801
811
  ensure
802
812
  Category.class_eval { reset_callbacks :move }
803
813
  end
814
+
815
+ # helper to turn arranged hash to levels array
816
+ def hash_to_array hash, level = 0
817
+ array = []
818
+ hash.each do |key, value|
819
+ array.push [level, "#{key}"]
820
+ array += hash_to_array(value, level.next)
821
+ end
822
+ array
823
+ end
824
+
825
+ def test_arrangement
826
+ levels = [
827
+ [0, "Top Level"],
828
+ [1, "Child 1"],
829
+ [1, "Child 2"],
830
+ [2, "Child 2.1"],
831
+ [1, "Child 3"],
832
+ [0, "Top Level 2"]]
833
+
834
+ assert_equal hash_to_array(Category.arrange), levels
835
+
836
+ # some deeper structure
837
+
838
+ category = Category.find_by_name("Child 1")
839
+ c1 = Category.new(:name => "Child 1.1")
840
+ c2 = Category.new(:name => "Child 1.1.1")
841
+ c3 = Category.new(:name => "Child 1.1.1.1")
842
+ c4 = Category.new(:name => "Child 1.2")
843
+ [c1, c2, c3, c4].each(&:save!)
844
+
845
+ c1.move_to_child_of(category)
846
+ c2.move_to_child_of(c1)
847
+ c3.move_to_child_of(c2)
848
+ c4.move_to_child_of(category)
849
+
850
+ levels = [
851
+ [0, "Top Level"],
852
+ [1, "Child 1"],
853
+ [2, "Child 1.1"],
854
+ [3, "Child 1.1.1"],
855
+ [4, "Child 1.1.1.1"],
856
+ [2, "Child 1.2"],
857
+ [1, "Child 2"],
858
+ [2, "Child 2.1"],
859
+ [1, "Child 3" ],
860
+ [0, "Top Level 2"]]
861
+
862
+ assert_equal hash_to_array(Category.arrange), levels
863
+ end
864
+
804
865
  end
@@ -9,6 +9,10 @@ require 'active_support'
9
9
  require 'active_support/test_case'
10
10
  require 'active_record'
11
11
  require 'action_pack'
12
+ require 'action_view'
13
+ require 'action_view/base'
14
+ require 'action_view/template/handlers/erb'
15
+ require 'action_view/test_case'
12
16
  require 'nested_set'
13
17
 
14
18
  CollectiveIdea::Acts::NestedSet::Railtie.extend_active_record
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 1
7
- - 5
8
- - 4
9
- version: 1.5.4
7
+ - 6
8
+ - 0
9
+ version: 1.6.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Brandon Keepers
@@ -15,13 +15,25 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-11-12 00:00:00 +03:00
18
+ date: 2010-12-11 00:00:00 +03:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
- name: railties
23
- prerelease: false
22
+ name: nested_set
24
23
  requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ prerelease: false
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: railties
36
+ requirement: &id002 !ruby/object:Gem::Requirement
25
37
  none: false
26
38
  requirements:
27
39
  - - ">="
@@ -32,11 +44,11 @@ dependencies:
32
44
  - 0
33
45
  version: 3.0.0
34
46
  type: :runtime
35
- version_requirements: *id001
47
+ prerelease: false
48
+ version_requirements: *id002
36
49
  - !ruby/object:Gem::Dependency
37
50
  name: activerecord
38
- prerelease: false
39
- requirement: &id002 !ruby/object:Gem::Requirement
51
+ requirement: &id003 !ruby/object:Gem::Requirement
40
52
  none: false
41
53
  requirements:
42
54
  - - ">="
@@ -47,11 +59,11 @@ dependencies:
47
59
  - 0
48
60
  version: 3.0.0
49
61
  type: :runtime
50
- version_requirements: *id002
62
+ prerelease: false
63
+ version_requirements: *id003
51
64
  - !ruby/object:Gem::Dependency
52
65
  name: sqlite3-ruby
53
- prerelease: false
54
- requirement: &id003 !ruby/object:Gem::Requirement
66
+ requirement: &id004 !ruby/object:Gem::Requirement
55
67
  none: false
56
68
  requirements:
57
69
  - - ">="
@@ -60,11 +72,11 @@ dependencies:
60
72
  - 0
61
73
  version: "0"
62
74
  type: :development
63
- version_requirements: *id003
75
+ prerelease: false
76
+ version_requirements: *id004
64
77
  - !ruby/object:Gem::Dependency
65
78
  name: actionpack
66
- prerelease: false
67
- requirement: &id004 !ruby/object:Gem::Requirement
79
+ requirement: &id005 !ruby/object:Gem::Requirement
68
80
  none: false
69
81
  requirements:
70
82
  - - ">="
@@ -75,11 +87,11 @@ dependencies:
75
87
  - 0
76
88
  version: 3.0.0
77
89
  type: :development
78
- version_requirements: *id004
90
+ prerelease: false
91
+ version_requirements: *id005
79
92
  - !ruby/object:Gem::Dependency
80
93
  name: activesupport
81
- prerelease: false
82
- requirement: &id005 !ruby/object:Gem::Requirement
94
+ requirement: &id006 !ruby/object:Gem::Requirement
83
95
  none: false
84
96
  requirements:
85
97
  - - ">="
@@ -90,11 +102,69 @@ dependencies:
90
102
  - 0
91
103
  version: 3.0.0
92
104
  type: :development
93
- version_requirements: *id005
105
+ prerelease: false
106
+ version_requirements: *id006
107
+ - !ruby/object:Gem::Dependency
108
+ name: bench_press
109
+ requirement: &id007 !ruby/object:Gem::Requirement
110
+ none: false
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ segments:
115
+ - 0
116
+ - 3
117
+ - 1
118
+ version: 0.3.1
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: *id007
94
122
  - !ruby/object:Gem::Dependency
95
123
  name: jeweler
124
+ requirement: &id008 !ruby/object:Gem::Requirement
125
+ none: false
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ segments:
130
+ - 0
131
+ version: "0"
132
+ type: :development
96
133
  prerelease: false
97
- requirement: &id006 !ruby/object:Gem::Requirement
134
+ version_requirements: *id008
135
+ - !ruby/object:Gem::Dependency
136
+ name: railties
137
+ requirement: &id009 !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ segments:
143
+ - 3
144
+ - 0
145
+ - 0
146
+ version: 3.0.0
147
+ type: :runtime
148
+ prerelease: false
149
+ version_requirements: *id009
150
+ - !ruby/object:Gem::Dependency
151
+ name: activerecord
152
+ requirement: &id010 !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ segments:
158
+ - 3
159
+ - 0
160
+ - 0
161
+ version: 3.0.0
162
+ type: :runtime
163
+ prerelease: false
164
+ version_requirements: *id010
165
+ - !ruby/object:Gem::Dependency
166
+ name: sqlite3-ruby
167
+ requirement: &id011 !ruby/object:Gem::Requirement
98
168
  none: false
99
169
  requirements:
100
170
  - - ">="
@@ -103,7 +173,66 @@ dependencies:
103
173
  - 0
104
174
  version: "0"
105
175
  type: :development
106
- version_requirements: *id006
176
+ prerelease: false
177
+ version_requirements: *id011
178
+ - !ruby/object:Gem::Dependency
179
+ name: actionpack
180
+ requirement: &id012 !ruby/object:Gem::Requirement
181
+ none: false
182
+ requirements:
183
+ - - ">="
184
+ - !ruby/object:Gem::Version
185
+ segments:
186
+ - 3
187
+ - 0
188
+ - 0
189
+ version: 3.0.0
190
+ type: :development
191
+ prerelease: false
192
+ version_requirements: *id012
193
+ - !ruby/object:Gem::Dependency
194
+ name: activesupport
195
+ requirement: &id013 !ruby/object:Gem::Requirement
196
+ none: false
197
+ requirements:
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ segments:
201
+ - 3
202
+ - 0
203
+ - 0
204
+ version: 3.0.0
205
+ type: :development
206
+ prerelease: false
207
+ version_requirements: *id013
208
+ - !ruby/object:Gem::Dependency
209
+ name: bench_press
210
+ requirement: &id014 !ruby/object:Gem::Requirement
211
+ none: false
212
+ requirements:
213
+ - - ">="
214
+ - !ruby/object:Gem::Version
215
+ segments:
216
+ - 0
217
+ - 3
218
+ - 1
219
+ version: 0.3.1
220
+ type: :development
221
+ prerelease: false
222
+ version_requirements: *id014
223
+ - !ruby/object:Gem::Dependency
224
+ name: jeweler
225
+ requirement: &id015 !ruby/object:Gem::Requirement
226
+ none: false
227
+ requirements:
228
+ - - ">="
229
+ - !ruby/object:Gem::Version
230
+ segments:
231
+ - 0
232
+ version: "0"
233
+ type: :development
234
+ prerelease: false
235
+ version_requirements: *id015
107
236
  description: An awesome nested set implementation for Active Record
108
237
  email: info@collectiveidea.com
109
238
  executables: []
@@ -111,14 +240,13 @@ executables: []
111
240
  extensions: []
112
241
 
113
242
  extra_rdoc_files:
114
- - README.rdoc
243
+ - README.md
115
244
  files:
116
245
  - .autotest
117
- - .gitignore
118
246
  - Gemfile
119
247
  - Gemfile.lock
120
248
  - MIT-LICENSE
121
- - README.rdoc
249
+ - README.md
122
250
  - Rakefile
123
251
  - VERSION
124
252
  - init.rb
@@ -130,6 +258,7 @@ files:
130
258
  - lib/nested_set/railtie.rb
131
259
  - nested_set.gemspec
132
260
  - rails/init.rb
261
+ - test/benchmarks.rb
133
262
  - test/db/database.yml
134
263
  - test/db/schema.rb
135
264
  - test/fixtures/categories.yml
@@ -144,8 +273,8 @@ homepage: http://github.com/skyeagle/nested_set
144
273
  licenses: []
145
274
 
146
275
  post_install_message:
147
- rdoc_options:
148
- - --charset=UTF-8
276
+ rdoc_options: []
277
+
149
278
  require_paths:
150
279
  - lib
151
280
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -172,8 +301,9 @@ signing_key:
172
301
  specification_version: 3
173
302
  summary: An awesome nested set implementation for Active Record
174
303
  test_files:
175
- - test/nested_set_test.rb
304
+ - test/benchmarks.rb
305
+ - test/db/schema.rb
306
+ - test/fixtures/category.rb
176
307
  - test/nested_set/helper_test.rb
308
+ - test/nested_set_test.rb
177
309
  - test/test_helper.rb
178
- - test/fixtures/category.rb
179
- - test/db/schema.rb
data/.gitignore DELETED
@@ -1,26 +0,0 @@
1
- ## MAC OS
2
- .DS_Store
3
-
4
- ## TEXTMATE
5
- *.tmproj
6
- tmtags
7
-
8
- ## EMACS
9
- *~
10
- \#*
11
- .\#*
12
-
13
- ## VIM
14
- *.swp
15
-
16
- ## PROJECT::GENERAL
17
- coverage
18
- rdoc
19
- pkg
20
-
21
- # PROJECT::SPECIFIC
22
- awesome_nested_set.sqlite3.db
23
- test/debug.log
24
- rdoc
25
- *.sw?
26
- .bundle
@@ -1,99 +0,0 @@
1
- = NestedSet
2
-
3
- Nested Set is an implementation of the nested set pattern for ActiveRecord models. It is replacement for acts_as_nested_set and BetterNestedSet, but awesomer. It supports Rails 3.0 and later.
4
-
5
- === See, it's Rails 3 only.
6
-
7
- == Installation
8
-
9
- The plugin is available as a gem:
10
-
11
- gem 'nested_set'
12
-
13
- Or install as a plugin:
14
-
15
- rails plugin install git://github.com/skyeagle/nested_set.git
16
-
17
- == Usage
18
-
19
- To make use of nested_set, your model needs to have 3 fields: lft, rgt, and parent_id:
20
-
21
- class CreateCategories < ActiveRecord::Migration
22
- def self.up
23
- create_table :categories do |t|
24
- t.string :name
25
- t.integer :parent_id
26
- t.integer :lft
27
- t.integer :rgt
28
-
29
- # Uncomment it to store item level
30
- # t.integer :depth
31
- end
32
- end
33
-
34
- def self.down
35
- drop_table :categories
36
- end
37
- end
38
-
39
- Enable the nested set functionality by declaring acts_as_nested_set on your model
40
-
41
- class Category < ActiveRecord::Base
42
- acts_as_nested_set
43
- end
44
-
45
- Run `rake rdoc` to generate the API docs and see CollectiveIdea::Acts::NestedSet::Base::SingletonMethods for more info.
46
-
47
- == Conversion from other trees
48
-
49
- 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
50
-
51
- Category.rebuild!
52
-
53
- Your tree be converted to a valid nested set.
54
-
55
- == View Helper
56
-
57
- The view helper is called #nested_set_options.
58
-
59
- Example usage:
60
-
61
- <%= f.select :parent_id, nested_set_options(Category, @category) {|i| "#{'-' * i.level} #{i.name}" } %>
62
-
63
- <%= select_tag 'parent_id', options_for_select(nested_set_options(Category) {|i| "#{'-' * i.level} #{i.name}" } ) %>
64
-
65
- or sorted select:
66
-
67
- <%= f.select :parent_id, sorted_nested_set_options(Category, lambda(&:name)) {|i| "#{'-' * i.level} #{i.name}" } %>
68
-
69
- <% sort_method = lambda{|x, y| x.name.downcase <=> y.name.downcase} %>
70
- <%= select_tag 'parent_id', options_for_select(sorted_nested_set_options(Category, sort_method){|i| "#{'-' * i.level} #{i.name}" } ) %>
71
-
72
- See CollectiveIdea::Acts::NestedSet::Helper for more information about the helpers.
73
-
74
- == References
75
-
76
- You can learn more about nested sets at:
77
-
78
- http://www.dbmsmag.com/9603d06.html
79
- http://threebit.net/tutorials/nestedset/tutorial1.html
80
- http://api.rubyonrails.com/classes/ActiveRecord/Acts/NestedSet/ClassMethods.html
81
- http://opensource.symetrie.com/trac/better_nested_set/
82
-
83
- == How to contribute
84
-
85
- If you find what you might think is a bug:
86
-
87
- 1. Check the GitHub issue tracker to see if anyone else has had the same issue.
88
- http://github.com/skyeagle/nested_set/issues/
89
- 2. If you don't see anything, create an issue with information on how to reproduce it.
90
-
91
- If you want to contribute an enhancement or a fix:
92
-
93
- 1. Fork the project on github.
94
- http://github.com/skyeagle/nested_set/
95
- 2. Make your changes with tests.
96
- 3. Commit the changes without making changes to the Rakefile, VERSION, or any other files that aren't related to your enhancement or fix
97
- 4. Send a pull request.
98
-
99
- Copyright ©2010 Collective Idea, released under the MIT license