Skip to content

Commit a5cbde0

Browse files
authored
Inherit Tenanted::GlobalId::Locator from UnscopedLocator (#332)
`Tenanted::GlobalId::Locator` subclassed nothing, so it inherited none of GlobalID's locator behavior. It did not implement `#model_class`, which GlobalID 1.4.0 deprecated. It did not implement `#locate_many`, which `GlobalID::Locator.fetch` requires, so Active Job on Rails edge could not deserialize GlobalID arguments. It also applied a model's `default_scope` to GlobalID lookups, unlike Rails, whose default locator resolves GlobalIDs unscoped. `Tenanted::GlobalId::Locator` will inherit from `GlobalID::Locator::UnscopedLocator`, the locator Rails installs by default, and will keep only the tenant safety check. `#locate_many` will enforce the same tenant checks as `#locate`.
2 parents ccaa2de + d291265 commit a5cbde0

5 files changed

Lines changed: 268 additions & 72 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# `activerecord-tenanted` Changelog
22

3+
## next / unreleased
4+
5+
### Fixed
6+
7+
- `Tenanted::GlobalId::Locator` now inherits from `GlobalID::Locator::UnscopedLocator`, which is the locator Rails uses by default. Two behavior changes follow: GlobalID lookups no longer apply a model's `default_scope`, matching Rails; and `#locate_many` is now implemented, which Active Job on Rails edge requires to deserialize GlobalID arguments. `#locate_many` enforces the same tenant safety checks as `#locate`. @flavorjones
8+
- `Tenanted::GlobalId::Locator` no longer emits a deprecation warning from GlobalID 1.4.0 about the missing `model_class` method. @flavorjones
9+
10+
311
## v0.7.0 / 2026-06-08
412

513
### Security

Gemfile.lock

Lines changed: 45 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
GIT
22
remote: https://github.com/rails/rails.git
3-
revision: 81e325e9edac428c98b40327ae006799ed7b3790
3+
revision: df4bb49eeed65ca7d6ee197a3d8c35d35df7d383
44
branch: main
55
specs:
66
actioncable (8.2.0.alpha)
@@ -49,7 +49,7 @@ GIT
4949
rails-html-sanitizer (~> 1.7)
5050
activejob (8.2.0.alpha)
5151
activesupport (= 8.2.0.alpha)
52-
globalid (>= 0.3.6)
52+
globalid (>= 1.4.0)
5353
activemodel (8.2.0.alpha)
5454
activesupport (= 8.2.0.alpha)
5555
activerecord (8.2.0.alpha)
@@ -73,6 +73,7 @@ GIT
7373
logger (>= 1.4.2)
7474
minitest (>= 5.1)
7575
psych (>= 4)
76+
ractor-dispatch (>= 0.2.0)
7677
securerandom (>= 0.3)
7778
tzinfo (~> 2.0, >= 2.0.5)
7879
uri (>= 0.13.1)
@@ -132,24 +133,24 @@ GEM
132133
rack-test (>= 0.6.3)
133134
regexp_parser (>= 1.5, < 3.0)
134135
xpath (~> 3.2)
135-
concurrent-ruby (1.3.6)
136+
concurrent-ruby (1.3.8)
136137
connection_pool (3.0.2)
137-
crass (1.0.6)
138+
crass (1.0.7)
138139
date (3.5.1)
139140
debug (1.11.1)
140141
irb (~> 1.10)
141142
reline (>= 0.3.8)
142143
drb (2.2.3)
143-
erb (6.0.4)
144+
erb (6.0.6)
144145
erubi (1.13.1)
145-
et-orbi (1.4.0)
146+
et-orbi (1.4.1)
146147
tzinfo
147-
fugit (1.12.2)
148+
fugit (1.13.0)
148149
et-orbi (~> 1.4)
149150
raabro (~> 1.4)
150-
globalid (1.3.0)
151+
globalid (1.4.0)
151152
activesupport (>= 6.1)
152-
i18n (1.14.8)
153+
i18n (1.15.2)
153154
concurrent-ruby (~> 1.0)
154155
importmap-rails (2.2.3)
155156
actionpack (>= 6.0.0)
@@ -164,14 +165,14 @@ GEM
164165
jbuilder (2.15.1)
165166
actionview (>= 7.0.0)
166167
activesupport (>= 7.0.0)
167-
json (2.19.8)
168-
language_server-protocol (3.17.0.5)
168+
json (2.21.2)
169+
language_server-protocol (3.17.0.6)
169170
lint_roller (1.1.0)
170171
logger (1.7.0)
171-
loofah (2.25.1)
172+
loofah (2.25.2)
172173
crass (~> 1.0.2)
173174
nokogiri (>= 1.12.0)
174-
mail (2.9.0)
175+
mail (2.9.1)
175176
logger
176177
mini_mime (>= 0.1.1)
177178
net-imap
@@ -186,7 +187,7 @@ GEM
186187
minitest-mock (5.27.0)
187188
minitest-parallel_fork (2.1.1)
188189
minitest (>= 5.15.0)
189-
net-imap (0.6.4)
190+
net-imap (0.6.6)
190191
date
191192
net-protocol
192193
net-pop (0.1.2)
@@ -196,19 +197,19 @@ GEM
196197
net-smtp (0.5.1)
197198
net-protocol
198199
nio4r (2.7.5)
199-
nokogiri (1.19.3-arm64-darwin)
200+
nokogiri (1.19.4-arm64-darwin)
200201
racc (~> 1.4)
201-
nokogiri (1.19.3-x86_64-darwin)
202+
nokogiri (1.19.4-x86_64-darwin)
202203
racc (~> 1.4)
203-
nokogiri (1.19.3-x86_64-linux-gnu)
204+
nokogiri (1.19.4-x86_64-linux-gnu)
204205
racc (~> 1.4)
205-
nokogiri (1.19.3-x86_64-linux-musl)
206+
nokogiri (1.19.4-x86_64-linux-musl)
206207
racc (~> 1.4)
207208
parallel (2.1.0)
208-
parser (3.3.11.1)
209+
parser (3.3.12.0)
209210
ast (~> 2.4.1)
210211
racc
211-
pp (0.6.3)
212+
pp (0.6.4)
212213
prettyprint
213214
prettyprint (0.2.0)
214215
prism (1.9.0)
@@ -222,7 +223,7 @@ GEM
222223
public_suffix (7.0.5)
223224
puma (8.0.2)
224225
nio4r (~> 2.0)
225-
raabro (1.4.0)
226+
raabro (1.5.0)
226227
racc (1.8.1)
227228
rack (3.2.6)
228229
rack-session (2.1.2)
@@ -232,24 +233,30 @@ GEM
232233
rack (>= 1.3)
233234
rackup (2.3.1)
234235
rack (>= 3)
236+
ractor-dispatch (0.2.0)
235237
rails-dom-testing (2.3.0)
236238
activesupport (>= 5.0.0)
237239
minitest
238240
nokogiri (>= 1.6)
239-
rails-html-sanitizer (1.7.0)
240-
loofah (~> 2.25)
241+
rails-html-sanitizer (1.7.1)
242+
loofah (~> 2.25, >= 2.25.2)
241243
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
242244
rainbow (3.1.1)
243245
rake (13.4.2)
244-
rdoc (7.2.0)
246+
rbs (4.1.2)
247+
logger
248+
prism (>= 1.6.0)
249+
tsort
250+
rdoc (8.0.0)
245251
erb
246-
psych (>= 4.0.0)
252+
prism (>= 1.6.0)
253+
rbs (>= 4.0.0)
247254
tsort
248255
regexp_parser (2.12.0)
249256
reline (0.6.3)
250257
io-console (~> 0.5)
251258
rexml (3.4.4)
252-
rubocop (1.87.0)
259+
rubocop (1.89.0)
253260
json (~> 2.3)
254261
language_server-protocol (~> 3.17.0.2)
255262
lint_roller (~> 1.1.0)
@@ -260,7 +267,7 @@ GEM
260267
rubocop-ast (>= 1.49.0, < 2.0)
261268
ruby-progressbar (~> 1.7)
262269
unicode-display_width (>= 2.4.0, < 4.0)
263-
rubocop-ast (1.49.1)
270+
rubocop-ast (1.50.0)
264271
parser (>= 3.3.7.2)
265272
prism (~> 1.7)
266273
rubocop-minitest (0.39.1)
@@ -284,15 +291,15 @@ GEM
284291
lint_roller (~> 1.1)
285292
rubocop (>= 1.72.1)
286293
ruby-progressbar (1.13.0)
287-
rubyzip (3.3.1)
294+
rubyzip (3.4.1)
288295
securerandom (0.4.1)
289-
selenium-webdriver (4.44.0)
296+
selenium-webdriver (4.46.0)
290297
base64 (~> 0.2)
291298
logger (~> 1.4)
292299
rexml (~> 3.2, >= 3.2.5)
293300
rubyzip (>= 1.2.2, < 4.0)
294301
websocket (~> 1.0)
295-
solid_cable (4.0.0)
302+
solid_cable (4.0.2)
296303
actioncable (>= 7.2)
297304
activejob (>= 7.2)
298305
activerecord (>= 7.2)
@@ -301,7 +308,7 @@ GEM
301308
activejob (>= 7.2)
302309
activerecord (>= 7.2)
303310
railties (>= 7.2)
304-
solid_queue (1.4.0)
311+
solid_queue (1.6.0)
305312
activejob (>= 7.1)
306313
activerecord (>= 7.1)
307314
concurrent-ruby (>= 1.3.1)
@@ -315,13 +322,13 @@ GEM
315322
stimulus-rails (1.3.4)
316323
railties (>= 6.0.0)
317324
stringio (3.2.0)
318-
tailwindcss-rails (4.4.0)
325+
tailwindcss-rails (4.6.0)
319326
railties (>= 7.0.0)
320327
tailwindcss-ruby (~> 4.0)
321-
tailwindcss-ruby (4.3.0-arm64-darwin)
322-
tailwindcss-ruby (4.3.0-x86_64-darwin)
323-
tailwindcss-ruby (4.3.0-x86_64-linux-gnu)
324-
tailwindcss-ruby (4.3.0-x86_64-linux-musl)
328+
tailwindcss-ruby (4.3.3-arm64-darwin)
329+
tailwindcss-ruby (4.3.3-x86_64-darwin)
330+
tailwindcss-ruby (4.3.3-x86_64-linux-gnu)
331+
tailwindcss-ruby (4.3.3-x86_64-linux-musl)
325332
thor (1.5.0)
326333
timeout (0.6.1)
327334
tsort (0.2.0)
@@ -336,13 +343,13 @@ GEM
336343
uri (1.1.1)
337344
useragent (0.16.11)
338345
websocket (1.2.11)
339-
websocket-driver (0.8.1)
346+
websocket-driver (0.8.2)
340347
base64
341348
websocket-extensions (>= 0.1.0)
342349
websocket-extensions (0.1.5)
343350
xpath (3.2.0)
344351
nokogiri (~> 1.8)
345-
zeitwerk (2.8.2)
352+
zeitwerk (2.8.3)
346353

347354
PLATFORMS
348355
arm64-darwin

lib/active_record/tenanted/global_id.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,15 @@ def tenant
99
params && params[:tenant]
1010
end
1111

12-
class Locator
12+
class Locator < ::GlobalID::Locator::UnscopedLocator
1313
def locate(gid, options = {})
1414
ensure_tenant_context_safety(gid)
15-
gid.model_class.find(gid.model_id)
15+
super
16+
end
17+
18+
def locate_many(gids, options = {})
19+
gids.each { |gid| ensure_tenant_context_safety(gid) }
20+
super
1621
end
1722

1823
private

test/integration/test/active_job_test.rb

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ class NoteCheerioJobTest < ActiveJob::TestCase
3535

3636
e = assert_raises(ActiveJob::DeserializationError) { perform_enqueued_jobs }
3737

38-
# this will be a RecordNotFound if the GlobalID locator is not installed correctly
39-
assert_kind_of(ActiveRecord::Tenanted::WrongTenantError, e.cause)
38+
assert_tenant_error(ActiveRecord::Tenanted::WrongTenantError, e)
4039
end
4140

4241
test "global id locator catches untenanted context" do
@@ -51,8 +50,16 @@ class NoteCheerioJobTest < ActiveJob::TestCase
5150

5251
e = assert_raises(ActiveJob::DeserializationError) { perform_enqueued_jobs }
5352

54-
# this will be a RecordNotFound if the GlobalID locator is not installed correctly
55-
# this will be a WrongTenantError if the active job test helper isn't installed correctly
56-
assert_kind_of(ActiveRecord::Tenanted::NoTenantError, e.cause)
53+
assert_tenant_error(ActiveRecord::Tenanted::NoTenantError, e)
5754
end
55+
56+
private
57+
# GlobalID::Locator.fetch may re-raise our error wrapped in RecordUnavailable.
58+
def assert_tenant_error(error_class, error)
59+
chain = [ error ]
60+
chain << chain.last.cause while chain.last.cause
61+
62+
assert(chain.any? { |e| e.is_a?(error_class) },
63+
"Expected #{error_class} in #{chain.map(&:class).inspect}")
64+
end
5865
end

0 commit comments

Comments
 (0)