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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9c1ccdf38306a7ddff768b00b73f3d3ff58bf094f7524e55b63e0502f4d757dd
4
- data.tar.gz: 5572ccbc0023f7d7226b41f24ffa8513084dc2e8780075f48904dac3aa510a96
3
+ metadata.gz: 577a92da7b862dcefb0b95b7a95e56bf2e3dede08cd87f940ca76691300caf88
4
+ data.tar.gz: c5d32bf01cb73cd8e4f9d1d8d845c6a9c823acef24343d5686de3617d3a1d404
5
5
  SHA512:
6
- metadata.gz: 0a9003bb5a943e7a4d48abed8db293a292dd21dfae943ab1c4899673920fa1c943b5f949d4caf9351c763b3f62e0a14bbfca0ff3e9dfa8fe7daa8e9f4ea6462e
7
- data.tar.gz: 378da103d4c78855cc5c2acef7557e19a27eb2c3f11a4c63d26fa1e29dadff551d433d84060b0f7ef5bd2b711808d97b5c071e80e8f1ff5b61801ddca7a8bd38
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
@@ -1,3 +1,3 @@
1
1
  module WithRecursiveTree
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -6,8 +6,22 @@ module WithRecursiveTree
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  included do
9
- scope :bfs, -> { order :depth }
10
- scope :dfs, -> { self }
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.with(search_tree: self.class.with_recursive(
56
- search_tree: [
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 search_tree ON #{self.class.table_name}.#{self.class.with_recursive_tree_primary_key} = search_tree.#{self.class.with_recursive_tree_foreign_key}")
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("search_tree")).from("search_tree AS #{self.class.table_name}")
82
+ ).select("*").from("tree AS #{self.class.table_name}")
61
83
  end
62
84
 
63
85
  def self_and_descendants
64
- self.class.with(search_tree: self.class.with_recursive(
65
- search_tree: [
66
- self.class.where(self.class.with_recursive_tree_primary_key => send(self.class.with_recursive_tree_primary_key)).select("*, '/' || #{self.class.with_recursive_tree_primary_key} || '/' AS path, 0 AS depth"),
67
- Arel.sql(self.class.joins("JOIN search_tree ON #{self.class.table_name}.#{self.class.with_recursive_tree_foreign_key} = search_tree.#{self.class.with_recursive_tree_primary_key}").select("#{self.class.table_name}.*, search_tree.path || #{self.class.table_name}.#{self.class.with_recursive_tree_primary_key} || '/' AS path, depth + 1 AS depth").order(self.class.with_recursive_tree_order).to_sql)
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("search_tree")).from("search_tree AS #{self.class.table_name}")
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.1.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: 2024-12-09 00:00:00.000000000 Z
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: '7.2'
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: '7.2'
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: '7.2'
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: '7.2'
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: '7.2'
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: '7.2'
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: '0'
80
+ version: 3.1.0
80
81
  required_rubygems_version: !ruby/object:Gem::Requirement
81
82
  requirements:
82
83
  - - ">="