with_recursive_tree 0.2.0 → 0.3.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: 577a92da7b862dcefb0b95b7a95e56bf2e3dede08cd87f940ca76691300caf88
4
- data.tar.gz: c5d32bf01cb73cd8e4f9d1d8d845c6a9c823acef24343d5686de3617d3a1d404
3
+ metadata.gz: 108a3060c431b14be0274538083b1251da56c3c5404ff96628007c849a86b94c
4
+ data.tar.gz: 6d3619942217a168e4eb8bfc04533b5aa78edd6e0bf428eac9cd628007a47582
5
5
  SHA512:
6
- metadata.gz: e46d3982c72f316453a604deed2879a2ad10f6b97ed9f071f658fb157639958ac851874cc3d4525e744638f97a1592750a29b5c9a2e31a2e001a0472c3cf3527
7
- data.tar.gz: 603d05e1f4c5904b243d38f18771605662fa9e72744ba668cd2979ab48b339811b067ff1cf2a4e32eaeeabd3a8d3684f0e8e2e37f052874a4bda8e401759cf2e
6
+ metadata.gz: be39718928d3296a0dcbd9e2d149a6d8c7dcb3fece985620b433a4e45f5f83a4bdcc3e87f8b6840b2b6194d6428b476ba260a49c874754454622bc88811d4b06
7
+ data.tar.gz: 7e66c836eced475a35d398a4bceb47cd1ca441178b823b5be7c82d3a47534dd9e8ff1b9fd3842e72c1c288233c4a2c40b0703c7bdaad2e75e0273d551268ebfb
data/README.md CHANGED
@@ -18,7 +18,7 @@ $ bundle
18
18
 
19
19
  ## Usage
20
20
 
21
- First, your model needs a reference to the its parent. Tipically, this is a `parent_id` column in your table. Once you have that reference, you can add `with_recursive_tree` to your model:
21
+ First, your model needs a reference to the its parent. Typically, this is a `parent_id` column in your table. Once you have that reference, you can add `with_recursive_tree` to your model:
22
22
 
23
23
  ```ruby
24
24
  class Category < ApplicationRecord
@@ -31,7 +31,7 @@ By doing this, with_recursive_tree will add 2 associations:
31
31
  * `parent`: the parent of the node
32
32
  * `children`: the children of this node
33
33
 
34
- To build these associations, with_recursive_tree will use the `id` and the `parent_id` columns as the primary and foreing keys, respectively. If you want to specify different primary and foreign keys, you can do that by passing the `primary_key` and `foreign_key` options. For example, for a categories table whose primary key is `category_id` and the parent record id is `parent_category_id`, you would set it up as follows:
34
+ To build these associations, with_recursive_tree will use the `id` and the `parent_id` columns as the primary and foreign keys, respectively. If you want to specify different primary and foreign keys, you can do that by passing the `primary_key` and `foreign_key` options. For example, for a categories table whose primary key is `category_id` and the parent record id is `parent_category_id`, you would set it up as follows:
35
35
 
36
36
  ```ruby
37
37
  class Category < ApplicationRecord
@@ -156,7 +156,7 @@ with_recursive_tree is compatible with:
156
156
 
157
157
  ## Benchmarks
158
158
 
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).
159
+ You can run some [benchmarks](/benchmarks/benchmark.rb) to compare with_recursive_tree against [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).
160
160
 
161
161
  Spoiler: benchmarks are always basic cases so you mustn't trust them as if they were the word of god, but they are useful tools for development/testing and setting a baseline performance requirement..
162
162
 
@@ -1,4 +1,4 @@
1
- module WithRecursiveTree
1
+ module WithRecursiveTree::InstanceMethods
2
2
  def ancestors
3
3
  self_and_ancestors.where.not self.class.with_recursive_tree_primary_key => send(self.class.with_recursive_tree_primary_key)
4
4
  end
@@ -1,3 +1,3 @@
1
1
  module WithRecursiveTree
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -1,31 +1,10 @@
1
- require "active_support/concern"
2
-
3
1
  require "with_recursive_tree/version"
4
2
 
5
3
  module WithRecursiveTree
6
- extend ActiveSupport::Concern
7
-
8
- included do
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
25
- end
26
-
27
- class_methods do
4
+ module ClassMethods
28
5
  def with_recursive_tree(primary_key: :id, foreign_key: :parent_id, order: nil)
6
+ include InstanceMethods
7
+
29
8
  belongs_to :parent, class_name: name, primary_key: primary_key, foreign_key: foreign_key, inverse_of: :children, optional: true
30
9
 
31
10
  has_many :children, -> { order order }, class_name: name, primary_key: primary_key, foreign_key: foreign_key, inverse_of: :parent
@@ -33,92 +12,111 @@ module WithRecursiveTree
33
12
  define_singleton_method(:with_recursive_tree_primary_key) { primary_key }
34
13
  define_singleton_method(:with_recursive_tree_foreign_key) { foreign_key }
35
14
  define_singleton_method(:with_recursive_tree_order) { order || primary_key }
