organizations 0.8.1 → 0.9.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: e3c621c2b6bb7bd32be611858d49af9cb12cf9fa80bc0309d1227eafc819f8d8
4
- data.tar.gz: c6028a9e424ab92432c44ae45bcba367d2a70eab56e3901314b1d5ff9b9b102f
3
+ metadata.gz: d3074b8343b80c07b277e1038a0181981274d1baaa7a52af85dd425edf09c5d3
4
+ data.tar.gz: 8529247495ba8c215216cf4acea19e7764b3e20ac16d997a1ca3b1ecc49f86d7
5
5
  SHA512:
6
- metadata.gz: 267e7126501538f4461616e2be708f1b3696a016b428135337193dc8eaf24532a446a9c651b1989610862025298cbb4b9ab38b7f1d901b0fcb3f49f68d28ec56
7
- data.tar.gz: 98d2dbd1b5369c247c6b8147c7472f0b53e74aa25c0c000b8458834ddff6e81f0ec6f3e599611f2ba8ef10b2dd88ddb5775fe15816cef13d57fb48a393ddce6a
6
+ metadata.gz: cb91ccb8891c4a17c65a85698c13c15931505c3d32688bc075402fdc8582dbd0ecaf2a6a0e581696f471991cd23344998563f340206855c9532352da22086fc7
7
+ data.tar.gz: 1e15480d1f104e0fddea00dd2509223a7b43707e9eccd8930461c24928c3c81338387ba6e95e6b093058ee1bed2741c15b8c2ba919d8ba2449cdd98388f54a62
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.9.0] - 2026-10-25
4
+
5
+ **The current organization, outside controllers.** One addition, opt-in to read. No migration is needed.
6
+
7
+ ### Added
8
+
9
+ - **`Organizations::Current`**, an `ActiveSupport::CurrentAttributes` with one attribute, `organization`. The controller helpers set it whenever `current_organization` resolves or changes (a fallback, `switch_to_organization!`, `current_organization =`, a cleared session), so models and mailers in the same request can read it. Rails resets it before every request and every job. Session-based resolution is unchanged.
10
+ - **`Organizations.with_organization(organization) { ... }`** for jobs, scripts and consoles. It sets the organization for the block, returns the block's value, and puts back the previous one afterwards, also when the block raises, so blocks nest.
11
+ - The README documents the one seam for a host's tenant context (for example a PostgreSQL setting that row-level security policies read): prepend a module that overrides `Organizations::Current#organization=`, which every change goes through, and add a `resets` callback. Asked for by ReplyBrain, whose reused gems must be scoped per request and per job.
12
+
3
13
  ## [0.8.1] - 2026-10-25
4
14
 
5
15
  **The joins take the lock order of the destroys.** A patch release with one fix. No migration is needed.
data/README.md CHANGED
@@ -2033,6 +2033,41 @@ Organizations.configure do |config|
2033
2033
  end
