Skip to content

Commit 1ddb3f8

Browse files
committed
Added preload_tree method
1 parent 51797be commit 1ddb3f8

File tree

10 files changed

+101
-13
lines changed

10 files changed

+101
-13
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
### Version 3.2.0
2+
- Added #preload_tree method to preload the parent/child relations of a single node
3+
14
### Version 3.1.0
25
- Rails 7 support
36

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ __Additional methods:__
134134
__Utility methods:__
135135
* `root?` - returns true if this node is a root node
136136
* `leaf?` - returns true if this node is a leave node
137-
137+
* `preload_tree` - fetches all descendants of this node and assignes the proper parent/children associations. You are then able to traverse the tree through the children/parent association without querying the database again.
138138

139139
## Customizing the recursion
140140

lib/acts_as_recursive_tree/acts_macro.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ module ActsMacro
88
# * <tt>foreign_key</tt> - specifies the column name to use for tracking
99
# of the tree (default: +parent_id+)
1010
def recursive_tree(parent_key: :parent_id, parent_type_column: nil)
11-
class_attribute :_recursive_tree_config
11+
class_attribute(:_recursive_tree_config, instance_writer: false)
12+
1213
self._recursive_tree_config = Config.new(
1314
model_class: self,
1415
parent_key: parent_key.to_sym,

lib/acts_as_recursive_tree/model.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,14 @@ def leaf?
9191
children.none?
9292
end
9393

94+
#
95+
# Fetches all descendants of this node and assigns the parent/children associations
96+
#
97+
def preload_tree
98+
ActsAsRecursiveTree::Preloaders::Descendants.new(self).preload!
99+
true
100+
end
101+
94102
def base_class
95103
self.class.base_class
96104
end
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# frozen_string_literal: true
2+
3+
module ActsAsRecursiveTree
4+
module Preloaders
5+
#
6+
# Preloads all descendants records for a given node and sets the parent and child associations on each record
7+
# based on the preloaded data. After this, calling #parent or #children will not trigger a database query.
8+
#
9+
class Descendants
10+
def initialize(node)
11+
@node = node
12+
@parent_key = node._recursive_tree_config.parent_key
13+
end
14+
15+
def preload!
16+
apply_records(@node)
17+
end
18+
19+
private
20+
21+
def records
22+
@records ||= @node.descendants.to_a
23+
end
24+
25+
def apply_records(parent_node)
26+
children = records.select { |child| child.send(@parent_key) == parent_node.id }
27+
28+
parent_node.association(:children).target = children
29+
30+
children.each do |child|
31+
child.association(:parent).target = parent_node
32+
apply_records(child)
33+
end
34+
end
35+
end
36+
end
37+
end

lib/acts_as_recursive_tree/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# frozen_string_literal: true
22

33
module ActsAsRecursiveTree
4-
VERSION = '3.1.0'
4+
VERSION = '3.2.0'
55
end
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
RSpec.describe ActsAsRecursiveTree::Preloaders::Descendants do
6+
include TreeMethods
7+
8+
let(:preloader) { described_class.new(root.reload) }
9+
let(:root) { create_tree(2) }
10+
let(:children) { root.children }
11+
12+
describe '#preload! will set the associations target attribute' do
13+
before do
14+
preloader.preload!
15+
end
16+
17+
it 'sets the children assoction' do
18+
children.each do |child|
19+
expect(child.association(:children).target).not_to be_nil
20+
end
21+
end
22+
23+
it 'sets the parent assoction' do
24+
children.each do |child|
25+
expect(child.association(:parent).target).not_to be_nil
26+
end
27+
end
28+
end
29+
end

spec/model/node_spec.rb

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,7 @@
33
require 'spec_helper'
44

55
describe Node do
6-
def create_tree(max_level, current_level = 0, node = nil)
7-
node = Node.create!(name: 'root') if node.nil?
8-
9-
1.upto(max_level - current_level) do |index|
10-
child = node.children.create!(name: "child #{index} - level #{current_level}")
11-
create_tree(max_level, current_level + 1, child)
12-
end
13-
14-
node
15-
end
6+
include TreeMethods
167

178
before do
189
@root = create_tree(3)

spec/spec_helper.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111

1212
require 'database_cleaner'
1313

14+
# Requires supporting ruby files with custom matchers and macros, etc,
15+
# in spec/support/ and its subdirectories.
16+
Dir[File.join(__dir__, 'support/**/*.rb')].sort.each { |f| require f }
17+
1418
# This file was generated by the `rspec --init` command. Conventionally, all
1519
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
1620
# The generated `.rspec` file contains `--require spec_helper` which will cause

spec/support/tree_methods.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# frozen_string_literal: true
2+
3+
# Helper methods for simple tree creation
4+
module TreeMethods
5+
def create_tree(max_level, current_level = 0, node = nil)
6+
node = Node.create!(name: 'root') if node.nil?
7+
8+
1.upto(max_level - current_level) do |index|
9+
child = node.children.create!(name: "child #{index} - level #{current_level}")
10+
create_tree(max_level, current_level + 1, child)
11+
end
12+
13+
node
14+
end
15+
end

0 commit comments

Comments
 (0)