closure_tree 9.6.2 → 9.7.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/CHANGELOG.md +7 -0
- data/README.md +23 -3
- data/lib/closure_tree/finders.rb +1 -1
- data/lib/closure_tree/hierarchy_maintenance.rb +3 -3
- data/lib/closure_tree/numeric_deterministic_ordering.rb +1 -1
- data/lib/closure_tree/support.rb +2 -2
- data/lib/closure_tree/support_attributes.rb +3 -9
- data/lib/closure_tree/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3b33b2d3bff238b8d3a42e41b94ece6f9b6c9efbcacd23628972d6237f2c3bda
|
|
4
|
+
data.tar.gz: 82a70b8fd95a7bdc6726e3408765027ff45caaa6c21080326966d39eeba89dd8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 51f1f00fcc5c81e2fa380540178f63581aa1f66bfc941777480fb28ac87b2fbd00881e067940f62b5ae031e7a38d16a71f6b4a5e2b757e444df6e4343be09e73
|
|
7
|
+
data.tar.gz: 8116299896bd8f6eacdbfd7a4ffc8453d2328bc773760cb4d028c4444280b46513efcd7587c806bdd2194df46a0fba53847f232df99f7eadff99a3596606e5f3
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [9.7.0](https://github.com/ClosureTree/closure_tree/compare/closure_tree/v9.6.2...closure_tree/v9.7.0) (2026-05-06)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* allow advisory_lock_name proc to receive instance for per-tenant lock granularity ([#491](https://github.com/ClosureTree/closure_tree/issues/491)) ([a7a554c](https://github.com/ClosureTree/closure_tree/commit/a7a554c004fb865ab4ee721f929a8a00bb5cdc46))
|
|
9
|
+
|
|
3
10
|
## [9.6.2](https://github.com/ClosureTree/closure_tree/compare/closure_tree/v9.6.1...closure_tree/v9.6.2) (2026-04-07)
|
|
4
11
|
|
|
5
12
|
|
data/README.md
CHANGED
|
@@ -560,7 +560,7 @@ class Tag < ApplicationRecord
|
|
|
560
560
|
has_closure_tree advisory_lock_name: 'custom_tag_lock'
|
|
561
561
|
end
|
|
562
562
|
|
|
563
|
-
# Dynamic via Proc
|
|
563
|
+
# Dynamic via Proc (1-arity: receives model class only)
|
|
564
564
|
class Tag < ApplicationRecord
|
|
565
565
|
has_closure_tree advisory_lock_name: ->(model_class) { "#{Rails.env}_#{model_class.name.underscore}" }
|
|
566
566
|
end
|
|
@@ -568,16 +568,36 @@ end
|
|
|
568
568
|
# Delegate to model method
|
|
569
569
|
class Tag < ApplicationRecord
|
|
570
570
|
has_closure_tree advisory_lock_name: :custom_lock_name
|
|
571
|
-
|
|
571
|
+
|
|
572
572
|
def self.custom_lock_name
|
|
573
573
|
"tag_lock_#{current_tenant_id}"
|
|
574
574
|
end
|
|
575
575
|
end
|
|
576
576
|
```
|
|
577
577
|
|
|
578
|
+
#### Per-instance lock names (multi-tenant / scoped models)
|
|
579
|
+
|
|
580
|
+
Pass a 2-arity proc to receive both the model class and the current record instance.
|
|
581
|
+
This is the recommended approach for scoped models where each tenant should have its own lock,
|
|
582
|
+
avoiding unnecessary serialization across tenants.
|
|
583
|
+
|
|
584
|
+
```ruby
|
|
585
|
+
class Node < ApplicationRecord
|
|
586
|
+
has_closure_tree scope: :company_id,
|
|
587
|
+
advisory_lock_name: ->(klass, instance) {
|
|
588
|
+
company = instance&.company_id
|
|
589
|
+
company ? "ct_#{klass.name}_#{company}" : "ct_#{klass.name}"
|
|
590
|
+
}
|
|
591
|
+
end
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
When `instance` is `nil` (class-level operations like `Node.rebuild!`), the proc should
|
|
595
|
+
fall back to a model-wide name. Instance-level operations (`save`, `destroy`, `add_sibling`,
|
|
596
|
+
`find_or_create_by_path`) pass the record itself so the lock is scoped to that tenant.
|
|
597
|
+
|
|
578
598
|
This is particularly useful when:
|
|
579
599
|
* You need environment-specific lock names
|
|
580
|
-
* You're using multi-tenancy and need tenant-
|
|
600
|
+
* You're using multi-tenancy and need per-tenant locks (avoiding cross-tenant contention)
|
|
581
601
|
* You want to avoid lock name collisions between similar model names
|
|
582
602
|
|
|
583
603
|
## Multi-Database Support
|
data/lib/closure_tree/finders.rb
CHANGED
|
@@ -20,7 +20,7 @@ module ClosureTree
|
|
|
20
20
|
return found if found
|
|
21
21
|
|
|
22
22
|
attrs = subpath.shift
|
|
23
|
-
_ct.with_advisory_lock do
|
|
23
|
+
_ct.with_advisory_lock(self) do
|
|
24
24
|
# shenanigans because children.create is bound to the superclass
|
|
25
25
|
# (in the case of polymorphism):
|
|
26
26
|
child = children.where(attrs).first || begin
|
|
@@ -64,7 +64,7 @@ module ClosureTree
|
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
def _ct_before_destroy
|
|
67
|
-
_ct.with_advisory_lock do
|
|
67
|
+
_ct.with_advisory_lock(self) do
|
|
68
68
|
_ct_adopt_children_to_grandparent if _ct.options[:dependent] == :adopt
|
|
69
69
|
delete_hierarchy_references
|
|
70
70
|
self.class.find(id).children.find_each(&:rebuild!) if _ct.options[:dependent] == :nullify
|
|
@@ -86,7 +86,7 @@ module ClosureTree
|
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
def rebuild!(called_by_rebuild = false)
|
|
89
|
-
_ct.with_advisory_lock do
|
|
89
|
+
_ct.with_advisory_lock(self) do
|
|
90
90
|
delete_hierarchy_references unless (defined? @was_new_record) && @was_new_record
|
|
91
91
|
hierarchy_class.create!(ancestor: self, descendant: self, generations: 0)
|
|
92
92
|
unless root?
|
|
@@ -112,7 +112,7 @@ module ClosureTree
|
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
def delete_hierarchy_references
|
|
115
|
-
_ct.with_advisory_lock do
|
|
115
|
+
_ct.with_advisory_lock(self) do
|
|
116
116
|
# The crazy double-wrapped sub-subselect works around MySQL's limitation of subselects on the same table that is being mutated.
|
|
117
117
|
# It shouldn't affect performance of postgresql.
|
|
118
118
|
# See http://dev.mysql.com/doc/refman/5.0/en/subquery-errors.html
|
data/lib/closure_tree/support.rb
CHANGED
|
@@ -157,10 +157,10 @@ module ClosureTree
|
|
|
157
157
|
" AND #{conditions.join(' AND ')}"
|
|
158
158
|
end
|
|
159
159
|
|
|
160
|
-
def with_advisory_lock(&block)
|
|
160
|
+
def with_advisory_lock(instance = nil, &block)
|
|
161
161
|
lock_method = options[:advisory_lock_timeout_seconds].present? ? :with_advisory_lock! : :with_advisory_lock
|
|
162
162
|
if options[:with_advisory_lock] && connection.supports_advisory_locks? && model_class.respond_to?(lock_method)
|
|
163
|
-
model_class.public_send(lock_method, advisory_lock_name, advisory_lock_options) do
|
|
163
|
+
model_class.public_send(lock_method, advisory_lock_name(instance), advisory_lock_options) do
|
|
164
164
|
transaction(&block)
|
|
165
165
|
end
|
|
166
166
|
else
|
|
@@ -8,28 +8,22 @@ module ClosureTree
|
|
|
8
8
|
extend Forwardable
|
|
9
9
|
def_delegators :model_class, :connection, :transaction, :table_name, :base_class, :inheritance_column, :column_names
|
|
10
10
|
|
|
11
|
-
def advisory_lock_name
|
|
12
|
-
# Allow customization via options or instance method
|
|
11
|
+
def advisory_lock_name(instance = nil)
|
|
13
12
|
if options[:advisory_lock_name]
|
|
14
13
|
case options[:advisory_lock_name]
|
|
15
14
|
when Proc
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
proc = options[:advisory_lock_name]
|
|
16
|
+
proc.arity == 1 ? proc.call(base_class) : proc.call(base_class, instance)
|
|
18
17
|
when Symbol
|
|
19
|
-
# Allow delegation to a model method
|
|
20
18
|
if model_class.respond_to?(options[:advisory_lock_name])
|
|
21
19
|
model_class.send(options[:advisory_lock_name])
|
|
22
20
|
else
|
|
23
21
|
raise ArgumentError, "Model #{model_class} does not respond to #{options[:advisory_lock_name]}"
|
|
24
22
|
end
|
|
25
23
|
else
|
|
26
|
-
# Use static string value
|
|
27
24
|
options[:advisory_lock_name].to_s
|
|
28
25
|
end
|
|
29
26
|
else
|
|
30
|
-
# Default: Use CRC32 for a shorter, consistent hash
|
|
31
|
-
# This gives us 8 hex characters which is plenty for uniqueness
|
|
32
|
-
# and leaves room for prefixes
|
|
33
27
|
"ct_#{Zlib.crc32(base_class.name.to_s).to_s(16)}"
|
|
34
28
|
end
|
|
35
29
|
end
|
data/lib/closure_tree/version.rb
CHANGED
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.
|
|
4
|
+
version: 9.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matthew McEachen
|
|
@@ -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.
|
|
181
|
+
documentation_uri: https://www.rubydoc.info/gems/closure_tree/9.7.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'
|