2034
2034
  ```
2035
2035
 
2036
+ ### The current organization outside controllers
2037
+
2038
+ Once `current_organization` resolves in a request, the same organization is in `Organizations::Current.organization`, readable from models, mailers and anything else that runs in that request. It follows switches, and Rails resets it when the request ends. Resolve it early, with `before_action :require_organization!` or a `current_organization` call, so the rest of the request sees it.
2039
+
2040
+ Jobs, scripts and consoles have no controller, so they say which organization they act for:
2041
+
2042
+ ```ruby
2043
+ class SendInvoiceJob < ApplicationJob
2044
+ def perform(invoice)
2045
+ Organizations.with_organization(invoice.organization) do
2046
+ InvoiceMailer.paid(invoice).deliver_now
2047
+ end
2048
+ end
2049
+ end
2050
+ ```
2051
+
2052
+ The block puts back whatever was current before, even when it raises, so blocks nest. Rails resets `Organizations::Current` before every job too.
2053
+
2054
+ Every change goes through one writer, `Organizations::Current.organization=`, and every reset through its `resets` callbacks. Those two points are where to hook your own tenant context, such as the PostgreSQL setting your row-level security policies read (a `SET LOCAL` in the transaction they run in):
2055
+
2056
+ ```ruby
2057
+ # config/initializers/organizations.rb
2058
+ module TenantContext
2059
+ def organization=(organization)
2060
+ super
2061
+ Tenant.enter(organization) # your code: remember it, set the database setting...
2062
+ end
2063
+ end
2064
+
2065
+ Organizations::Current.prepend(TenantContext)
2066
+ Organizations::Current.resets { Tenant.leave }
2067
+ ```
2068
+
2069
+ Session-based resolution is unchanged: the session still decides which organization is current, and `Organizations::Current` only publishes it.
2070
+
2036
2071
  ### Integrates with acts_as_tenant
2037
2072
 
2038
2073
  For automatic query scoping, include the integration concern:
@@ -38,7 +38,7 @@ module Organizations
38
38
  # Returns the current organization from session
39
39
  # Validates membership - if user was removed, auto-switches to next available org
40
40
  # Falls back to most recently joined org if no session set
41
- # Memoized within the request
41
+ # Memoized within the request, and published as Organizations::Current.organization
42
42
  # @return [Organizations::Organization, nil]
43
43
  def current_organization
44
44
  return @_current_organization if defined?(@_current_organization)
@@ -58,7 +58,7 @@ module Organizations
58
58
  if org && user.is_member_of?(org)
59
59
  # Valid membership - use this org
60
60
  user._current_organization_id = org.id
61
- @_current_organization = org
61
+ self.resolved_current_organization = org
62
62
  else
63
63
  # User was removed from this org OR no session set
64
64
  # Auto-switch to next available org (most recently joined)
@@ -68,9 +68,9 @@ module Organizations
68
68
  if fallback_org
69
69
  session[session_key] = fallback_org.id
70
70
  user._current_organization_id = fallback_org.id
71
- @_current_organization = fallback_org
71
+ self.resolved_current_organization = fallback_org
72
72
  else
73
- @_current_organization = nil
73
+ self.resolved_current_organization = nil
74
74
  end
75
75
  end
76
76
  end
@@ -459,7 +459,7 @@ module Organizations
459
459
 
460
460
  if org
461
461
  session[session_key] = org.id
462
- @_current_organization = org
462
+ self.resolved_current_organization = org
463
463
  forget_current_membership!
464
464
 
465
465
  # Update user's context
@@ -578,13 +578,21 @@ module Organizations
578
578
  def clear_organization_session!
579
579
  session_key = Organizations.configuration.session_key
580
580
  session.delete(session_key)
581
- @_current_organization = nil
581
+ self.resolved_current_organization = nil
582
582
  forget_current_membership!
583
583
 
584
584
  user = organizations_current_user
585
585
  user&.clear_organization_cache!
586
586
  end
587
587
 
588
+ # Memoizes the organization for this request and publishes it to
589
+ # Organizations::Current, so models and the host's tenant hook see the
590
+ # one the helpers resolved.
591
+ def resolved_current_organization=(organization)
592
+ @_current_organization = organization
593
+ Organizations::Current.organization = organization
594
+ end
595
+
588
596
  # Drop the memoized membership so the next current_membership call reads
589
597
  # it again for the organization that is current by then. Assigning nil
590
598
  # would not do: current_membership memoizes nil too, so it would keep
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/current_attributes"
4
+
5
+ module Organizations
6
+ # The organization the current request or job acts for.
7
+ #
8
+ # The controller helpers set it whenever `current_organization` resolves
9
+ # or changes (a switch, a fallback, a sign-out from the organization).
10
+ # Jobs and scripts set it with `Organizations.with_organization`. Rails
11
+ # resets it before every request and every job, so a value never leaks
12
+ # from one to the next.
13
+ #
14
+ # Organizations::Current.organization # => #<Organizations::Organization id: 42>
15
+ #
16
+ # It is the one place a host hooks its own tenant context, such as a
17
+ # PostgreSQL setting that row-level security policies read: every change
18
+ # goes through `organization=`, and every reset through `resets`.
19
+ #
20
+ # module TenantContext
21
+ # def organization=(organization)
22
+ # super
23
+ # Tenant.enter(organization)
24
+ # end
25
+ # end
26
+ #
27
+ # Organizations::Current.prepend(TenantContext)
28
+ # Organizations::Current.resets { Tenant.leave }
29
+ class Current < ActiveSupport::CurrentAttributes
30
+ attribute :organization
31
+ end
32
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Organizations
4
- VERSION = "0.8.1"
4
+ VERSION = "0.9.0"
5
5
  end
data/lib/organizations.rb CHANGED
@@ -112,6 +112,7 @@ module Organizations
112
112
  autoload :ActsAsTenantIntegration, "organizations/acts_as_tenant_integration"
113
113
  autoload :TestHelpers, "organizations/test_helpers"
114
114
  autoload :CurrentUserResolution, "organizations/current_user_resolution"
115
+ autoload :Current, "organizations/current"
115
116
  autoload :InvitationAcceptanceResult, "organizations/invitation_acceptance_result"
116
117
  autoload :InvitationAcceptanceFailure, "organizations/invitation_acceptance_failure"
117
118
  autoload :EmailNormalizer, "organizations/email_normalizer"
@@ -213,6 +214,21 @@ module Organizations
213
214
  raise
214
215
  end
215
216
 
217
+ # Runs the block with `organization` as the current organization, then
218
+ # puts back whatever was current before, even if the block raises. For
219
+ # jobs, scripts and consoles, where no controller resolves one.
220
+ #
221
+ # @example
222
+ # Organizations.with_organization(invoice.organization) do
223
+ # InvoiceMailer.paid(invoice).deliver_now
224
+ # end
225
+ #
226
+ # @param organization [Organizations::Organization, nil]
227
+ # @return the block's value
228
+ def with_organization(organization, &)
229
+ Current.set(organization: organization, &)
230
+ end
231
+
216
232
  # Reset configuration to defaults
217
233
  # Primarily used in tests
218
234
  def reset_configuration!
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: organizations
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - rameerez
@@ -155,6 +155,7 @@ files:
155
155
  - lib/organizations/callbacks.rb
156
156
  - lib/organizations/configuration.rb
157
157
  - lib/organizations/controller_helpers.rb
158
+ - lib/organizations/current.rb
158
159
  - lib/organizations/current_user_resolution.rb
159
160
  - lib/organizations/email_normalizer.rb
160
161
  - lib/organizations/engine.rb