acts-as-tree-2.4.0/0000755000175000017500000000000012734034242012404 5ustar aleealeeacts-as-tree-2.4.0/Rakefile0000644000175000017500000000075112734034242014054 0ustar aleealeerequire '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.4.0/gemfiles/0000755000175000017500000000000012734034242014177 5ustar aleealeeacts-as-tree-2.4.0/gemfiles/rails-3.2.gemfile0000644000175000017500000000017312734034242017144 0ustar aleealeesource "https://rubygems.org" gem "rails", "~> 3.2.22" gem "i18n", "< 0.7" gem "rack-cache", "< 1.3" gemspec path: "../" acts-as-tree-2.4.0/gemfiles/rails-3.1.gemfile0000644000175000017500000000017212734034242017142 0ustar aleealeesource "https://rubygems.org" gem "rails", "~> 3.1.0" gem "i18n", "< 0.7" gem "rack-cache", "< 1.3" gemspec path: "../" acts-as-tree-2.4.0/gemfiles/rails-4.2.gemfile0000644000175000017500000000011412734034242017140 0ustar aleealeesource "https://rubygems.org" gem "rails", "~> 4.2.0" gemspec path: "../" acts-as-tree-2.4.0/gemfiles/rails-3.0.gemfile0000644000175000017500000000014012734034242017134 0ustar aleealeesource "https://rubygems.org" gem "rails", "~> 3.0.0" gem "i18n", "< 0.7" gemspec path: "../" acts-as-tree-2.4.0/gemfiles/rails-5.0.gemfile0000644000175000017500000000012212734034242017136 0ustar aleealeesource "https://rubygems.org" gem "rails", "~> 5.0.0.beta1" gemspec path: "../" acts-as-tree-2.4.0/gemfiles/rails-4.0.gemfile0000644000175000017500000000011412734034242017136 0ustar aleealeesource "https://rubygems.org" gem "rails", "~> 4.0.0" gemspec path: "../" acts-as-tree-2.4.0/gemfiles/rails-4.1.gemfile0000644000175000017500000000011412734034242017137 0ustar aleealeesource "https://rubygems.org" gem "rails", "~> 4.1.0" gemspec path: "../" acts-as-tree-2.4.0/acts_as_tree.gemspec0000644000175000017500000000226512734034242016412 0ustar aleealee# -*- 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.4.0/lib/0000755000175000017500000000000012734034242013152 5ustar aleealeeacts-as-tree-2.4.0/lib/acts_as_tree/0000755000175000017500000000000012734034242015606 5ustar aleealeeacts-as-tree-2.4.0/lib/acts_as_tree/railtie.rb0000644000175000017500000000036112734034242017564 0ustar aleealeemodule 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.4.0/lib/acts_as_tree/version.rb0000644000175000017500000000005212734034242017615 0ustar aleealeemodule ActsAsTree VERSION = "2.4.0" end acts-as-tree-2.4.0/lib/acts_as_tree/active_record/0000755000175000017500000000000012734034242020417 5ustar aleealeeacts-as-tree-2.4.0/lib/acts_as_tree/active_record/acts/0000755000175000017500000000000012734034242021351 5ustar aleealeeacts-as-tree-2.4.0/lib/acts_as_tree/active_record/acts/tree.rb0000644000175000017500000000047112734034242022637 0ustar aleealeerequire '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.4.0/lib/acts_as_tree.rb0000644000175000017500000002323012734034242016133 0ustar aleealeerequire '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: # # * 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 = { 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, 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, foreign_key: configuration[:foreign_key], dependent: configuration[:dependent], inverse_of: :parent else has_many :children, class_name: name, 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 = %Q{#{configuration.fetch :order, "nil"}} order(order_option) end def self.root self.roots.first end def self.roots where(:#{configuration[:foreign_key]} => nil).default_tree_order end EOV 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 efficent 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("id 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| block.call 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) block.call 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 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 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 # Deprecating the following code in the future. require 'acts_as_tree/active_record/acts/tree' acts-as-tree-2.4.0/.gitignore0000644000175000017500000000010012734034242014363 0ustar aleealee*.gem .bundle pkg/* .rvmrc Gemfile.lock gemfiles/*.gemfile.lock acts-as-tree-2.4.0/test/0000755000175000017500000000000012734034242013363 5ustar aleealeeacts-as-tree-2.4.0/test/acts_as_tree_test.rb0000644000175000017500000003543712734034242017417 0ustar aleealeerequire '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(counter_cache = false) # AR keeps printing annoying schema statements capture_stdout do ActiveRecord::Base.logger ActiveRecord::Schema.define(version: 1) do create_table :mixins do |t| t.column :type, :string t.column :parent_id, :integer t.column(:children_count, :integer, default: 0) if counter_cache t.timestamps null: false end end Mixin.reset_column_information end end def teardown_db # Silence tables deprecation on rails 5.0 ActiveSupport::Deprecation.silence do ActiveRecord::Base.connection.tables.each do |table| ActiveRecord::Base.connection.drop_table(table) end end end class Mixin < ActiveRecord::Base include ActsAsTree end class TreeMixin < Mixin acts_as_tree foreign_key: "parent_id", order: "id" 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 TreeTest < 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! @root3 = TreeMixin.create! end def teardown teardown_db 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, TreeMixin.count @root1.destroy assert_equal 2, TreeMixin.count @root2.destroy @root3.destroy assert_equal 0, TreeMixin.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, TreeMixin.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], TreeMixin.roots end def test_leaves assert_equal [@child1_child_child, @root_child2, @root2, @root3], TreeMixin.leaves end def test_default_tree_order assert_equal [@root1, @root_child1, @child1_child, @child1_child_child, @root_child2, @root2, @root3], TreeMixin.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, Mixin.respond_to?(:tree_view) Mixin.extend ActsAsTree::TreeView assert_equal true, TreeMixin.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 { TreeMixin.tree_view(:id) } end def test_tree_walker assert_equal false, TreeMixin.respond_to?(:walk_tree) assert_equal false, TreeMixin.new.respond_to?(:walk_tree) TreeMixin.extend ActsAsTree::TreeWalker assert_equal true, TreeMixin.respond_to?(:walk_tree) assert_equal true, TreeMixin.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 { TreeMixin.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 { TreeMixin.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 teardown_db 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 teardown teardown_db 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 teardown_db 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 teardown teardown_db 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 teardown teardown_db 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 teardown teardown_db 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 teardown_db setup_db(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 teardown teardown_db 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 teardown_db setup_db @root = TreeMixinWithTouch.create! @child = TreeMixinWithTouch.create! parent_id: @root.id end def teardown teardown_db 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 acts-as-tree-2.4.0/.travis.yml0000644000175000017500000000233112734034242014514 0ustar aleealeelanguage: ruby sudo: false cache: bundler 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 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 acts-as-tree-2.4.0/README.md0000644000175000017500000001472212734034242013671 0ustar aleealee# ActsAsTree [![Build Status](https://secure.travis-ci.org/amerine/acts_as_tree.svg?branch=master)](http://travis-ci.org/amerine/acts\_as\_tree) [![Gem Version](https://badge.fury.io/rb/acts_as_tree.svg)](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.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.4.0/Gemfile0000644000175000017500000000011012734034242013667 0ustar aleealeesource 'https://rubygems.org' gemspec group :test do gem 'rake' end acts-as-tree-2.4.0/metadata.yml0000644000175000017500000000604312734034242014712 0ustar aleealee--- !ruby/object:Gem::Specification name: acts_as_tree version: !ruby/object:Gem::Version version: 2.4.0 platform: ruby authors: - Erik Dahlstrand - Rails Core - Mark Turner - Swanand Pagnis - Felix Bünemann autorequire: bindir: bin cert_chain: [] date: 2016-01-11 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: activerecord requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 3.0.0 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 3.0.0 - !ruby/object:Gem::Dependency name: sqlite3 requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: rdoc requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: minitest requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 4.7.5 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 4.7.5 description: A gem that adds simple support for organizing ActiveRecord models into parent–children relationships. email: - erik.dahlstrand@gmail.com - mark@amerine.net - swanand.pagnis@gmail.com - felix.buenemann@gmail.com executables: [] extensions: [] extra_rdoc_files: [] files: - ".gitignore" - ".travis.yml" - Gemfile - README.md - Rakefile - acts_as_tree.gemspec - 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 - lib/acts_as_tree.rb - lib/acts_as_tree/active_record/acts/tree.rb - lib/acts_as_tree/railtie.rb - lib/acts_as_tree/version.rb - test/acts_as_tree_test.rb homepage: https://github.com/amerine/acts_as_tree licenses: [] metadata: {} post_install_message: rdoc_options: - "--charset=UTF-8" require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.4.8 signing_key: specification_version: 4 summary: Provides a simple tree behaviour to active_record models. test_files: - test/acts_as_tree_test.rb