with_recursive_tree 0.1.0 → 0.2.0
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.
- checksums.yaml +4 -4
- data/README.md +10 -0
- data/lib/with_recursive_tree/backport.rb +60 -0
- data/lib/with_recursive_tree/version.rb +1 -1
- data/lib/with_recursive_tree.rb +59 -11
- metadata +10 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 577a92da7b862dcefb0b95b7a95e56bf2e3dede08cd87f940ca76691300caf88
|
4
|
+
data.tar.gz: c5d32bf01cb73cd8e4f9d1d8d845c6a9c823acef24343d5686de3617d3a1d404
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e46d3982c72f316453a604deed2879a2ad10f6b97ed9f071f658fb157639958ac851874cc3d4525e744638f97a1592750a29b5c9a2e31a2e001a0472c3cf3527
|
7
|
+
data.tar.gz: 603d05e1f4c5904b243d38f18771605662fa9e72744ba668cd2979ab48b339811b067ff1cf2a4e32eaeeabd3a8d3684f0e8e2e37f052874a4bda8e401759cf2e
|
data/README.md
CHANGED
@@ -144,6 +144,16 @@ A
|
|
144
144
|
---R
|
145
145
|
```
|
146
146
|
|
147
|
+
## Compatibility
|
148
|
+
|
149
|
+
with_recursive_tree is compatible with:
|
150
|
+
|
151
|
+
* Rails 6.0 and above
|
152
|
+
* Ruby 3.1 and above
|
153
|
+
* Postgres version 13 and above
|
154
|
+
* MySQL version 8 and above
|
155
|
+
* SQLite3 version 3.34 and above
|
156
|
+
|
147
157
|
## Benchmarks
|
148
158
|
|
149
159
|
You can run some [benchmarks](/benchmarks/benchmark.rb) to compare with_recursive_tree agains [acts_as_tree](https://github.com/amerine/acts_as_tree), [ancestry](https://github.com/stefankroes/ancestry/) and [closure_tree](https://github.com/ClosureTree/closure_tree).
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module WithRecursiveTree
|
2
|
+
def ancestors
|
3
|
+
self_and_ancestors.where.not self.class.with_recursive_tree_primary_key => send(self.class.with_recursive_tree_primary_key)
|
4
|
+
end
|
5
|
+
|
6
|
+
def descendants
|
7
|
+
self_and_descendants.where.not self.class.with_recursive_tree_primary_key => send(self.class.with_recursive_tree_primary_key)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self_and_ancestors
|
11
|
+
sql = <<-SQL
|
12
|
+
WITH RECURSIVE tree AS (
|
13
|
+
#{self.class.where(self.class.with_recursive_tree_primary_key => send(self.class.with_recursive_tree_primary_key)).to_sql}
|
14
|
+
UNION ALL
|
15
|
+
#{self.class.joins("JOIN tree ON #{self.class.table_name}.#{self.class.with_recursive_tree_primary_key} = tree.#{self.class.with_recursive_tree_foreign_key}").to_sql}
|
16
|
+
) SELECT * FROM tree
|
17
|
+
SQL
|
18
|
+
|
19
|
+
self.class.select("*").from("(#{sql}) AS #{self.class.table_name}")
|
20
|
+
end
|
21
|
+
|
22
|
+
def self_and_descendants
|
23
|
+
anchor_path = if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
|
24
|
+
"ARRAY[#{self.class.with_recursive_tree_order_column}]::text[]"
|
25
|
+
elsif defined?(ActiveRecord::ConnectionAdapters::MySQL)
|
26
|
+
"CAST(CONCAT('/', #{self.class.with_recursive_tree_primary_key}, '/') AS CHAR(512))"
|
27
|
+
elsif defined?(ActiveRecord::ConnectionAdapters::SQLite3Adapter)
|
28
|
+
"'/' || #{self.class.with_recursive_tree_primary_key} || '/'"
|
29
|
+
end
|
30
|
+
|
31
|
+
recursive_path = if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
|
32
|
+
"tree.path || #{self.class.table_name}.#{self.class.with_recursive_tree_order_column}::text"
|
33
|
+
elsif defined?(ActiveRecord::ConnectionAdapters::MySQL)
|
34
|
+
"CONCAT(tree.path, #{self.class.table_name}.#{self.class.with_recursive_tree_primary_key}, '/')"
|
35
|
+
elsif defined?(ActiveRecord::ConnectionAdapters::SQLite3)
|
36
|
+
"tree.path || #{self.class.table_name}.#{self.class.with_recursive_tree_primary_key} || '/'"
|
37
|
+
end
|
38
|
+
|
39
|
+
recursive_query = self.class.joins("JOIN tree ON #{self.class.table_name}.#{self.class.with_recursive_tree_foreign_key} = tree.#{self.class.with_recursive_tree_primary_key}").select("#{self.class.table_name}.*, #{recursive_path} AS path, depth + 1 AS depth")
|
40
|
+
|
41
|
+
# order by is only available in SQLIte for rails versions older than 7.2
|
42
|
+
if defined?(ActiveRecord::ConnectionAdapters::SQLite3)
|
43
|
+
recursive_query = recursive_query.order(self.class.with_recursive_tree_order)
|
44
|
+
end
|
45
|
+
|
46
|
+
sql = <<-SQL
|
47
|
+
WITH RECURSIVE tree AS (
|
48
|
+
#{self.class.where(self.class.with_recursive_tree_primary_key => send(self.class.with_recursive_tree_primary_key)).select("*, #{anchor_path} AS path, 0 AS depth").to_sql}
|
49
|
+
UNION ALL
|
50
|
+
#{Arel.sql(recursive_query.to_sql)}
|
51
|
+
) SELECT * FROM tree
|
52
|
+
SQL
|
53
|
+
|
54
|
+
self.class.select("*").from("(#{sql}) AS #{self.class.table_name}")
|
55
|
+
end
|
56
|
+
|
57
|
+
def siblings
|
58
|
+
self_and_siblings.where.not self.class.with_recursive_tree_primary_key => send(self.class.with_recursive_tree_primary_key)
|
59
|
+
end
|
60
|
+
end
|
data/lib/with_recursive_tree.rb
CHANGED
@@ -6,8 +6,22 @@ module WithRecursiveTree
|
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
8
|
included do
|
9
|
-
scope :bfs, -> {
|
10
|
-
|
9
|
+
scope :bfs, -> {
|
10
|
+
if defined?(ActiveRecord::ConnectionAdapters::MySQL)
|
11
|
+
order :depth, with_recursive_tree_order
|
12
|
+
else
|
13
|
+
order :depth
|
14
|
+
end
|
15
|
+
}
|
16
|
+
scope :dfs, -> do
|
17
|
+
if defined?(ActiveRecord::ConnectionAdapters::MySQL)
|
18
|
+
order with_recursive_tree_order, :path
|
19
|
+
elsif defined?(ActiveRecord::ConnectionAdapters::PostgreSQL)
|
20
|
+
order :path
|
21
|
+
elsif defined?(ActiveRecord::ConnectionAdapters::SQLite3)
|
22
|
+
self
|
23
|
+
end
|
24
|
+
end
|
11
25
|
end
|
12
26
|
|
13
27
|
class_methods do
|
@@ -24,6 +38,14 @@ module WithRecursiveTree
|
|
24
38
|
def roots
|
25
39
|
where with_recursive_tree_foreign_key => nil
|
26
40
|
end
|
41
|
+
|
42
|
+
def with_recursive_tree_order_column
|
43
|
+
if with_recursive_tree_order.is_a?(Hash)
|
44
|
+
with_recursive_tree_order.keys.first
|
45
|
+
else
|
46
|
+
with_recursive_tree_order.to_s.split(" ").first
|
47
|
+
end
|
48
|
+
end
|
27
49
|
end
|
28
50
|
|
29
51
|
def ancestors
|
@@ -52,21 +74,43 @@ module WithRecursiveTree
|
|
52
74
|
end
|
53
75
|
|
54
76
|
def self_and_ancestors
|
55
|
-
self.class.
|
56
|
-
|
77
|
+
self.class.with_recursive(
|
78
|
+
tree: [
|
57
79
|
self.class.where(self.class.with_recursive_tree_primary_key => send(self.class.with_recursive_tree_primary_key)),
|
58
|
-
self.class.joins("JOIN
|
80
|
+
self.class.joins("JOIN tree ON #{self.class.table_name}.#{self.class.with_recursive_tree_primary_key} = tree.#{self.class.with_recursive_tree_foreign_key}")
|
59
81
|
]
|
60
|
-
).select("*").from("
|
82
|
+
).select("*").from("tree AS #{self.class.table_name}")
|
61
83
|
end
|
62
84
|
|
63
85
|
def self_and_descendants
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
86
|
+
anchor_path = if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
|
87
|
+
"ARRAY[#{self.class.with_recursive_tree_order_column}]::text[]"
|
88
|
+
elsif defined?(ActiveRecord::ConnectionAdapters::MySQL)
|
89
|
+
"CAST(CONCAT('/', #{self.class.with_recursive_tree_primary_key}, '/') AS CHAR(512))"
|
90
|
+
elsif defined?(ActiveRecord::ConnectionAdapters::SQLite3Adapter)
|
91
|
+
"'/' || #{self.class.with_recursive_tree_primary_key} || '/'"
|
92
|
+
end
|
93
|
+
|
94
|
+
recursive_path = if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
|
95
|
+
"tree.path || #{self.class.table_name}.#{self.class.with_recursive_tree_order_column}::text"
|
96
|
+
elsif defined?(ActiveRecord::ConnectionAdapters::MySQL)
|
97
|
+
"CONCAT(tree.path, #{self.class.table_name}.#{self.class.with_recursive_tree_primary_key}, '/')"
|
98
|
+
elsif defined?(ActiveRecord::ConnectionAdapters::SQLite3)
|
99
|
+
"tree.path || #{self.class.table_name}.#{self.class.with_recursive_tree_primary_key} || '/'"
|
100
|
+
end
|
101
|
+
|
102
|
+
recursive_query = self.class.joins("JOIN tree ON #{self.class.table_name}.#{self.class.with_recursive_tree_foreign_key} = tree.#{self.class.with_recursive_tree_primary_key}").select("#{self.class.table_name}.*, #{recursive_path} AS path, depth + 1 AS depth")
|
103
|
+
|
104
|
+
unless defined?(ActiveRecord::ConnectionAdapters::MySQL)
|
105
|
+
recursive_query = recursive_query.order(self.class.with_recursive_tree_order)
|
106
|
+
end
|
107
|
+
|
108
|
+
self.class.with_recursive(
|
109
|
+
tree: [
|
110
|
+
self.class.where(self.class.with_recursive_tree_primary_key => send(self.class.with_recursive_tree_primary_key)).select("*, #{anchor_path} AS path, 0 AS depth"),
|
111
|
+
Arel.sql(recursive_query.to_sql)
|
68
112
|
]
|
69
|
-
).select("*").from("
|
113
|
+
).select("*").from("tree AS #{self.class.table_name}")
|
70
114
|
end
|
71
115
|
|
72
116
|
def self_and_siblings
|
@@ -78,6 +122,10 @@ module WithRecursiveTree
|
|
78
122
|
end
|
79
123
|
end
|
80
124
|
|
125
|
+
if Gem::Dependency.new("", "< 7.2.0").match?("", ActiveRecord::VERSION::STRING)
|
126
|
+
require "with_recursive_tree/backport"
|
127
|
+
end
|
128
|
+
|
81
129
|
ActiveSupport.on_load :active_record do
|
82
130
|
include WithRecursiveTree
|
83
131
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: with_recursive_tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Patricio Mac Adden
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-01-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,42 +16,42 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '6.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '6.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activesupport
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '6.0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '6.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: railties
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '6.0'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '6.0'
|
55
55
|
description: Tree structures for ActiveRecord
|
56
56
|
email:
|
57
57
|
- patriciomacadden@gmail.com
|
@@ -62,6 +62,7 @@ files:
|
|
62
62
|
- README.md
|
63
63
|
- Rakefile
|
64
64
|
- lib/with_recursive_tree.rb
|
65
|
+
- lib/with_recursive_tree/backport.rb
|
65
66
|
- lib/with_recursive_tree/version.rb
|
66
67
|
homepage: https://github.com/sinaptia/with_recursive_tree
|
67
68
|
licenses: []
|
@@ -76,7 +77,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
76
77
|
requirements:
|
77
78
|
- - ">="
|
78
79
|
- !ruby/object:Gem::Version
|
79
|
-
version:
|
80
|
+
version: 3.1.0
|
80
81
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
82
|
requirements:
|
82
83
|
- - ">="
|