acts_as_better_tree 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/Gemfile +8 -0
- data/LICENSE +22 -0
- data/README.md +62 -0
- data/Rakefile +4 -0
- data/acts_as_better_tree.gemspec +17 -0
- data/lib/acts_as_better_tree/version.rb +3 -0
- data/lib/acts_as_better_tree.rb +179 -0
- metadata +56 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Isaac Sloan
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# ActsAsBetterTree
|
2
|
+
|
3
|
+
acts_as_better_tree is great for anyone who needs a fast tree capable of handling millions of nodes without slowing down on writes like nestedset or on reads like a standard tree.
|
4
|
+
It is backwards compatible with acts_as_tree and remains fast with large datasets by storing the ancestry of every node in the field csv_ids.
|
5
|
+
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'acts_as_better_tree'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install acts_as_better_tree
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Required fields are parent_id, root_id, csv_ids.
|
24
|
+
|
25
|
+
create_table :categories do |t|
|
26
|
+
t.column :root_id, :integer
|
27
|
+
t.column :parent_id, :integer
|
28
|
+
t.column :csv_ids, :string
|
29
|
+
t.column :name, :string
|
30
|
+
end
|
31
|
+
|
32
|
+
If upgrading from acts_as_tree just add root_id and csv_ids and run Category.build_csv_ids!
|
33
|
+
|
34
|
+
class Category < ActiveRecord::Base
|
35
|
+
acts_as_better_tree :order => "name"
|
36
|
+
end
|
37
|
+
|
38
|
+
Example:
|
39
|
+
root
|
40
|
+
\_ child1
|
41
|
+
\_ subchild1
|
42
|
+
\_ subchild2
|
43
|
+
|
44
|
+
root = Category.create("name" => "root")
|
45
|
+
child1 = root.children.create("name" => "child1")
|
46
|
+
subchild1 = child1.children.create("name" => "subchild1")
|
47
|
+
|
48
|
+
root.parent # => nil
|
49
|
+
child1.parent # => root
|
50
|
+
root.children # => [child1]
|
51
|
+
root.children.first.children.first # => subchild1
|
52
|
+
|
53
|
+
Copyright (c) 2008 Isaac Sloan, released under the MIT license
|
54
|
+
Inspired by David Heinemeier Hansson's acts_as_tree
|
55
|
+
|
56
|
+
## Contributing
|
57
|
+
|
58
|
+
1. Fork it
|
59
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
60
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
61
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
62
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/acts_as_better_tree/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Isaac Sloan"]
|
6
|
+
gem.email = ["isaac@isaacsloan.com"]
|
7
|
+
gem.description = %q{acts_as_better_tree is great for anyone who needs a fast tree capable of handling millions of nodes without slowing down on writes like nestedset or on reads like a standard tree.}
|
8
|
+
gem.summary = %q{acts_as_better_tree is backwards compatible with acts_as_tree and remains fast with large datasets by storing the ancestry of every node in the field csv_ids.}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "acts_as_better_tree"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = ActsAsBetterTree::VERSION
|
17
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require "acts_as_better_tree/version"
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Acts
|
5
|
+
module BetterTree
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def acts_as_better_tree(options = {}, &b)
|
12
|
+
configuration = {:order => "id ASC", :destroy_dependent => true }
|
13
|
+
configuration.update(options) if options.is_a?(Hash)
|
14
|
+
|
15
|
+
belongs_to :parent, :class_name => name, :foreign_key => :parent_id
|
16
|
+
has_many :children, {:class_name => name, :foreign_key => :parent_id, :order => configuration[:order]}.merge(configuration[:destroy_dependent] ? {:dependent => :destroy} : {}), &b
|
17
|
+
has_many :parents_children, {:class_name => name, :primary_key => :parent_id, :foreign_key => :parent_id, :order => configuration[:order]}, &b
|
18
|
+
belongs_to :root, :class_name => name, :foreign_key => :root_id
|
19
|
+
scope :roots, :order => configuration[:order], :conditions => {:parent_id => nil}
|
20
|
+
after_create :assign_csv_ids
|
21
|
+
after_validation :update_csv_ids, :on => :update
|
22
|
+
|
23
|
+
instance_eval do
|
24
|
+
include ActiveRecord::Acts::BetterTree::InstanceMethods
|
25
|
+
|
26
|
+
def root(options = {})
|
27
|
+
roots(options).first
|
28
|
+
end
|
29
|
+
|
30
|
+
def traverse(nodes = self.roots, &block)
|
31
|
+
nodes.each do |node|
|
32
|
+
yield node
|
33
|
+
traverse(node.children, &block)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Call this to upgrade an existing acts_as_tree to acts_as_better_tree
|
38
|
+
def build_csv_ids!(nodes = self.roots)
|
39
|
+
transaction do
|
40
|
+
traverse(nodes) do |node|
|
41
|
+
node.csv_ids = node.build_csv_ids
|
42
|
+
node.save
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
module InstanceMethods
|
51
|
+
def parent_foreign_key_changed?
|
52
|
+
parent_id_changed?
|
53
|
+
end
|
54
|
+
|
55
|
+
def ancestors
|
56
|
+
if self.csv_ids
|
57
|
+
ids = self.csv_ids.split(',')[0...-1]
|
58
|
+
(@ancestors ||= self.class.where(:id => ids).order('csv_ids ASC'))
|
59
|
+
else
|
60
|
+
node, nodes = self, []
|
61
|
+
nodes << node = node.parent while node.parent
|
62
|
+
(@ancestors ||= nodes.reverse)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self_and_ancestors
|
67
|
+
ancestors + [self]
|
68
|
+
end
|
69
|
+
|
70
|
+
def self_and_children
|
71
|
+
[self] + children
|
72
|
+
end
|
73
|
+
|
74
|
+
def siblings
|
75
|
+
self_and_siblings - [self]
|
76
|
+
end
|
77
|
+
|
78
|
+
def self_and_siblings
|
79
|
+
unless parent_id.blank?
|
80
|
+
self.parents_children
|
81
|
+
else
|
82
|
+
self.class.roots
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def ancestor_of?(node)
|
87
|
+
node.csv_ids.length > self.csv_ids.length && node.csv_ids.starts_with?(self.csv_ids)
|
88
|
+
end
|
89
|
+
|
90
|
+
def descendant_of?(node)
|
91
|
+
self.csv_ids.length > node.csv_ids.length && self.csv_ids.starts_with?(node.csv_ids)
|
92
|
+
end
|
93
|
+
|
94
|
+
def all_children(options = {})
|
95
|
+
find_all_children_with_csv_ids(nil, options)
|
96
|
+
end
|
97
|
+
|
98
|
+
def self_and_all_children
|
99
|
+
[self] + all_children
|
100
|
+
end
|
101
|
+
|
102
|
+
def depth
|
103
|
+
self.csv_ids.scan(/\,/).size
|
104
|
+
end
|
105
|
+
|
106
|
+
def childless?
|
107
|
+
return self.class.where(:parent_id => self.id).first.blank?
|
108
|
+
end
|
109
|
+
|
110
|
+
def root?
|
111
|
+
return self.parent.blank?
|
112
|
+
end
|
113
|
+
|
114
|
+
def move_to_child_of(category)
|
115
|
+
self.update_attributes(:parent_id => category.id)
|
116
|
+
end
|
117
|
+
|
118
|
+
def make_root
|
119
|
+
self.update_attributes(:parent_id => nil)
|
120
|
+
end
|
121
|
+
|
122
|
+
def to_csv(nodes = self.children)
|
123
|
+
csv = []
|
124
|
+
nodes.each do |node|
|
125
|
+
if node.childless?
|
126
|
+
csv += [node.self_and_ancestors.map(&:name).join(",")]
|
127
|
+
else
|
128
|
+
csv += [to_csv(node.children)]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
return csv.join("\n")
|
132
|
+
end
|
133
|
+
|
134
|
+
def build_csv_ids
|
135
|
+
self.parent_id.blank? ? self.id.to_s : "#{self.parent.csv_ids},#{self.id}"
|
136
|
+
end
|
137
|
+
|
138
|
+
protected
|
139
|
+
|
140
|
+
def csv_id_like_pattern(prefix = nil)
|
141
|
+
(prefix || self.csv_ids) + ',%'
|
142
|
+
end
|
143
|
+
|
144
|
+
def build_root_id
|
145
|
+
return (self.parent_id ? self.parent.root_id : self.id)
|
146
|
+
end
|
147
|
+
|
148
|
+
def find_all_children_with_csv_ids(prefix = nil, options = {})
|
149
|
+
conditions = [self.class.send(:sanitize_sql, ['csv_ids LIKE ?', csv_id_like_pattern(prefix)])]
|
150
|
+
conditions << "(#{self.class.send(:sanitize_sql, options[:conditions])})" unless options[:conditions].blank?
|
151
|
+
options.update(:conditions => conditions.join(" AND "))
|
152
|
+
self.class.find(:all, options)
|
153
|
+
end
|
154
|
+
|
155
|
+
def assign_csv_ids
|
156
|
+
self.update_attributes(:csv_ids => build_csv_ids, :root_id => build_root_id)
|
157
|
+
end
|
158
|
+
|
159
|
+
def update_csv_ids
|
160
|
+
return unless parent_foreign_key_changed?
|
161
|
+
old_csv_ids = self.csv_ids
|
162
|
+
self.csv_ids = build_csv_ids
|
163
|
+
self.root_id = build_root_id
|
164
|
+
self.class.update_all("csv_ids = Replace(csv_ids, '#{old_csv_ids},', '#{self.csv_ids},'), root_id = #{self.root_id}", "csv_ids like '#{old_csv_ids},%'")
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
#ActiveRecord::Base.send :include, ActiveRecord::Acts::BetterTree
|
172
|
+
#
|
173
|
+
#ActiveRecord::Base.class_eval do
|
174
|
+
# include ActiveRecord::Acts::BetterTree
|
175
|
+
#end
|
176
|
+
|
177
|
+
class ActiveRecord::Base
|
178
|
+
include ActiveRecord::Acts::BetterTree
|
179
|
+
end
|
metadata
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: acts_as_better_tree
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.3
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Isaac Sloan
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-02 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: acts_as_better_tree is great for anyone who needs a fast tree capable
|
15
|
+
of handling millions of nodes without slowing down on writes like nestedset or on
|
16
|
+
reads like a standard tree.
|
17
|
+
email:
|
18
|
+
- isaac@isaacsloan.com
|
19
|
+
executables: []
|
20
|
+
extensions: []
|
21
|
+
extra_rdoc_files: []
|
22
|
+
files:
|
23
|
+
- .gitignore
|
24
|
+
- Gemfile
|
25
|
+
- LICENSE
|
26
|
+
- README.md
|
27
|
+
- Rakefile
|
28
|
+
- acts_as_better_tree.gemspec
|
29
|
+
- lib/acts_as_better_tree.rb
|
30
|
+
- lib/acts_as_better_tree/version.rb
|
31
|
+
homepage: ''
|
32
|
+
licenses: []
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options: []
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ! '>='
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ! '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubyforge_project:
|
51
|
+
rubygems_version: 1.8.21
|
52
|
+
signing_key:
|
53
|
+
specification_version: 3
|
54
|
+
summary: acts_as_better_tree is backwards compatible with acts_as_tree and remains
|
55
|
+
fast with large datasets by storing the ancestry of every node in the field csv_ids.
|
56
|
+
test_files: []
|