36
- end
37
-
38
- def roots
39
- where with_recursive_tree_foreign_key => nil
40
- end
15
+ define_singleton_method(:with_recursive_tree_order_column) do
16
+ if with_recursive_tree_order.is_a?(Hash)
17
+ with_recursive_tree_order.keys.first
18
+ else
19
+ with_recursive_tree_order.to_s.split(" ").first
20
+ end
21
+ end
41
22
 
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
23
+ scope :bfs, -> {
24
+ if defined?(ActiveRecord::ConnectionAdapters::MySQL)
25
+ order(:depth, with_recursive_tree_order)
26
+ else
27
+ order(:depth)
28
+ end
29
+ }
30
+ scope :dfs, -> do
31
+ if defined?(ActiveRecord::ConnectionAdapters::MySQL)
32
+ order(with_recursive_tree_order, :path)
33
+ elsif defined?(ActiveRecord::ConnectionAdapters::PostgreSQL)
34
+ order(:path)
35
+ elsif defined?(ActiveRecord::ConnectionAdapters::SQLite3)
36
+ self
37
+ end
47
38
  end
39
+ scope :roots, -> { where with_recursive_tree_foreign_key => nil }
48
40
  end
49
41
  end
50
42
 
51
- def ancestors
52
- self_and_ancestors.excluding self
53
- end
54
-
55
- def descendants
56
- self_and_descendants.excluding self
57
- end
58
-
59
- def leaf?
60
- children.none?
61
- end
43
+ module InstanceMethods
44
+ def ancestors
45
+ self_and_ancestors.excluding self
46
+ end
62
47
 
63
- def depth
64
- attributes["depth"] || ancestors.count
65
- end
66
- alias_method :level, :depth
48
+ def descendants
49
+ self_and_descendants.excluding self
50
+ end
67
51
 
68
- def root
69
- self_and_ancestors.find_by self.class.with_recursive_tree_foreign_key => nil
70
- end
52
+ def leaf?
53
+ children.none?
54
+ end
71
55
 
72
- def root?
73
- parent.blank?
74
- end
56
+ def depth
57
+ attributes["depth"] || ancestors.count
58
+ end
59
+ alias_method :level, :depth
75
60
 
76
- def self_and_ancestors
77
- self.class.with_recursive(
78
- tree: [
79
- self.class.where(self.class.with_recursive_tree_primary_key => send(self.class.with_recursive_tree_primary_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}")
81
- ]
82
- ).select("*").from("tree AS #{self.class.table_name}")
83
- end
61
+ def root
62
+ self_and_ancestors.find_by self.class.with_recursive_tree_foreign_key => nil
63
+ end
84
64
 
85
- def self_and_descendants
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} || '/'"
65
+ def root?
66
+ parent.blank?
92
67
  end
93
68
 
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} || '/'"
69
+ def self_and_ancestors
70
+ self.class.with_recursive(
71
+ tree: [
72
+ self.class.where(self.class.with_recursive_tree_primary_key => send(self.class.with_recursive_tree_primary_key)),
73
+ 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}")
74
+ ]
75
+ ).select("*").from("tree AS #{self.class.table_name}")
100
76
  end
101
77
 
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")
78
+ def self_and_descendants
79
+ anchor_path = if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
80
+ "ARRAY[#{self.class.with_recursive_tree_order_column}]::text[]"
81
+ elsif defined?(ActiveRecord::ConnectionAdapters::MySQL)
82
+ "CAST(CONCAT('/', #{self.class.with_recursive_tree_primary_key}, '/') AS CHAR(512))"
83
+ elsif defined?(ActiveRecord::ConnectionAdapters::SQLite3Adapter)
84
+ "'/' || #{self.class.with_recursive_tree_primary_key} || '/'"
85
+ end
86
+
87
+ recursive_path = if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
88
+ "tree.path || #{self.class.table_name}.#{self.class.with_recursive_tree_order_column}::text"
89
+ elsif defined?(ActiveRecord::ConnectionAdapters::MySQL)
90
+ "CONCAT(tree.path, #{self.class.table_name}.#{self.class.with_recursive_tree_primary_key}, '/')"
91
+ elsif defined?(ActiveRecord::ConnectionAdapters::SQLite3)
92
+ "tree.path || #{self.class.table_name}.#{self.class.with_recursive_tree_primary_key} || '/'"
93
+ end
103
94
 
104
- unless defined?(ActiveRecord::ConnectionAdapters::MySQL)
105
- recursive_query = recursive_query.order(self.class.with_recursive_tree_order)
95
+ 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")
96
+
97
+ unless defined?(ActiveRecord::ConnectionAdapters::MySQL)
98
+ recursive_query = recursive_query.order(self.class.with_recursive_tree_order)
99
+ end
100
+
101
+ self.class.with_recursive(
102
+ tree: [
103
+ 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"),
104
+ Arel.sql(recursive_query.to_sql)
105
+ ]
106
+ ).select("*").from("tree AS #{self.class.table_name}")
106
107
  end
107
108
 
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)
112
- ]
113
- ).select("*").from("tree AS #{self.class.table_name}")
114
- end
109
+ def self_and_siblings
110
+ root? ? self.class.roots : parent.children
111
+ end
115
112
 
116
- def self_and_siblings
117
- root? ? self.class.roots : parent.children
113
+ def siblings
114
+ self_and_siblings.excluding self
115
+ end
118
116
  end
119
117
 
120
- def siblings
121
- self_and_siblings.excluding self
118
+ def self.included(mod)
119
+ mod.extend ClassMethods
122
120
  end
123
121
  end
124
122
 
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.2.0
4
+ version: 0.3.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: 2025-01-03 00:00:00.000000000 Z
11
+ date: 2025-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -77,7 +77,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
77
77
  requirements:
78
78
  - - ">="
79
79
  - !ruby/object:Gem::Version
80
- version: 3.1.0
80
+ version: '3.0'
81
81
  required_rubygems_version: !ruby/object:Gem::Requirement
82
82
  requirements:
83
83
  - - ">="