acts_as_tree-2.8.0/ 0000755 0000041 0000041 00000000000 13341233424 014204 5 ustar www-data www-data acts_as_tree-2.8.0/.travis.yml 0000644 0000041 0000041 00000003611 13341233424 016316 0 ustar www-data www-data language: ruby
sudo: false
cache: bundler
before_install: gem install bundler -v '~> 1.15' --conservative --minimal-deps
rvm:
- 1.9.2
- 1.9.3
- 2.0.0
- 2.1.8
- 2.2.4
gemfile:
- gemfiles/rails-3.0.gemfile
- gemfiles/rails-3.1.gemfile
- gemfiles/rails-3.2.gemfile
- gemfiles/rails-4.0.gemfile
- gemfiles/rails-4.1.gemfile
- gemfiles/rails-4.2.gemfile
- gemfiles/rails-5.0.gemfile
- gemfiles/rails-5.1.gemfile
- gemfiles/rails-5.2.gemfile
matrix:
exclude:
# rails 4.x requries ruby 1.9.3 or newer
- rvm: 1.9.2
gemfile: gemfiles/rails-4.0.gemfile
- rvm: 1.9.2
gemfile: gemfiles/rails-4.1.gemfile
- rvm: 1.9.2
gemfile: gemfiles/rails-4.2.gemfile
# rails < 3.2 is unsupported on ruby 2.0+
- rvm: 2.0.0
gemfile: gemfiles/rails-3.0.gemfile
- rvm: 2.0.0
gemfile: gemfiles/rails-3.1.gemfile
- rvm: 2.1.8
gemfile: gemfiles/rails-3.0.gemfile
- rvm: 2.1.8
gemfile: gemfiles/rails-3.1.gemfile
- rvm: 2.2.4
gemfile: gemfiles/rails-3.0.gemfile
- rvm: 2.2.4
gemfile: gemfiles/rails-3.1.gemfile
# rails 5.0 requires ruby 2.2.2+
- rvm: 1.9.2
gemfile: gemfiles/rails-5.0.gemfile
- rvm: 1.9.3
gemfile: gemfiles/rails-5.0.gemfile
- rvm: 2.0.0
gemfile: gemfiles/rails-5.0.gemfile
- rvm: 2.1.8
gemfile: gemfiles/rails-5.0.gemfile
# rails 5.1 requires ruby 2.2.2+
- rvm: 1.9.2
gemfile: gemfiles/rails-5.1.gemfile
- rvm: 1.9.3
gemfile: gemfiles/rails-5.1.gemfile
- rvm: 2.0.0
gemfile: gemfiles/rails-5.1.gemfile
- rvm: 2.1.8
gemfile: gemfiles/rails-5.1.gemfile
# rails 5.2 requires ruby 2.2.2+
- rvm: 1.9.2
gemfile: gemfiles/rails-5.2.gemfile
- rvm: 1.9.3
gemfile: gemfiles/rails-5.2.gemfile
- rvm: 2.0.0
gemfile: gemfiles/rails-5.2.gemfile
- rvm: 2.1.8
gemfile: gemfiles/rails-5.2.gemfile
acts_as_tree-2.8.0/test/ 0000755 0000041 0000041 00000000000 13341233424 015163 5 ustar www-data www-data acts_as_tree-2.8.0/test/acts_as_tree_test.rb 0000644 0000041 0000041 00000045625 13341233424 021217 0 ustar www-data www-data require 'minitest/autorun'
require 'minitest/benchmark'
require 'active_record'
require 'acts_as_tree'
class ActsAsTreeTestCase < (defined?(MiniTest::Test) ? MiniTest::Test : MiniTest::Unit::TestCase)
def assert_queries(num = 1, &block)
query_count, result = count_queries(&block)
result
ensure
assert_equal num, query_count, "#{query_count} instead of #{num} queries were executed."
end
def assert_no_queries(&block)
assert_queries(0, &block)
end
def count_queries &block
count = 0
counter_f = ->(name, started, finished, unique_id, payload) {
unless %w[ CACHE SCHEMA ].include? payload[:name]
count += 1
end
}
begin
subscribed = ActiveSupport::Notifications.subscribe("sql.active_record", &counter_f)
result = block.call
ensure
ActiveSupport::Notifications.unsubscribe subscribed
end
[count, result]
end
def capture_stdout(&block)
real_stdout = $stdout
$stdout = StringIO.new
yield
$stdout.string
ensure
$stdout = real_stdout
end
end
ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
def setup_db(options = {})
# AR keeps printing annoying schema statements
capture_stdout do
ActiveRecord::Base.logger
ActiveRecord::Schema.define(version: 1) do
create_table :mixins, force: true do |t|
t.column :type, :string
t.column :parent_id, :integer
t.column :external_id, :integer if options[:external_ids]
t.column :external_parent_id, :integer if options[:external_ids]
t.column :children_count, :integer, default: 0 if options[:counter_cache]
t.timestamps null: false
end
create_table :level_mixins, force: true do |t|
t.column :level, :string
t.column :parent_id, :integer
t.timestamps null: false
end
end
# Fix broken reset_column_information in some activerecord versions.
if ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 2 ||
ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR == 1
ActiveRecord::Base.connection.schema_cache.clear!
end
Mixin.reset_column_information
end
end
class Mixin < ActiveRecord::Base
include ActsAsTree
end
class LevelMixin < ActiveRecord::Base
include ActsAsTree
acts_as_tree foreign_key: "parent_id", order: "id"
end
class TreeMixin < Mixin
acts_as_tree foreign_key: "parent_id", order: "id"
end
class TreeMixinWithLevelMethod < Mixin
acts_as_tree foreign_key: "parent_id", order: "id"
def level
'Has Level Method'
end
end
class TreeMixinWithoutOrder < Mixin
acts_as_tree foreign_key: "parent_id"
end
class TreeMixinNullify < Mixin
acts_as_tree foreign_key: "parent_id", order: "id", dependent: :nullify
end
class TreeMixinWithCounterCache < Mixin
acts_as_tree foreign_key: "parent_id", order: "id", counter_cache: :children_count
end
class RecursivelyCascadedTreeMixin < Mixin
acts_as_tree foreign_key: "parent_id"
has_one :first_child, class_name: 'RecursivelyCascadedTreeMixin', foreign_key: :parent_id
end
class TreeMixinWithTouch < Mixin
acts_as_tree foreign_key: "parent_id", order: "id", touch: true
end
class ExternalTreeMixin < Mixin
acts_as_tree foreign_key: "external_parent_id", primary_key: "external_id"
end
class ExternalTreeMixinNullify < Mixin
acts_as_tree foreign_key: "external_parent_id", primary_key: "external_id", order: "id", dependent: :nullify
end
class TreeTest < ActsAsTreeTestCase
def setup
setup_db
@tree_mixin = TreeMixin
@root1 = @tree_mixin.create!
@root_child1 = @tree_mixin.create! parent_id: @root1.id
@child1_child = @tree_mixin.create! parent_id: @root_child1.id
@child1_child_child = @tree_mixin.create! parent_id: @child1_child.id
@root_child2 = @tree_mixin.create! parent_id: @root1.id
@root2 = @tree_mixin.create!
@root3 = @tree_mixin.create!
end
def test_children
assert_equal @root1.children, [@root_child1, @root_child2]
assert_equal @root_child1.children, [@child1_child]
assert_equal @child1_child.children, [@child1_child_child]
assert_equal @child1_child_child.children, []
assert_equal @root_child2.children, []
end
def test_parent
assert_equal @root_child1.parent, @root1
assert_equal @root_child1.parent, @root_child2.parent
assert_nil @root1.parent
end
def test_delete
assert_equal 7, @tree_mixin.count
@root1.destroy
assert_equal 2, @tree_mixin.count
@root2.destroy
@root3.destroy
assert_equal 0, @tree_mixin.count
end
def test_insert
@extra = @root1.children.create
assert @extra
assert_equal @extra.parent, @root1
assert_equal 3, @root1.reload.children.count
assert @root1.children.include?(@extra)
assert @root1.children.include?(@root_child1)
assert @root1.children.include?(@root_child2)
end
def test_ancestors
assert_equal [], @root1.ancestors
assert_equal [@root1], @root_child1.ancestors
assert_equal [@root_child1, @root1], @child1_child.ancestors
assert_equal [@root1], @root_child2.ancestors
assert_equal [], @root2.ancestors
assert_equal [], @root3.ancestors
end
def test_root
assert_equal @root1, @tree_mixin.root
assert_equal @root1, @root1.root
assert_equal @root1, @root_child1.root
assert_equal @root1, @child1_child.root
assert_equal @root1, @root_child2.root
assert_equal @root2, @root2.root
assert_equal @root3, @root3.root
end
def test_roots
assert_equal [@root1, @root2, @root3], @tree_mixin.roots
end
def test_leaves
assert_equal [@child1_child_child, @root_child2, @root2, @root3], @tree_mixin.leaves
end
def test_default_tree_order
assert_equal [@root1, @root_child1, @child1_child, @child1_child_child, @root_child2, @root2, @root3], @tree_mixin.default_tree_order
end
def test_siblings
assert_equal [@root2, @root3], @root1.siblings
assert_equal [@root_child2], @root_child1.siblings
assert_equal [], @child1_child.siblings
assert_equal [@root_child1], @root_child2.siblings
assert_equal [@root1, @root3], @root2.siblings
assert_equal [@root1, @root2], @root3.siblings
end
def test_self_and_siblings
assert_equal [@root1, @root2, @root3], @root1.self_and_siblings
assert_equal [@root_child1, @root_child2], @root_child1.self_and_siblings
assert_equal [@child1_child], @child1_child.self_and_siblings
assert_equal [@root_child1, @root_child2], @root_child2.self_and_siblings
assert_equal [@root1, @root2, @root3], @root2.self_and_siblings
assert_equal [@root1, @root2, @root3], @root3.self_and_siblings
end
def test_self_and_children
assert_equal [@root1, @root_child1, @root_child2], @root1.self_and_children
assert_equal [@root2], @root2.self_and_children
end
def test_self_and_ancestors
assert_equal [@child1_child, @root_child1, @root1], @child1_child.self_and_ancestors
assert_equal [@root2], @root2.self_and_ancestors
end
def test_self_and_descendants
assert_equal [@root1, @root_child1, @root_child2, @child1_child, @child1_child_child], @root1.self_and_descendants
assert_equal [@root2], @root2.self_and_descendants
end
def test_descendants
assert_equal [@root_child1, @root_child2, @child1_child, @child1_child_child], @root1.descendants
assert_equal [], @root2.descendants
end
def test_nullify
root4 = TreeMixinNullify.create!
root4_child = TreeMixinNullify.create! parent_id: root4.id
assert_equal 2, TreeMixinNullify.count
assert_equal root4.id, root4_child.parent_id
root4.destroy
assert_equal 1, TreeMixinNullify.count
assert_nil root4_child.reload.parent_id
end
def test_is_root
assert_equal true, @root1.root?
assert_equal true, @root2.root?
assert_equal true, @root3.root?
assert_equal false, @root_child1.root?
assert_equal false, @child1_child.root?
assert_equal false, @child1_child_child.root?
assert_equal false, @root_child2.root?
end
def test_is_leaf
assert_equal true, @root2.leaf?
assert_equal true, @root3.leaf?
assert_equal true, @child1_child_child.leaf?
assert_equal true, @root_child2.leaf?
assert_equal false, @root1.leaf?
assert_equal false, @root_child1.leaf?
assert_equal false, @child1_child.leaf?
end
def test_tree_view
assert_equal false, @tree_mixin.respond_to?(:tree_view)
@tree_mixin.extend ActsAsTree::TreeView
assert_equal true, @tree_mixin.respond_to?(:tree_view)
tree_view_outputs = <<-END.gsub(/^ {6}/, '')
root
|_ 1
| |_ 2
| |_ 3
| |_ 4
| |_ 5
|_ 6
|_ 7
END
assert_equal tree_view_outputs, capture_stdout { @tree_mixin.tree_view(:id) }
end
def test_tree_walker
assert_equal false, @tree_mixin.respond_to?(:walk_tree)
assert_equal false, @tree_mixin.new.respond_to?(:walk_tree)
@tree_mixin.extend ActsAsTree::TreeWalker
assert_equal true, @tree_mixin.respond_to?(:walk_tree)
assert_equal true, @tree_mixin.new.respond_to?(:walk_tree)
walk_tree_dfs_output = <<-END.gsub(/^\s+/, '')
1
-2
--3
---4
-5
6
7
END
assert_equal walk_tree_dfs_output, capture_stdout { @tree_mixin.walk_tree{|elem, level| puts "#{'-'*level}#{elem.id}"} }
walk_tree_dfs_sub_output = <<-END.gsub(/^\s+/, '')
2
-3
--4
5
END
assert_equal walk_tree_dfs_sub_output, capture_stdout { @root1.walk_tree{|elem, level| puts "#{'-'*level}#{elem.id}"} }
walk_tree_bfs_output = <<-END.gsub(/^\s+/, '')
1
6
7
-2
-5
--3
---4
END
assert_equal walk_tree_bfs_output, capture_stdout { @tree_mixin.walk_tree(:algorithm => :bfs){|elem, level| puts "#{'-'*level}#{elem.id}"} }
walk_tree_bfs_sub_output = <<-END.gsub(/^\s+/, '')
2
5
-3
--4
END
assert_equal walk_tree_bfs_sub_output, capture_stdout { @root1.walk_tree(:algorithm => :bfs){|elem, level| puts "#{'-'*level}#{elem.id}"} }
end
end
class TestDeepDescendantsPerformance < ActsAsTreeTestCase
def setup
setup_db
@root1 = TreeMixin.create!
create_cascade_children @root1, "root1", 10
@root2 = TreeMixin.create!
create_cascade_children @root2, "root2", 20
@root3 = TreeMixin.create!
create_cascade_children @root3, "root3", 30
@root4 = TreeMixin.create!
create_cascade_children @root4, "root4", 40
@root5 = TreeMixin.create!
create_cascade_children @root5, "root5", 50
end
def self.bench_range
[1, 2, 3, 4, 5]
end
def bench_descendants
skip("until I deal with the performance difference on travis")
assert_performance_linear 0.99 do |x|
obj = instance_variable_get "@root#{x}"
obj.descendants
end
end
def create_cascade_children parent, parent_name, count
first_child_name = "@#{parent_name}_child1"
first_record = TreeMixin.create! parent_id: parent.id
instance_variable_set first_child_name, first_record
(2...count).each do |child_count|
name = "@#{parent_name}_child#{child_count}"
prev = instance_variable_get "@#{parent_name}_child#{child_count - 1}"
new_record = TreeMixin.create! parent_id: prev.id
instance_variable_set name, new_record
end
end
end
class TreeTestWithEagerLoading < ActsAsTreeTestCase
def setup
setup_db
@root1 = TreeMixin.create!
@root_child1 = TreeMixin.create! parent_id: @root1.id
@child1_child = TreeMixin.create! parent_id: @root_child1.id
@root_child2 = TreeMixin.create! parent_id: @root1.id
@root2 = TreeMixin.create!
@root3 = TreeMixin.create!
@rc1 = RecursivelyCascadedTreeMixin.create!
@rc2 = RecursivelyCascadedTreeMixin.create! parent_id: @rc1.id
@rc3 = RecursivelyCascadedTreeMixin.create! parent_id: @rc2.id
@rc4 = RecursivelyCascadedTreeMixin.create! parent_id: @rc3.id
end
def test_eager_association_loading
roots = TreeMixin.includes(:children)
.where('mixins.parent_id IS NULL')
.order('mixins.id')
assert_equal [@root1, @root2, @root3], roots
assert_no_queries do
assert_equal 2, roots[0].children.size
assert_equal 0, roots[1].children.size
assert_equal 0, roots[2].children.size
end
end
def test_eager_association_loading_with_recursive_cascading_three_levels_has_many
root_node = RecursivelyCascadedTreeMixin.includes({children: {children: :children}})
.order('mixins.id')
.first
assert_equal @rc4, assert_no_queries { root_node.children.first.children.first.children.first }
end
def test_eager_association_loading_with_recursive_cascading_three_levels_has_one
root_node = RecursivelyCascadedTreeMixin.includes({first_child: {first_child: :first_child}})
.order('mixins.id')
.first
assert_equal @rc4, assert_no_queries { root_node.first_child.first_child.first_child }
end
def test_eager_association_loading_with_recursive_cascading_three_levels_belongs_to
leaf_node = RecursivelyCascadedTreeMixin.includes({parent: {parent: :parent}})
.order('mixins.id DESC')
.first
assert_equal @rc1, assert_no_queries { leaf_node.parent.parent.parent }
end
end
class TreeTestWithoutOrder < ActsAsTreeTestCase
def setup
setup_db
@root1 = TreeMixinWithoutOrder.create!
@root2 = TreeMixinWithoutOrder.create!
end
def test_root
assert [@root1, @root2].include? TreeMixinWithoutOrder.root
end
def test_roots
assert_equal [], [@root1, @root2] - TreeMixinWithoutOrder.roots
end
end
class UnsavedTreeTest < ActsAsTreeTestCase
def setup
setup_db
@root = TreeMixin.new
@root_child = @root.children.build
end
def test_inverse_of
# We want children to be aware of their parent before saving either
assert_equal @root, @root_child.parent
end
end
class TreeTestWithCounterCache < ActsAsTreeTestCase
def setup
setup_db counter_cache: true
@root = TreeMixinWithCounterCache.create!
@child1 = TreeMixinWithCounterCache.create! parent_id: @root.id
@child1_child1 = TreeMixinWithCounterCache.create! parent_id: @child1.id
@child2 = TreeMixinWithCounterCache.create! parent_id: @root.id
[@root, @child1, @child1_child1, @child2].map(&:reload)
end
def test_counter_cache
assert_equal 2, @root.children_count
assert_equal 1, @child1.children_count
end
def test_update_parents_counter_cache
@child1_child1.update_attributes(:parent_id => @root.id)
assert_equal 3, @root.reload.children_count
assert_equal 0, @child1.reload.children_count
end
def test_leaves
assert_equal [@child1_child1, @child2], TreeMixinWithCounterCache.leaves
assert !@root.leaf?
assert @child2.leaf?
end
def test_counter_cache_being_used
assert_no_queries { @root.leaf? }
assert_no_queries { @child2.leaf? }
end
end
class TreeTestWithTouch < ActsAsTreeTestCase
def setup
setup_db
@root = TreeMixinWithTouch.create!
@child = TreeMixinWithTouch.create! parent_id: @root.id
end
def test_updated_at
previous_root_updated_at = @root.updated_at
@child.update_attributes(:type => "new_type")
@root.reload
assert @root.updated_at != previous_root_updated_at
end
end
class ExternalTreeTest < TreeTest
def setup
setup_db external_ids: true
@tree_mixin = ExternalTreeMixin
@root1 = @tree_mixin.create! external_id: 1101
@root_child1 = @tree_mixin.create! external_id: 1102, external_parent_id: @root1.external_id
@child1_child = @tree_mixin.create! external_id: 1103, external_parent_id: @root_child1.external_id
@child1_child_child = @tree_mixin.create! external_id: 1104, external_parent_id: @child1_child.external_id
@root_child2 = @tree_mixin.create! external_id: 1105, external_parent_id: @root1.external_id
@root2 = @tree_mixin.create! external_id: 1106
@root3 = @tree_mixin.create! external_id: 1107
end
def test_nullify
root4 = ExternalTreeMixinNullify.create! external_id: 1108
root4_child = ExternalTreeMixinNullify.create! external_id: 1109, external_parent_id: root4.external_id
assert_equal 2, ExternalTreeMixinNullify.count
assert_equal root4.external_id, root4_child.external_parent_id
root4.destroy
assert_equal 1, ExternalTreeMixinNullify.count
assert_nil root4_child.reload.external_parent_id
end
end
class GenerationMethods < ActsAsTreeTestCase
def setup
setup_db
@root1 = TreeMixin.create!
@root_child1 = TreeMixin.create! parent_id: @root1.id
@child1_child = TreeMixin.create! parent_id: @root_child1.id
@child1_child_child = TreeMixin.create! parent_id: @child1_child.id
@root_child2 = TreeMixin.create! parent_id: @root1.id
@root2 = TreeMixin.create!
@root2_child1 = TreeMixin.create! parent_id: @root2.id
@root2_child2 = TreeMixin.create! parent_id: @root2.id
@root2_child1_child = TreeMixin.create! parent_id: @root2_child1.id
@root3 = TreeMixin.create!
@level_column = LevelMixin.create! level: 'Has Level Column'
@level_method = TreeMixinWithLevelMethod.create!
end
def test_generations
assert_equal(
{
0 => [@root1, @root2, @root3],
1 => [@root_child1, @root_child2, @root2_child1, @root2_child2],
2 => [@child1_child, @root2_child1_child],
3 => [@child1_child_child]
},
TreeMixin.generations
)
end
def test_generation
assert_equal [@root2, @root3], @root1.generation
assert_equal [@root_child2, @root2_child1, @root2_child2],
@root_child1.generation
assert_equal [@root2_child1_child], @child1_child.generation
assert_equal [], @child1_child_child.generation
end
def test_self_and_generation
assert_equal [@root1, @root2, @root3], @root1.self_and_generation
assert_equal [@root_child1, @root_child2, @root2_child1, @root2_child2],
@root_child1.self_and_generation
assert_equal [@child1_child, @root2_child1_child],
@child1_child.self_and_generation
assert_equal [@child1_child_child], @child1_child_child.self_and_generation
end
def test_tree_level
assert_equal 0, @root1.tree_level
assert_equal 1, @root_child1.tree_level
assert_equal 2, @child1_child.tree_level
assert_equal 3, @child1_child_child.tree_level
end
def test_level
assert_equal 0, @root1.level
assert_equal 1, @root_child1.level
assert_equal 2, @child1_child.level
assert_equal 3, @child1_child_child.level
end
def test_alias_tree_level
assert_equal 'Has Level Method', @level_method.level
assert_equal 'Has Level Column', @level_column.level
end
end
acts_as_tree-2.8.0/README.md 0000644 0000041 0000041 00000016330 13341233424 015466 0 ustar www-data www-data # ActsAsTree
[](http://travis-ci.org/amerine/acts\_as\_tree)
[](http://badge.fury.io/rb/acts\_as\_tree)
ActsAsTree extends ActiveRecord to add simple support for organizing items into parent–children relationships. By default, ActsAsTree expects a foreign key column called `parent_id`.
## Example
```ruby
class Category < ActiveRecord::Base
acts_as_tree order: "name"
end
root = Category.create("name" => "root")
child1 = root.children.create("name" => "child1")
subchild1 = child1.children.create("name" => "subchild1")
root.parent # => nil
child1.parent # => root
root.children # => [child1]
root.children.first.children.first # => subchild1
```
We also have a convenient `TreeView` module you can mixin if you want a little visual representation of the tree strucuture. Example:
```ruby
class Category < ActiveRecord::Base
extend ActsAsTree::TreeView
acts_as_tree order: 'name'
end
> Category.tree_view(:name)
root
|_ child1
| |_ subchild1
| |_ subchild2
|_ child2
|_ subchild3
|_ subchild4
=> nil
```
And there's a `TreeWalker` module (traversing the tree using depth-first search (default) or breadth-first search) as well. Example given the Model `Page` as
```ruby
class Page < ActiveRecord::Base
extend ActsAsTree::TreeWalker
acts_as_tree order: 'rank'
end
```
In your view you could traverse the tree using
```erb
<% Page.walk_tree do |page, level| %>
<%= link_to "#{'-'*level}#{page.name}", page_path(page) %>
<% end %>
```
You also could use walk\_tree as an instance method such as:
```erb
<% Page.first.walk_tree do |page, level| %>
<%= link_to "#{'-'*level}#{page.name}", page_path(page) %>
<% end %>
```
## Compatibility
We no longer support Ruby 1.8 or versions of Rails/ActiveRecord older than 3.0. If you're using a version of ActiveRecord older than 3.0 please use 0.1.1.
Moving forward we will do our best to support the latest versions of ActiveRecord and Ruby.
## Change Log
* 2.8.0 - August 27, 2018
* Added support for rails 5.2, see #76, #77 -- felixbuenemann
* 2.7.1 - January 30, 2018
* Fix column quoting if the `:order` option is a symbol, see #73, #74 -- felixbuenemann
* 2.7.0 - September 15, 2017
* Added support for rails 5.1, see #67, #68 -- felixbuenemann, marcinwierzbicki
* 2.6.1 - January 18, 2017
* Avoid conflicts of `#level` method with existing column, see #57, #58, #60 -- markhgbrewster
* Fix tests on rails 4.2 with ruby < 2.1 -- felixbuenemann
* 2.6.0 - October 9, 2016
* Add generations methods, see #56 -- markhgbrewster
* 2.5.1 - September 8, 2016
* Fix early database connection in acts\_as\_tree, see #55 -- felixbuenemann
* 2.5.0 - August 14, 2016
* Allow for use of a different primary key, see #50 -- Two9A
* 2.4.0 - January 12, 2016
* Added support for rails 5.0, see #46 -- klacointe
* 2.3.0 - November 6, 2015
* Added touch option to acts\_as\_tree relation. See #40 -- mbenitezm
* Fix tests on rails 3.x with ruby 1.9.2. See #41 -- felixbuenemann
* 2.2.0 - June 15, 2015
* Added TreeWalker.walk\_tree instance method. See #32, #37, #38 -- felixbuenemann, genewoo
* Fix tests on rails 3.x. See #36 -- marshall-lee
* 2.1.0 - September 25, 2014
* Added TreeWalker. See #30 -- 545ch4
* 2.0.0 - July 3, 2014
* Renamed Presentation module to TreeView, see #27, #28 -- felixbuenemann
* 1.6.1 - May 29, 2014
* Readme Improvements, see #26 -- schlick
* Improvements and Fixes for counter cache (fix counter\_cache: true). See #24, #25 -- dv
* Cleanup and fix tests, see #24.
* 1.6.0 - April 21, 2014
* Added new `leaves` method. See #23 -- MichalPokorny
* 1.5.1 - March 28, 2014
* Fixing descendants modification bug. See #20 -- amerine, tmuerell
* 1.5.0 - December 16, 2013
* Added new `descendants` method -- adamkleingit
* Fixed warning message -- akicho8
* 1.4.0 - June 25, 2013
* `Presentation#tree_view` -- rainchen
* `root?` && `leaf?` methods. -- xuanxu
* 1.3.0 - March 29, 2013
* Rails 4.0 Support! -- mischa78
* Readme Fixes -- mischa78 & seanhussey
* 1.2.0 - October 29, 2012
* Adding new `self_with_ancestors` accessor -- felixbuenemann
* `roots` is now a scope.
* 1.1.0 - April 24, 2012
* Deprecate the ActiveRecord::Acts::Tree module in favor of ActsAsTree
* 1.0.1 - April 18, 2012
* Include the Railtie for easier loading in Rails. Will reassess the forced module inclusion in the future.
* 1.0.0 - April 14, 2012
* Official 1.0 release. Force users to include the ActiveRecord::Acts::Tree module.
* 0.2.0 - April 9, 2012
* Rails 3 Support
* 0.1.1 - February 3, 2010
* Bug Fixes
* 0.1.0 - October 9, 2009
* First Gem Release
## Note on Patches/Pull Requests
1. Fork the project.
2. Make your feature addition or bug fix.
3. Add tests for it. This is important so we don't break it in a future version
unintentionally.
4. Commit, do not mess with rakefile, version, or history. (if you want to have
your own version, that is fine but bump version in a commit by itself so we can
ignore when we pull)
5. Send us a pull request. Bonus points for topic branches.
6. All contributors to this project, after their first accepted patch, are given push
access to the repository and are welcome as full contributors to ActsAsTree. All
we ask is that all changes go through CI and a Pull Request before merging.
## Releasing new versions
1. We follow Semver. So if you're shipping interface breaking changes then bump
the major version. We don't care if we ship version 1101.1.1, as long as
people know that 1101.1.1 has breaking differences from 1100.0. If you're
adding new features, but not changing existing functionality bump the minor
version, if you're shipping a bugfix, just bump the patch.
2. Following the above rules, change the version found in lib/acts\_as\_tree/version.rb.
3. Make sure the Change log in the README includes a brief summary of the versions
changes, with credit to the contributors.
4. Commit these changes in one "release-prep" commit (on the master branch).
5. Push that commit up to the repo.
6. Run `rake release`
This will create and push a tag to Github, then generate a gem and push it to
Rubygems.
7. Profit.
## License (MIT)
Copyright (c) 2007 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the 'Software'), to deal in the
Software without restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
acts_as_tree-2.8.0/gemfiles/ 0000755 0000041 0000041 00000000000 13341233424 015777 5 ustar www-data www-data acts_as_tree-2.8.0/gemfiles/rails-4.0.gemfile 0000644 0000041 0000041 00000000144 13341233424 020741 0 ustar www-data www-data source "https://rubygems.org"
gem "rails", "~> 4.0.0"
gem "mime-types", "< 3"
gemspec path: "../"
acts_as_tree-2.8.0/gemfiles/rails-3.1.gemfile 0000644 0000041 0000041 00000000215 13341233424 020740 0 ustar www-data www-data source "https://rubygems.org"
gem "rails", "~> 3.1.0"
gem "i18n", "< 0.7"
gem "rack-cache", "< 1.3"
gem "rake", "< 11"
gemspec path: "../"
acts_as_tree-2.8.0/gemfiles/rails-3.2.gemfile 0000644 0000041 0000041 00000000216 13341233424 020742 0 ustar www-data www-data source "https://rubygems.org"
gem "rails", "~> 3.2.22"
gem "i18n", "< 0.7"
gem "rack-cache", "< 1.3"
gem "rake", "< 11"
gemspec path: "../"
acts_as_tree-2.8.0/gemfiles/rails-5.2.gemfile 0000644 0000041 0000041 00000000114 13341233424 020741 0 ustar www-data www-data source "https://rubygems.org"
gem "rails", "~> 5.2.0"
gemspec path: "../"
acts_as_tree-2.8.0/gemfiles/rails-4.2.gemfile 0000644 0000041 0000041 00000000174 13341233424 020746 0 ustar www-data www-data source "https://rubygems.org"
gem "rails", "~> 4.2.0"
gem "mime-types", "< 3"
gem "nokogiri", "< 1.7"
gemspec path: "../"
acts_as_tree-2.8.0/gemfiles/rails-3.0.gemfile 0000644 0000041 0000041 00000000163 13341233424 020741 0 ustar www-data www-data source "https://rubygems.org"
gem "rails", "~> 3.0.0"
gem "i18n", "< 0.7"
gem "rake", "< 11"
gemspec path: "../"
acts_as_tree-2.8.0/gemfiles/rails-5.1.gemfile 0000644 0000041 0000041 00000000114 13341233424 020740 0 ustar www-data www-data source "https://rubygems.org"
gem "rails", "~> 5.1.0"
gemspec path: "../"
acts_as_tree-2.8.0/gemfiles/rails-4.1.gemfile 0000644 0000041 0000041 00000000144 13341233424 020742 0 ustar www-data www-data source "https://rubygems.org"
gem "rails", "~> 4.1.0"
gem "mime-types", "< 3"
gemspec path: "../"
acts_as_tree-2.8.0/gemfiles/rails-5.0.gemfile 0000644 0000041 0000041 00000000114 13341233424 020737 0 ustar www-data www-data source "https://rubygems.org"
gem "rails", "~> 5.0.0"
gemspec path: "../"
acts_as_tree-2.8.0/.gitignore 0000644 0000041 0000041 00000000100 13341233424 016163 0 ustar www-data www-data *.gem
.bundle
pkg/*
.rvmrc
Gemfile.lock
gemfiles/*.gemfile.lock
acts_as_tree-2.8.0/Rakefile 0000644 0000041 0000041 00000000751 13341233424 015654 0 ustar www-data www-data require 'bundler/gem_tasks'
desc "Run the tests."
task :test do
$: << "lib" << "test"
Dir["test/*_test.rb"].each { |f| require f[5..-4] }
end
task :default => :test
# Run the rdoc task to generate rdocs for this gem
require 'rdoc/task'
RDoc::Task.new do |rdoc|
require "acts_as_tree/version"
version = ActsAsTree::VERSION
rdoc.rdoc_dir = 'rdoc'
rdoc.title = "acts_as_tree-rails3 #{version}"
rdoc.rdoc_files.include('README*')
rdoc.rdoc_files.include('lib/**/*.rb')
end
acts_as_tree-2.8.0/lib/ 0000755 0000041 0000041 00000000000 13341233424 014752 5 ustar www-data www-data acts_as_tree-2.8.0/lib/acts_as_tree/ 0000755 0000041 0000041 00000000000 13341233424 017406 5 ustar www-data www-data acts_as_tree-2.8.0/lib/acts_as_tree/version.rb 0000644 0000041 0000041 00000000052 13341233424 021415 0 ustar www-data www-data module ActsAsTree
VERSION = "2.8.0"
end
acts_as_tree-2.8.0/lib/acts_as_tree/railtie.rb 0000644 0000041 0000041 00000000361 13341233424 021364 0 ustar www-data www-data module ActsAsTree
class Railtie < Rails::Railtie
initializer 'acts_as_tree.insert_into_active_record' do
ActiveSupport.on_load :active_record do
ActiveRecord::Base.send(:include, ActsAsTree)
end
end
end
end
acts_as_tree-2.8.0/lib/acts_as_tree/active_record/ 0000755 0000041 0000041 00000000000 13341233424 022217 5 ustar www-data www-data acts_as_tree-2.8.0/lib/acts_as_tree/active_record/acts/ 0000755 0000041 0000041 00000000000 13341233424 023151 5 ustar www-data www-data acts_as_tree-2.8.0/lib/acts_as_tree/active_record/acts/tree.rb 0000644 0000041 0000041 00000000471 13341233424 024437 0 ustar www-data www-data require 'acts_as_tree'
module ActiveRecord
module Acts #:nodoc:
module Tree
include ::ActsAsTree
def self.included(base)
Kernel.warn "[DEPRECATION] The module ActiveRecord::Acts::Tree has moved to ActsAsTree"
base.extend ::ActsAsTree::ClassMethods
end
end
end
end
acts_as_tree-2.8.0/lib/acts_as_tree.rb 0000644 0000041 0000041 00000027654 13341233424 017751 0 ustar www-data www-data require 'acts_as_tree/version'
module ActsAsTree
if defined? Rails::Railtie
require 'acts_as_tree/railtie'
elsif defined? Rails::Initializer
raise "acts_as_tree 1.0 is not compatible with Rails 2.3 or older"
end
def self.included(base)
base.extend(ClassMethods)
end
# Specify this +acts_as+ extension if you want to model a tree structure
# by providing a parent association and a children association. This
# requires that you have a foreign key column, which by default is called
# +parent_id+.
#
# class Category < ActiveRecord::Base
# include ActsAsTree
#
# acts_as_tree :order => "name"
# end
#
# Example:
# root
# \_ child1
# \_ subchild1
# \_ subchild2
#
# root = Category.create("name" => "root")
# child1 = root.children.create("name" => "child1")
# subchild1 = child1.children.create("name" => "subchild1")
#
# root.parent # => nil
# child1.parent # => root
# root.children # => [child1]
# root.children.first.children.first # => subchild1
#
# In addition to the parent and children associations, the following
# instance methods are added to the class after calling
# acts_as_tree:
# * siblings - Returns all the children of the parent, excluding
# the current node ([subchild2] when called
# on subchild1)
# * self_and_siblings - Returns all the children of the parent,
# including the current node ([subchild1, subchild2]
# when called on subchild1)
# * ancestors - Returns all the ancestors of the current node
# ([child1, root] when called on subchild2)
# * root - Returns the root of the current node (root
# when called on subchild2)
module ClassMethods
# Configuration options are:
#
# * primary_key - specifies the column name for relations
# (default: +id+)
# * foreign_key - specifies the column name to use for tracking
# of the tree (default: +parent_id+)
# * order - makes it possible to sort the children according to
# this SQL snippet.
# * counter_cache - keeps a count in a +children_count+ column
# if set to +true+ (default: +false+). Specify
# a custom column by passing a symbol or string.
def acts_as_tree(options = {})
configuration = {
primary_key: "id",
foreign_key: "parent_id",
order: nil,
counter_cache: nil,
dependent: :destroy,
touch: false
}
configuration.update(options) if options.is_a?(Hash)
if configuration[:counter_cache] == true
configuration[:counter_cache] = :children_count
end
belongs_to_opts = {
class_name: name,
primary_key: configuration[:primary_key],
foreign_key: configuration[:foreign_key],
counter_cache: configuration[:counter_cache],
touch: configuration[:touch],
inverse_of: :children
}
belongs_to_opts[:optional] = true if ActiveRecord::VERSION::MAJOR >= 5
belongs_to :parent, belongs_to_opts
if ActiveRecord::VERSION::MAJOR >= 4
has_many :children, lambda { order configuration[:order] },
class_name: name,
primary_key: configuration[:primary_key],
foreign_key: configuration[:foreign_key],
dependent: configuration[:dependent],
inverse_of: :parent
else
has_many :children, class_name: name,
primary_key: configuration[:primary_key],
foreign_key: configuration[:foreign_key],
order: configuration[:order],
dependent: configuration[:dependent],
inverse_of: :parent
end
class_eval <<-EOV
include ActsAsTree::InstanceMethods
def self.default_tree_order
order_option = #{configuration[:order].inspect}
order(order_option)
end
def self.root
self.roots.first
end
def self.roots
where(:#{configuration[:foreign_key]} => nil).default_tree_order
end
EOV
# Returns a hash of all nodes grouped by their level in the tree structure.
#
# Class.generations # => { 0=> [root1, root2], 1=> [root1child1, root1child2, root2child1, root2child2] }
def self.generations
all.group_by{ |node| node.tree_level }
end
if configuration[:counter_cache]
after_update :update_parents_counter_cache
def children_counter_cache_column
reflect_on_association(:parent).counter_cache_column
end
def leaves
where(children_counter_cache_column => 0).default_tree_order
end
else
# Fallback to less efficient ways to find leaves.
class_eval <<-EOV
def self.leaves
internal_ids = select(:#{configuration[:foreign_key]}).where(arel_table[:#{configuration[:foreign_key]}].not_eq(nil))
where("\#{connection.quote_column_name('#{configuration[:primary_key]}')} NOT IN (\#{internal_ids.to_sql})").default_tree_order
end
EOV
end
end
end
module TreeView
# show records in a tree view
# Example:
# root
# |_ child1
# | |_ subchild1
# | |_ subchild2
# |_ child2
# |_ subchild3
# |_ subchild4
#
def tree_view(label_method = :to_s, node = nil, level = -1)
if node.nil?
puts "root"
nodes = roots
else
label = "|_ #{node.send(label_method)}"
if level == 0
puts " #{label}"
else
puts " |#{" "*level}#{label}"
end
nodes = node.children
end
nodes.each do |child|
tree_view(label_method, child, level+1)
end
end
end
module TreeWalker
# Traverse the tree and call a block with the current node and current
# depth-level.
#
# options:
# algorithm:
# :dfs for depth-first search (default)
# :bfs for breadth-first search
# where: AR where statement to filter certain nodes
#
# The given block sets two parameters:
# first: The current node
# second: The current depth-level within the tree
#
# Example of acts_as_tree for model Page (ERB view):
# <% Page.walk_tree do |page, level| %>
# <%= link_to "#{' '*level}#{page.name}", page_path(page) %>
# <% end %>
#
# There is also a walk_tree instance method that starts walking from
# the node it is called on.
#
def walk_tree(options = {}, &block)
algorithm = options.fetch :algorithm, :dfs
where = options.fetch :where, {}
send("walk_tree_#{algorithm}", where, &block)
end
def self.extended(mod)
mod.class_eval do
def walk_tree(options = {}, &block)
algorithm = options.fetch :algorithm, :dfs
where = options.fetch :where, {}
self.class.send("walk_tree_#{algorithm}", where, self, &block)
end
end
end
private
def walk_tree_bfs(where = {}, node = nil, level = -1, &block)
nodes = (node.nil? ? roots : node.children).where(where)
nodes.each { |child| yield(child, level + 1) }
nodes.each { |child| walk_tree_bfs where, child, level + 1, &block }
end
def walk_tree_dfs(where = {}, node = nil, level = -1, &block)
yield(node, level) unless level == -1
nodes = (node.nil? ? roots : node.children).where(where)
nodes.each { |child| walk_tree_dfs where, child, level + 1, &block }
end
end
module InstanceMethods
# Returns list of ancestors, starting from parent until root.
#
# subchild1.ancestors # => [child1, root]
def ancestors
node, nodes = self, []
nodes << node = node.parent while node.parent
nodes
end
# Returns list of descendants, starting from current node, not including current node.
#
# root.descendants # => [child1, child2, subchild1, subchild2, subchild3, subchild4]
def descendants
children.each_with_object(children.to_a) {|child, arr|
arr.concat child.descendants
}.uniq
end
# Returns list of descendants, starting from current node, including current node.
#
# root.self_and_descendants # => [root, child1, child2, subchild1, subchild2, subchild3, subchild4]
def self_and_descendants
[self] + descendants
end
# Returns the root node of the tree.
def root
node = self
node = node.parent while node.parent
node
end
# Returns all siblings of the current node.
#
# subchild1.siblings # => [subchild2]
def siblings
self_and_siblings - [self]
end
# Returns all siblings and a reference to the current node.
#
# subchild1.self_and_siblings # => [subchild1, subchild2]
def self_and_siblings
parent ? parent.children : self.class.roots
end
# Returns all the nodes at the same level in the tree as the current node.
#
# root1child1.generation # => [root1child2, root2child1, root2child2]
def generation
self_and_generation - [self]
end
# Returns a reference to the current node and all the nodes at the same level as it in the tree.
#
# root1child1.self_and_generation # => [root1child1, root1child2, root2child1, root2child2]
def self_and_generation
self.class.select {|node| node.tree_level == self.tree_level }
end
# Returns the level (depth) of the current node
#
# root1child1.tree_level # => 1
def tree_level
self.ancestors.size
end
# Returns the level (depth) of the current node unless level is a column on the node.
# Allows backwards compatibility with older versions of the gem.
# Allows integration with apps using level as a column name.
#
# root1child1.level # => 1
def level
if self.class.column_names.include?('level')
super
else
tree_level
end
end
# Returns children (without subchildren) and current node itself.
#
# root.self_and_children # => [root, child1]
def self_and_children
[self] + self.children
end
# Returns ancestors and current node itself.
#
# subchild1.self_and_ancestors # => [subchild1, child1, root]
def self_and_ancestors
[self] + self.ancestors
end
# Returns true if node has no parent, false otherwise
#
# subchild1.root? # => false
# root.root? # => true
def root?
parent.nil?
end
# Returns true if node has no children, false otherwise
#
# subchild1.leaf? # => true
# child1.leaf? # => false
def leaf?
children.size.zero?
end
private
if ActiveRecord::VERSION::MAJOR > 5 || ActiveRecord::VERSION::MAJOR == 5 && ActiveRecord::VERSION::MINOR >= 2
def update_parents_counter_cache
end
elsif ActiveRecord::VERSION::MAJOR == 5 && ActiveRecord::VERSION::MINOR == 1
def update_parents_counter_cache
counter_cache_column = self.class.children_counter_cache_column
if saved_change_to_parent_id?
self.class.decrement_counter(counter_cache_column, parent_id_before_last_save)
self.class.increment_counter(counter_cache_column, parent_id)
end
end
else
def update_parents_counter_cache
counter_cache_column = self.class.children_counter_cache_column
if parent_id_changed?
self.class.decrement_counter(counter_cache_column, parent_id_was)
self.class.increment_counter(counter_cache_column, parent_id)
end
end
end
end
end
# Deprecating the following code in the future.
require 'acts_as_tree/active_record/acts/tree'
acts_as_tree-2.8.0/acts_as_tree.gemspec 0000644 0000041 0000041 00000002265 13341233424 020212 0 ustar www-data www-data # -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)
require 'acts_as_tree/version'
Gem::Specification.new do |s|
s.name = 'acts_as_tree'
s.version = ActsAsTree::VERSION
s.authors = ['Erik Dahlstrand', 'Rails Core', 'Mark Turner', 'Swanand Pagnis', 'Felix Bünemann']
s.email = ['erik.dahlstrand@gmail.com', 'mark@amerine.net', 'swanand.pagnis@gmail.com', 'felix.buenemann@gmail.com']
s.homepage = 'https://github.com/amerine/acts_as_tree'
s.summary = %q{Provides a simple tree behaviour to active_record models.}
s.description = %q{A gem that adds simple support for organizing ActiveRecord models into parent–children relationships.}
s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
s.rdoc_options = ["--charset=UTF-8"]
s.add_dependency "activerecord", ">= 3.0.0"
# Dependencies (installed via 'bundle install')...
s.add_development_dependency "sqlite3"
s.add_development_dependency "rdoc"
s.add_development_dependency "minitest", ">= 4.7.5"
end
acts_as_tree-2.8.0/Gemfile 0000644 0000041 0000041 00000000110 13341233424 015467 0 ustar www-data www-data source 'https://rubygems.org'
gemspec
group :test do
gem 'rake'
end