closure_tree 9.2.0 → 9.5.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: ace270a69b46da9e3ba8594cb5299e0eb3d9b222f3261c6304566cdbe1f5c7bd
4
- data.tar.gz: ea4dccd196cf9278d510f89fa62d5e892916f2effa82b9c08cf851305523d528
3
+ metadata.gz: 6ab4fbc86c1ae73dc78bb959fa0038e88dfa60e91b1f7322707c76bc5bd8583a
4
+ data.tar.gz: '08824b7349988634bc2d0c4ab3e3ad7757d4491e1617c68b44359e42aafacd51'
5
5
  SHA512:
6
- metadata.gz: 6f915001574717ed1bbadf7f528a6d8d04aa1143cf3ca736db3c1649e72cf4e0d3a60303df86456b6f5ab86a8b1a819987475361a6497f2488f3768ff243dc68
7
- data.tar.gz: 865de6b37ea0db45a27852799e2853b37091f32e06e28a405123e429275a05c7591069039f878cf26f6064e0e5d58ad67c9d227ef8911c2092667f009b4610f5
6
+ metadata.gz: 31d6affbeb9376696c84d6424a55df7dbbab839f37f245da53185ad488a21bbad1871c1ac0f42cddd912388a172af4b7341e3a92287b47622a957b80c9d59061
7
+ data.tar.gz: 6ead47a3741c1a6d859771b532391dd8aa29f64554884eb042aca67964643c0cfc009662ad5d2939f090ce1b83fa1dff12d004c5ecaee22d5bf538e783b722e3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## [9.5.0](https://github.com/ClosureTree/closure_tree/compare/closure_tree-v9.3.0...closure_tree/v9.5.0) (2026-01-21)
4
+
5
+
6
+ ### Features
7
+
8
+ * Add `dependent: :adopt` option for `has_closure_tree` ([#471](https://github.com/ClosureTree/closure_tree/issues/471)) ([d47d7c9](https://github.com/ClosureTree/closure_tree/commit/d47d7c93b59327112a68a7f8454a278877b6ba77))
9
+ * add PostgreSQL schema-qualified table name support ([#462](https://github.com/ClosureTree/closure_tree/issues/462)) ([5f9006c](https://github.com/ClosureTree/closure_tree/commit/5f9006cece95a76f665cb50c2615317e3fa48586))
10
+ * Add runtime advisory lock name customization and multi-database documentation ([#454](https://github.com/ClosureTree/closure_tree/issues/454)) ([d6ffd73](https://github.com/ClosureTree/closure_tree/commit/d6ffd7381e25a28f7a4742bfa2d9c893f0115395))
11
+ * migrate from ActiveSupport::Autoload to Zeitwerk ([#457](https://github.com/ClosureTree/closure_tree/issues/457)) ([d18e80c](https://github.com/ClosureTree/closure_tree/commit/d18e80cdbd4f3510377363bc7b5166f0cc1b0a6f))
12
+ * rewrite with clean api ([#451](https://github.com/ClosureTree/closure_tree/issues/451)) ([f56f2e1](https://github.com/ClosureTree/closure_tree/commit/f56f2e1a3490bb8a099cea8f80b676945fce1c2e))
13
+ * use with_advisory_lock that support rails 8.2 ([#479](https://github.com/ClosureTree/closure_tree/issues/479)) ([bca6231](https://github.com/ClosureTree/closure_tree/commit/bca623168d3255f57e186a3bdcd39e00d38e2a7c))
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * add implicit_order_column for Rails 8.1+ compatibility ([#464](https://github.com/ClosureTree/closure_tree/issues/464)) ([f384303](https://github.com/ClosureTree/closure_tree/commit/f38430334a79ea15d236f9212118dcb5e4530746))
19
+ * configure release-please to recognize v8.0.0 release ([#455](https://github.com/ClosureTree/closure_tree/issues/455)) ([fc34f21](https://github.com/ClosureTree/closure_tree/commit/fc34f2148570afd83608b07a3f5282e5fd475783))
20
+ * eager loading of hierarchies by defining the primary key ([#465](https://github.com/ClosureTree/closure_tree/issues/465)) ([8c7e490](https://github.com/ClosureTree/closure_tree/commit/8c7e490ff89239e44aee71577288bff0e177a8d6))
21
+ * hierarchy class inheritance to avoid STI validations ([#392](https://github.com/ClosureTree/closure_tree/issues/392)) ([#472](https://github.com/ClosureTree/closure_tree/issues/472)) ([73b07a6](https://github.com/ClosureTree/closure_tree/commit/73b07a6cfeec226af16f51ce7c9c6c1d6d921b3d))
22
+ * improve api usage ([#475](https://github.com/ClosureTree/closure_tree/issues/475)) ([cde4d29](https://github.com/ClosureTree/closure_tree/commit/cde4d292236b4267ac4c0be2e2b21092a8b6298c))
23
+ * nil scope values being excluded from scope filtering ([#476](https://github.com/ClosureTree/closure_tree/issues/476)) ([f45880e](https://github.com/ClosureTree/closure_tree/commit/f45880e9765bfa3c1f171b7386bf5a5584208ad7))
24
+ * restore proper deprecation for database_less configuration ([#459](https://github.com/ClosureTree/closure_tree/issues/459)) ([de8b402](https://github.com/ClosureTree/closure_tree/commit/de8b40233d3de5243afce7ec9de9ad26c2eee181))
25
+
26
+ ## [9.3.0](https://github.com/ClosureTree/closure_tree/compare/closure_tree/v9.2.0...closure_tree/v9.3.0) (2025-11-19)
27
+
28
+
29
+ ### Features
30
+
31
+ * eager loading of hierarchies by defining the primary key ([#465](https://github.com/ClosureTree/closure_tree/issues/465)) ([8c7e490](https://github.com/ClosureTree/closure_tree/commit/8c7e490ff89239e44aee71577288bff0e177a8d6))
32
+
3
33
  ## [9.2.0](https://github.com/ClosureTree/closure_tree/compare/closure_tree/v9.1.1...closure_tree/v9.2.0) (2025-10-17)
4
34
 
5
35
 
data/README.md CHANGED
@@ -314,11 +314,13 @@ When you include ```has_closure_tree``` in your model, you can provide a hash to
314
314
  * ```:hierarchy_table_name``` to override the hierarchy table name. This defaults to the singular name of the model + "_hierarchies", like ```tag_hierarchies```.
315
315
  * ```:dependent``` determines what happens when a node is destroyed. Defaults to ```nullify```.
316
316
  * ```:nullify``` will simply set the parent column to null. Each child node will be considered a "root" node. This is the default.
317
+ * ```:adopt``` will move children to their grandparent (parent's parent). If there is no grandparent, children become root nodes. This is useful for maintaining tree structure when removing intermediate nodes.
317
318
  * ```:delete_all``` will delete all descendant nodes (which circumvents the destroy hooks)
318
319
  * ```:destroy``` will destroy all descendant nodes (which runs the destroy hooks on each child node)
319
320
  * ```nil``` does nothing with descendant nodes
320
321
  * ```:name_column``` used by #```find_or_create_by_path```, #```find_by_path```, and ```ancestry_path``` instance methods. This is primarily useful if the model only has one required field (like a "tag").
321
322
  * ```:order``` used to set up [deterministic ordering](#deterministic-ordering)
323
+ * ```:scope``` restricts root nodes and sibling ordering to specific columns. Can be a single symbol or an array of symbols. Example: ```scope: :user_id``` or ```scope: [:user_id, :group_id]```. This ensures that root nodes and siblings are scoped correctly when reordering. See [Ordering Roots](#ordering-roots) for more details.
322
324
  * ```:touch``` delegates to the `belongs_to` annotation for the parent, so `touch`ing cascades to all children (the performance of this for deep trees isn't currently optimal).
323
325
 
324
326
  ## Accessing Data
@@ -491,9 +493,30 @@ table. So for instance if you have 5 nodes with no parent, they will be ordered
491
493
  If your model represents many separate trees and you have a lot of records, this can cause performance
492
494
  problems, and doesn't really make much sense.
493
495
 
494
- You can disable this default behavior by passing `dont_order_roots: true` as an option to your delcaration:
496
+ You can scope root nodes and sibling ordering by passing the `scope` option:
495
497
 
498
+ ```ruby
499
+ class Block < ApplicationRecord
500
+ has_closure_tree order: 'sort_order', numeric_order: true, scope: :user_id
501
+ end
496
502
  ```
503
+
504
+ This ensures that:
505
+ * Root nodes are scoped by the specified columns. You can filter roots like: ```Block.roots.where(user_id: 123)```
506
+ * Sibling reordering only affects nodes with the same scope values
507
+ * Children reordering respects the parent's scope values
508
+
509
+ You can also scope by multiple columns:
510
+
511
+ ```ruby
512
+ class Block < ApplicationRecord
513
+ has_closure_tree order: 'sort_order', numeric_order: true, scope: [:user_id, :group_id]
514
+ end
515
+ ```
516
+
517
+ Alternatively, you can disable root ordering entirely by passing `dont_order_roots: true`:
518
+
519
+ ```ruby
497
520
  has_closure_tree order: 'sort_order', numeric_order: true, dont_order_roots: true
498
521
  ```
499
522
 
data/closure_tree.gemspec CHANGED
@@ -26,11 +26,11 @@ Gem::Specification.new do |gem|
26
26
  gem.required_ruby_version = '>= 3.3.0'
27
27
 
28
28
  gem.add_dependency 'activerecord', '>= 7.2.0'
29
- gem.add_dependency 'with_advisory_lock', '>= 7.0.0'
29
+ gem.add_dependency 'with_advisory_lock', '>= 7.5.0'
30
30
  gem.add_dependency 'zeitwerk', '~> 2.7'
31
31
 
32
32
  gem.add_development_dependency 'database_cleaner'
33
- gem.add_development_dependency 'minitest'
33
+ gem.add_development_dependency 'minitest', '~> 5.0'
34
34
  gem.add_development_dependency 'minitest-reporters'
35
35
  gem.add_development_dependency 'parallel'
36
36
  gem.add_development_dependency 'simplecov'
@@ -79,5 +79,13 @@ module ClosureTree
79
79
 
80
80
  delete_manager
81
81
  end
82
+
83
+ # Convert an Arel AST to SQL using the correct connection's visitor
84
+ # This ensures proper quoting for the specific database adapter (MySQL uses backticks, PostgreSQL uses double quotes)
85
+ def to_sql_with_connection(arel_manager)
86
+ collector = Arel::Collectors::SQLString.new
87
+ visitor = connection.visitor
88
+ visitor.accept(arel_manager.ast, collector).value
89
+ end
82
90
  end
83
91
  end
@@ -20,7 +20,7 @@ module ClosureTree
20
20
 
21
21
  has_many :children, *_ct.has_many_order_with_option, class_name: _ct.model_class.to_s,
22
22
  foreign_key: _ct.parent_column_name,
23
- dependent: _ct.options[:dependent],
23
+ dependent: _ct.options[:dependent] == :adopt ? :nullify : _ct.options[:dependent],
24
24
  inverse_of: :parent do
25
25
  # We have to redefine hash_tree because the activerecord relation is already scoped to parent_id.
26
26
  def hash_tree(options = {})
@@ -47,4 +47,4 @@ module ClosureTree
47
47
  source: :descendant
48
48
  end
49
49
  end
50
- end
50
+ end
@@ -14,7 +14,8 @@ module ClosureTree
14
14
  :numeric_order,
15
15
  :touch,
16
16
  :with_advisory_lock,
17
- :advisory_lock_name
17
+ :advisory_lock_name,
18
+ :scope
18
19
  )
19
20
 
20
21
  class_attribute :_ct
@@ -52,12 +52,26 @@ module ClosureTree
52
52
 
53
53
  def _ct_before_destroy
54
54
  _ct.with_advisory_lock do
55
+ _ct_adopt_children_to_grandparent if _ct.options[:dependent] == :adopt
55
56
  delete_hierarchy_references
56
57
  self.class.find(id).children.find_each(&:rebuild!) if _ct.options[:dependent] == :nullify
57
58
  end
58
59
  true # don't prevent destruction
59
60
  end
60
61
 
62
+ private def _ct_adopt_children_to_grandparent
63
+ grandparent_id = read_attribute(_ct.parent_column_name)
64
+ children_ids = self.class.where(_ct.parent_column_name => id).pluck(:id)
65
+
66
+ return if children_ids.empty?
67
+
68
+ # Update all children's parent_id in a single query
69
+ self.class.where(id: children_ids).update_all(_ct.parent_column_name => grandparent_id)
70
+
71
+ # Rebuild hierarchy for each child
72
+ self.class.where(id: children_ids).find_each(&:rebuild!)
73
+ end
74
+
61
75
  def rebuild!(called_by_rebuild = false)
62
76
  _ct.with_advisory_lock do
63
77
  delete_hierarchy_references unless (defined? @was_new_record) && @was_new_record
@@ -93,7 +107,7 @@ module ClosureTree
93
107
 
94
108
  hierarchy_table = hierarchy_class.arel_table
95
109
  delete_query = _ct.build_hierarchy_delete_query(hierarchy_table, id)
96
- _ct.connection.execute(delete_query.to_sql)
110
+ _ct.connection.execute(_ct.to_sql_with_connection(delete_query))
97
111
  end
98
112
  end
99
113
 
@@ -80,7 +80,9 @@ module ClosureTree
80
80
  end
81
81
 
82
82
  def self_and_siblings
83
- _ct.scope_with_order(_ct.base_class.where(_ct.parent_column_sym => _ct_parent_id))
83
+ scope = _ct.base_class.where(_ct.parent_column_sym => _ct_parent_id)
84
+ scope = _ct.apply_scope_conditions(scope, self)
85
+ _ct.scope_with_order(scope)
84
86
  end
85
87
 
86
88
  def siblings
@@ -15,16 +15,19 @@ module ClosureTree
15
15
  return unless saved_change_to_attribute?(_ct.parent_column_name) && !@was_new_record
16
16
 
17
17
  was_parent_id = attribute_before_last_save(_ct.parent_column_name)
18
- _ct.reorder_with_parent_id(was_parent_id)
18
+ scope_conditions = _ct.scope_values_from_instance(self)
19
+ _ct.reorder_with_parent_id(was_parent_id, nil, scope_conditions)
19
20
  end
20
21
 
21
22
  def _ct_reorder_siblings(minimum_sort_order_value = nil)
22
- _ct.reorder_with_parent_id(_ct_parent_id, minimum_sort_order_value)
23
+ scope_conditions = _ct.scope_values_from_instance(self)
24
+ _ct.reorder_with_parent_id(_ct_parent_id, minimum_sort_order_value, scope_conditions)
23
25
  reload unless destroyed?
24
26
  end
25
27
 
26
28
  def _ct_reorder_children(minimum_sort_order_value = nil)
27
- _ct.reorder_with_parent_id(_ct_id, minimum_sort_order_value)
29
+ scope_conditions = _ct.scope_values_from_instance(self)
30
+ _ct.reorder_with_parent_id(_ct_id, minimum_sort_order_value, scope_conditions)
28
31
  end
29
32
 
30
33
  def self_and_descendants_preordered
@@ -14,7 +14,7 @@ module ClosureTree
14
14
  end
15
15
 
16
16
  module MysqlAdapter
17
- def reorder_with_parent_id(parent_id, minimum_sort_order_value = nil)
17
+ def reorder_with_parent_id(parent_id, minimum_sort_order_value = nil, scope_conditions = {})
18
18
  return if parent_id.nil? && dont_order_roots
19
19
 
20
20
  min_where = if minimum_sort_order_value
@@ -22,18 +22,21 @@ module ClosureTree
22
22
  else
23
23
  ''
24
24
  end
25
+
26
+ scope_where = build_scope_where_clause(scope_conditions)
27
+
25
28
  connection.execute 'SET @i = 0'
26
29
  connection.execute <<-SQL.squish
27
30
  UPDATE #{quoted_table_name}
28
31
  SET #{quoted_order_column} = (@i := @i + 1) + #{minimum_sort_order_value.to_i - 1}
29
- WHERE #{where_eq(parent_column_name, parent_id)} #{min_where}
32
+ WHERE #{where_eq(parent_column_name, parent_id)} #{min_where}#{scope_where}
30
33
  ORDER BY #{nulls_last_order_by}
31
34
  SQL
32
35
  end
33
36
  end
34
37
 
35
38
  module PostgreSQLAdapter
36
- def reorder_with_parent_id(parent_id, minimum_sort_order_value = nil)
39
+ def reorder_with_parent_id(parent_id, minimum_sort_order_value = nil, scope_conditions = {})
37
40
  return if parent_id.nil? && dont_order_roots
38
41
 
39
42
  min_where = if minimum_sort_order_value
@@ -41,13 +44,16 @@ module ClosureTree
41
44
  else
42
45
  ''
43
46
  end
47
+
48
+ scope_where = build_scope_where_clause(scope_conditions)
49
+
44
50
  connection.execute <<-SQL.squish
45
51
  UPDATE #{quoted_table_name}
46
52
  SET #{quoted_order_column(false)} = t.seq + #{minimum_sort_order_value.to_i - 1}
47
53
  FROM (
48
54
  SELECT #{quoted_id_column_name} AS id, row_number() OVER(ORDER BY #{order_by}) AS seq
49
55
  FROM #{quoted_table_name}
50
- WHERE #{where_eq(parent_column_name, parent_id)} #{min_where}
56
+ WHERE #{where_eq(parent_column_name, parent_id)} #{min_where}#{scope_where}
51
57
  ) AS t
52
58
  WHERE #{quoted_table_name}.#{quoted_id_column_name} = t.id and
53
59
  #{quoted_table_name}.#{quoted_order_column(false)} is distinct from t.seq + #{minimum_sort_order_value.to_i - 1}
@@ -60,12 +66,13 @@ module ClosureTree
60
66
  end
61
67
 
62
68
  module GenericAdapter
63
- def reorder_with_parent_id(parent_id, minimum_sort_order_value = nil)
69
+ def reorder_with_parent_id(parent_id, minimum_sort_order_value = nil, scope_conditions = {})
64
70
  return if parent_id.nil? && dont_order_roots
65
71
 
66
72
  scope = model_class
67
73
  .where(parent_column_sym => parent_id)
68
74
  .order(nulls_last_order_by)
75
+ scope = scope.where(scope_conditions) if scope_conditions.any?
69
76
  scope = scope.where("#{quoted_order_column} >= #{minimum_sort_order_value}") if minimum_sort_order_value
70
77
  scope.each_with_index do |ea, idx|
71
78
  ea.update_order_value(idx + minimum_sort_order_value.to_i)
@@ -16,26 +16,53 @@ module ClosureTree
16
16
 
17
17
  @options = {
18
18
  parent_column_name: 'parent_id',
19
- dependent: :nullify, # or :destroy or :delete_all -- see the README
19
+ dependent: :nullify, # or :destroy, :delete_all, or :adopt -- see the README
20
20
  name_column: 'name',
21
21
  with_advisory_lock: true, # This will be overridden by adapter support
22
22
  numeric_order: false
23
23
  }.merge(options)
24
24
  raise ArgumentError, "name_column can't be 'path'" if options[:name_column] == 'path'
25
25
 
26
+ if options[:scope]
27
+ scope_option = options[:scope]
28
+ unless scope_option.is_a?(Symbol) || (scope_option.is_a?(Array) && scope_option.all? { |item| item.is_a?(Symbol) })
29
+ raise ArgumentError, "scope option must be a Symbol or an Array of Symbols (e.g., :user_id or [:user_id, :group_id])"
30
+ end
31
+ end
32
+
26
33
  return unless order_is_numeric?
27
34
 
28
35
  extend NumericOrderSupport.adapter_for_connection(connection)
29
36
  end
30
37
 
38
+ # Find the abstract base class for database connection
39
+ # This ensures hierarchy class uses the same database but doesn't inherit
40
+ # validations/callbacks from STI parent classes (issue #392)
41
+ def abstract_base_class
42
+ klass = model_class
43
+ while klass.superclass != ActiveRecord::Base
44
+ parent = klass.superclass
45
+ # Stop at abstract class (ApplicationRecord, SecondaryRecord, etc.)
46
+ return parent if parent.abstract_class?
47
+ # Stop at connection boundary (handles non-abstract parents with custom connections)
48
+ return parent if parent.connection_specification_name != parent.superclass.connection_specification_name
49
+ klass = parent
50
+ end
51
+ ActiveRecord::Base
52
+ end
53
+
31
54
  def hierarchy_class_for_model
32
55
  parent_class = model_class.module_parent
33
- hierarchy_class = parent_class.const_set(short_hierarchy_class_name, Class.new(model_class.superclass))
56
+ hierarchy_class = parent_class.const_set(short_hierarchy_class_name, Class.new(abstract_base_class))
34
57
  model_class_name = model_class.to_s
35
58
  hierarchy_class.class_eval do
36
59
  # Rails 8.1+ requires an implicit_order_column for models without a primary key
37
60
  self.implicit_order_column = 'ancestor_id'
38
61
 
62
+ # Rails uses the primary key to correctly match associations when using a join to preload (e.g. via `eager_load`).
63
+ # The migration generator adds a unique index across these three columns so this is safe.
64
+ self.primary_key = [:ancestor_id, :descendant_id, :generations]
65
+
39
66
  belongs_to :ancestor, class_name: model_class_name
40
67
  belongs_to :descendant, class_name: model_class_name
41
68
  def ==(other)
@@ -109,6 +136,22 @@ module ClosureTree
109
136
  end
110
137
  end
111
138
 
139
+ # Builds SQL WHERE conditions for scope columns
140
+ # Returns a string that can be appended to a WHERE clause
141
+ def build_scope_where_clause(scope_conditions)
142
+ return '' unless scope_conditions.is_a?(Hash) && scope_conditions.any?
143
+
144
+ conditions = scope_conditions.map do |column, value|
145
+ if value.nil?
146
+ "#{connection.quote_column_name(column.to_s)} IS NULL"
147
+ else
148
+ "#{connection.quote_column_name(column.to_s)} = #{quoted_value(value)}"
149
+ end
150
+ end
151
+
152
+ " AND #{conditions.join(' AND ')}"
153
+ end
154
+
112
155
  def with_advisory_lock(&block)
113
156
  if options[:with_advisory_lock] && connection.supports_advisory_locks? && model_class.respond_to?(:with_advisory_lock)
114
157
  model_class.with_advisory_lock(advisory_lock_name) do
@@ -170,5 +213,49 @@ module ClosureTree
170
213
  def create!(model_class, attributes)
171
214
  create(model_class, attributes).tap(&:save!)
172
215
  end
216
+
217
+ def scope_columns
218
+ return [] unless options[:scope]
219
+
220
+ scope_option = options[:scope]
221
+
222
+ case scope_option
223
+ when Symbol
224
+ [scope_option]
225
+ when Array
226
+ scope_option.select { |item| item.is_a?(Symbol) }
227
+ else
228
+ []
229
+ end
230
+ end
231
+
232
+ def scope_values_from_instance(instance)
233
+ return {} unless options[:scope] && instance
234
+
235
+ scope_option = options[:scope]
236
+ scope_hash = {}
237
+
238
+ case scope_option
239
+ when Symbol
240
+ value = instance.read_attribute(scope_option)
241
+ scope_hash[scope_option] = value
242
+ when Array
243
+ scope_option.each do |item|
244
+ if item.is_a?(Symbol)
245
+ value = instance.read_attribute(item)
246
+ scope_hash[item] = value
247
+ end
248
+ end
249
+ end
250
+
251
+ scope_hash
252
+ end
253
+
254
+ def apply_scope_conditions(scope, instance = nil)
255
+ return scope unless options[:scope] && instance
256
+
257
+ scope_values = scope_values_from_instance(instance)
258
+ scope_values.any? ? scope.where(scope_values) : scope
259
+ end
173
260
  end
174
261
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClosureTree
4
- VERSION = Gem::Version.new('9.2.0')
4
+ VERSION = Gem::Version.new('9.5.0')
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: closure_tree
3
3
  version: !ruby/object:Gem::Version
4
- version: 9.2.0
4
+ version: 9.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew McEachen
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 7.0.0
33
+ version: 7.5.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.0.0
40
+ version: 7.5.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: zeitwerk
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -70,16 +70,16 @@ dependencies:
70
70
  name: minitest
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ">="
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0'
75
+ version: '5.0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ">="
80
+ - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0'
82
+ version: '5.0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: minitest-reporters
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -178,7 +178,7 @@ licenses:
178
178
  metadata:
179
179
  bug_tracker_uri: https://github.com/ClosureTree/closure_tree/issues
180
180
  changelog_uri: https://github.com/ClosureTree/closure_tree/blob/master/CHANGELOG.md
181
- documentation_uri: https://www.rubydoc.info/gems/closure_tree/9.2.0
181
+ documentation_uri: https://www.rubydoc.info/gems/closure_tree/9.5.0
182
182
  homepage_uri: https://closuretree.github.io/closure_tree/
183
183
  source_code_uri: https://github.com/ClosureTree/closure_tree
184
184
  rubygems_mfa_required: 'true'