Conversation
The postgres: and mysql: namespaces unconditionally required spec/support/config (deleted in Phase 2.5), causing rake release to fail with LoadError. These tasks were unused by v4 CI. Also simplifies db:test:prepare to a no-op (v4 CI handles DB provisioning in workflow steps, not rake tasks).
* Add Tenant.each and elevator_insert_before for v4 alpha2 Tenant.each iterates tenants_provider, switching into each tenant for the block duration. Accepts an optional tenant list override. Needed by apps migrating from v3 where Tenant.each was available. elevator_insert_before lets apps position the elevator middleware before a named middleware (e.g., Warden) instead of appending to the end of the stack. Without this, apps with auth middleware need manual move_before hacks with fragile ordering dependencies. Bumps version to 4.0.0.alpha2. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add specs for Tenant.each edge cases and pinned model interaction - Empty list no-op, return value, pinned model bypass inside each - Unpinned model routes to tenant pool inside each - Verifies the contract: pinned models always use default pool, even when iterated via Tenant.each Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Harden Tenant.each and elevator_insert_before after review Tenant.each: - Guard tenants_provider returning nil/non-enumerable with ConfigurationError instead of opaque NoMethodError - Guard unconfigured Apartment with ConfigurationError - Document fail-fast semantics in method comment - Add specs: nil provider, unconfigured, mid-iteration exception elevator_insert_before: - Add type validation in Config#validate! (String/Class/nil) - Pass insert_before as explicit kwarg to insert_elevator_middleware (matches header_trust_warning? pattern of explicit args) - Wrap RuntimeError from missing middleware as ConfigurationError with actionable message - Add specs: use-path-with-kwargs, Class target, RuntimeError wrapping - Add default spec in config_spec - Document in README (quick-reference + Elevators section) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Scope rescue RuntimeError to insert_before path only The rescue was outside the if/else, so a RuntimeError from middleware_stack.use (append path) would produce a misleading "elevator_insert_before: nil not found" message. Now only the insert_before path is rescued and re-raised as ConfigurationError. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add spec: use path does not wrap RuntimeError Locks in the behavior that only insert_before errors get re-raised as ConfigurationError; the append path propagates RuntimeError unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…backs (#369) * Remove elevator_insert_before; auto-insert before ActionDispatch::Cookies No popular gem (Rack::Attack, Devise, Sentry) exposes a middleware positioning config. The Railtie now hardcodes insert_before ActionDispatch::Cookies — after error handling/logging, before sessions/auth/DB queries. Users who need different positioning skip config.elevator and use the standard Rails config.middleware API. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Use insert_after Callbacks instead of insert_before Cookies ActionDispatch::Cookies is absent in Rails API mode (config.api_only = true). ActionDispatch::Callbacks is always present and sits immediately before Cookies in the full stack, so insert_after Callbacks gives the same position while working in both modes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Update docs to reflect insert_after Callbacks positioning Sync CLAUDE.md, elevators.md, and elevators/CLAUDE.md with the new auto-insert position (after ActionDispatch::Callbacks). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
#370) Rails passes schema_search_path directly into SET search_path SQL with no quoting. Tenant names containing hyphens (e.g. test-tenant) cause a PG::SyntaxError. Double-quote each schema name so SET search_path TO "test-tenant","ext" parses correctly. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rn (#371) - Update search_path examples to show quoted schema names (matches PR #370: "acme","ext","public" not acme,ext,public) - Update elevator CLAUDE.md to show v4 config.elevator pattern and insert_before for manual positioning (matches PR #369) Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…372) Thin delegators preserving the v3 public API so upgrading apps don't need to find-and-replace call sites for a purely mechanical change. - Apartment.tenant_names -> config.tenants_provider.call - Apartment.excluded_models -> config.excluded_models (deprecated) Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Auto-default default_tenant to 'public' for schema strategy (#373) Schema strategy is PostgreSQL-only; 'public' is the standard default schema. Without this, every PG schema user had to explicitly set default_tenant = 'public' or Apartment::Tenant.current returned nil before any switch, cascading into FK violations and nil errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address review feedback: improve docs and comment clarity - README: keep default_tenant visible in Quick Start, note it's required for :database_name but auto-defaults for :schema - Upgrading guide: remove commented-out lines, add prose explaining the behavioral change and that users can remove explicit default_tenant - Design spec: update config example to reflect auto-defaulting - config.rb: improve comment to explain rationale, not restate code Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Remove dead fallback in migrator; validate against empty default_tenant - migrator.rb: remove `|| 'public'` fallback now that validate! guarantees a non-nil default_tenant for schema strategy - config.rb: reject empty/whitespace-only default_tenant strings - Two new specs for the empty string validation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Document non-public global schema and nil→'public' behavioral change Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Remove misleading nil→public migration note The nil default_tenant was a v4 regression, not v3 behavior. v3 already returned 'public' by default, so no one migrating would have relied on nil state. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add comment explaining strategy choice in CLI tenants spec Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… (#374) * Add design doc for shared pinned model connections Covers the dual-path approach for pinned model connection strategy: shared connections (qualified table names) where the engine supports cross-schema/database queries, separate pools otherwise. Includes force_separate_pinned_pool config opt-out and FK constraint fix. Incorporates architectural approach from #367. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Tighten shared pinned connections design doc Address review feedback: ivar rename touchpoints, adapter matrix formulas, qualification edge cases (table_name_prefix, raw SQL, identifier quoting), schema_cache_per_tenant interaction, connects_to warning, after_commit nuance, FK DDL-vs-DML precision. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add hybrid table name qualification strategy to design doc Use Rails table_name_prefix + reset_table_name for convention-named models; fall back to direct table_name= for models with explicit self.table_name. Covers STI inheritance, class_attribute propagation, clear_config teardown, and new test cases for both paths. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address review gaps in shared pinned connections design - Robust explicit_table_name? detection via compute_table_name comparison instead of bare @table_name ivar check - sub() prefix stripping instead of split('.').last (single-segment only) - Teardown tracks qualification path and original table name for restore - STI wording: only STI subclasses of pinned bases supported - Note base_config already returns string keys - Grep note for ivar rename completeness Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Final spec polish: edge case note, teardown ivars, explicit_table_name? test - Document cached==computed edge case (convention path still correct) - List new ivars in clear_config teardown obligations - Add explicit_table_name? unit test to testing section Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add implementation plan for shared pinned model connections 10 tasks: config option, template methods on AbstractAdapter, PG schema + MySQL adapter implementations, dual-path process_pinned_model, clear_config teardown, ConnectionHandling routing, integration tests, upgrade guide, and cross-version verification. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix plan gaps: mock_adapter default + Tenant.each split - Add shared_pinned_connection?: false to default mock_adapter double so existing tests keep working after ConnectionHandling change - Split 'pinned model inside Tenant.each' context into shared vs separate pool behaviors Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add force_separate_pinned_pool config option New boolean (default false) on Apartment::Config. Escape hatch for multi-server MySQL topologies and apps that rely on pinned model writes surviving tenant transaction rollbacks. Co-Authored-By: henkesn <14108170+henkesn@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add shared_pinned_connection?, qualify_pinned_table_name, explicit_table_name? Template methods on AbstractAdapter for the dual-path pinned model strategy. shared_pinned_connection? returns false by default (safe fallback). qualify_pinned_table_name raises NotImplementedError as guard. explicit_table_name? detects models with self.table_name =. Co-Authored-By: henkesn <14108170+henkesn@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix rubocop offense in qualify_pinned_table_name spec Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Implement shared_pinned_connection? and qualify_pinned_table_name for PG schema PostgresqlSchemaAdapter returns true for shared_pinned_connection? (schemas share a catalog). qualify_pinned_table_name uses hybrid strategy: table_name_prefix for convention-named models, direct table_name= for explicit self.table_name assignments. Co-Authored-By: henkesn <14108170+henkesn@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Implement shared_pinned_connection? and qualify_pinned_table_name for MySQL Mysql2Adapter returns true for shared_pinned_connection? (MySQL supports cross-database queries). qualify_pinned_table_name uses base_config['database'] as qualifier. TrilogyAdapter inherits both. Co-Authored-By: henkesn <14108170+henkesn@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Rewrite process_pinned_model with dual-path logic Shared path: qualify table name, skip establish_connection. Separate path: establish_connection with pinned_model_config (includes schema_search_path for PG schema strategy FK fix). Ivar renamed to @apartment_pinned_processed. Co-Authored-By: henkesn <14108170+henkesn@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Update clear_config teardown for new pinned model ivars Restores table name qualification on clear_config: convention path resets table_name_prefix + reset_table_name; explicit path restores original table_name. Cleans up all three ivars (@apartment_pinned_processed, @apartment_qualification_path, @apartment_original_table_name). Co-Authored-By: henkesn <14108170+henkesn@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Note: do not environmentify pinned model qualifiers environmentify maps tenant keys to database names; the default connection's identifiers (default_tenant, base_config['database']) are already resolved and must not be environmentified. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Conditionally route pinned models through tenant pool When shared_pinned_connection? is true, pinned models fall through to the tenant pool lookup instead of short-circuiting to the default pool. Preserves transactional integrity between pinned and tenant model writes. Co-Authored-By: henkesn <14108170+henkesn@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Update integration tests for dual-path pinned model behavior Separate context blocks for shared vs separate pool. Add transactional integrity tests: rollback rolls back both pinned and tenant writes when shared connections are supported. Co-Authored-By: henkesn <14108170+henkesn@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Update upgrade guide for shared pinned connections Add Pinned Model Connections section to upgrading-to-v4.md with adapter matrix, force_separate_pinned_pool escape hatch, and after_commit nuance. Co-Authored-By: henkesn <14108170+henkesn@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address PR review: ivar ordering, teardown robustness, error context, CI canary Critical fixes: - Reorder ivars in qualify_pinned_table_name to set after mutations succeed (prevents inconsistent state on failure) - Save original table_name_prefix on convention path for correct restoration in clear_config - Add clear_config teardown tests (convention, explicit, separate-pool) Important fixes: - Add else branch with warning in clear_config for unexpected qualification_path values - Make adapter nil check explicit in ConnectionHandling (was relying on !nil == true) - Wrap process_pinned_models with model-identifying error context - Fix shared_pinned_connection? base class comment (was inaccurate) - Drop "single-server" from MySQL wording (code doesn't detect topology) Suggestions: - Add explicit_table_name? edge case note in comment - Add Rails main to CI unit test matrix as canary for compute_table_name private API (continue-on-error, all Ruby versions) - Add ivar ordering and original prefix tests for PG schema adapter - Update design doc status to Implemented Co-Authored-By: henkesn <14108170+henkesn@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix rubocop Layout/FirstArgumentIndentation in abstract_adapter_spec Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Encapsulate pinned model state in Apartment::Model concern Move ivar management from external callers into the concern itself: - apartment_pinned_processed? / apartment_mark_processed! / apartment_restore! - Eliminates all instance_variable_get/set/defined?/remove_instance_variable calls from abstract_adapter.rb, adapters, and apartment.rb - clear_config delegates to klass.apartment_restore! (one line) - Guards against models registered without the concern (excluded_models shim) SOLID principle: the class owns its own state; callers use the public API. Co-Authored-By: henkesn <14108170+henkesn@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Delegate pinned_model? and move explicit_table_name? onto concern - pinned_model? now delegates to klass.apartment_pinned? when available, falling back to registry walk for excluded_models shim compatibility - explicit_table_name? moved from AbstractAdapter (private, ivar-poking) to Apartment::Model as apartment_explicit_table_name? — the model answers its own question about its table name - Removes all instance_variable_defined?/get calls from adapter code; adapters call klass.apartment_explicit_table_name? directly Co-Authored-By: henkesn <14108170+henkesn@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: move SOLID/style guidance to CLAUDE.md; document Model APIs AGENTS.md points at root CLAUDE.md for code style. Root and lib/apartment/CLAUDE.md document apartment_pinned?, apartment_explicit_table_name?, and adapter/concern boundaries. Made-with: Cursor * Update CLAUDE.md: CI matrix includes Rails main, drop stale spec count Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix excluded_models shim: include concern + mark pinned in process_pinned_model Models registered via excluded_models shim or Apartment.register_pinned_model don't include Apartment::Model. process_pinned_model now includes the concern and calls apartment_mark_pinned! (avoids pin_tenant recursion) so the model responds to all concern methods and is recognized as pinned by ConnectionHandling. Co-Authored-By: henkesn <14108170+henkesn@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add shim regression test + CLAUDE.md note on dynamic include Unit test: shim-registered model without concern gets Apartment::Model included and pinned flag set by process_pinned_model. CLAUDE.md: documents apartment_mark_pinned! and shim compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: henkesn <14108170+henkesn@users.noreply.github.com>
A canary that can't fail isn't a canary. Rails main failures should surface as real CI failures so we know when compute_table_name or other private APIs break. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
) persistent_schemas values flow into schema_search_path but were never validated. PostgresqlConfig#validate! now checks entries using the same TenantNameValidator rules as tenant names. Remove enforce_search_path_reset — v4 uses pool-per-tenant with schema_search_path baked into connection config. There is no runtime SET search_path to reset. Dead code from v3 mental model. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Delete completed plan: shared-pinned-connections All 10 tasks implemented and merged in #374. Plan served its purpose as the agent execution checklist; no remaining items. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Delete all completed phase plans (1-8) Phases 1-8 merged in PRs #345-366. Only overview.md retained as roadmap reference. ~340KB of completed checklists removed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix overview.md dead links and stale version strategy Replace plan file link with PR references. Replace version strategy section with implementation history showing actual release timeline. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TracePoint(:end) deferral so pin_tenant doesn't process the model before self.table_name is evaluated. Fixes silent qualification bug under eager_load=false + Zeitwerk lazy loading. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5 tasks: TracePoint deferral in pin_tenant, unit tests for deferred behavior + raise cleanup, integration test mirroring CampusESP failure, documentation updates, full verification. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Defer pin_tenant processing via TracePoint(:end, :raise)
When activated? is true, pin_tenant now registers a one-shot
TracePoint that fires after the class body closes, ensuring
self.table_name and other declarations are visible. :raise
handling disables unconditionally to prevent trace leaks.
Co-Authored-By: henkesn <14108170+henkesn@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Update model_spec for deferred pin_tenant processing
Test deferred processing via TracePoint (class body closes before
process_pinned_model runs). Test :raise cleanup (trace disabled on
class body failure, no leak). Existing non-activated path unchanged.
Also adds :b_return to TracePoint events in model.rb so Class.new {}
blocks trigger deferred processing (matches real class...end behavior).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Design doc: note :b_return needed for Class.new blocks
TracePoint(:end) only fires for source-parsed class/module keywords.
Class.new { } blocks fire :b_return instead. Implementation listens
for both to handle Zeitwerk-loaded files and anonymous classes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add integration test for deferred pin_tenant qualification
Mirrors the CampusESP pattern: pin_tenant above self.table_name.
Verifies qualification uses the explicit table name (not convention)
regardless of declaration order. Adapter-aware assertions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Document deferred pin_tenant processing
Upgrade guide: no ordering requirement between pin_tenant and
self.table_name. CLAUDE.md: TracePoint deferral mechanism with
:end/:b_return/:raise, thread safety, reopen edge case.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix TracePoint: use only :end, drop :b_return and :raise
:b_return fires for ALL block returns in class context (each, tap,
include hooks), not just Class.new closing — triggers premature
processing before self.table_name is evaluated. Confirmed by MRI
testing and Ruby docs (":b_return: block ending" vs ":end: finish
a class or module definition").
:raise fires for rescued exceptions too (begin/rescue in class body).
Unconditional disable prevents :end from running on successful load.
:end always fires for source-parsed class/module, even on unrescued
raise (MRI verified: event order is [:raise, :end]).
For Class.new { } (tests), :end does not fire. Tests use eval'd
source-parsed classes or call process_pinned_model explicitly.
Extracted apartment_defer_processing! private method from pin_tenant
to stay within Metrics/MethodLength.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Design doc: note TracePoint coexistence — no interference with user traces
Multiple TracePoints coexist independently in MRI. Disabling ours
does not affect other traces. Both fire for the same :end event.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Align design doc, comments, and PR description with :end-only implementation
Design doc: rewrite code sketch, TracePoint section, and raise
handling to match :end-only. Remove obsolete :b_return and :raise
mitigation sections. Document why each was rejected.
model.rb comment: clarify :end fires even on raise (event order
[:raise, :end]), add Class.new note.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add defensive guards: AR-only pin_tenant, anonymous class warning, nil adapter warning
- pin_tenant raises ArgumentError if called on non-AR class or module
- pin_tenant warns and skips TracePoint for anonymous classes (Class.new)
where :end does not fire
- Apartment.process_pinned_model warns when adapter is nil instead of
silently returning nil via safe navigation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Sync design doc, document Class.new gotcha, add guard tests
- Design doc code sample now shows ArgumentError guard, name.nil?
check, and marks sketch as abbreviated
- Upgrade guide documents Class.new + constant assignment pattern
(call process_pinned_model explicitly)
- Unit tests for: ArgumentError on non-AR class, ArgumentError on
module, anonymous class warning when activated
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Final doc sync: CLAUDE.md guards section, design doc testing section, PR description
- CLAUDE.md: add Guards paragraph (ArgumentError on non-AR, anonymous warning)
- Design doc: update Testing section to match actual tests (remove stale
:raise test, add guard tests)
- PR description: final file count, test count, defensive guards section
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Polish: fix design doc Class.new wording, integration test title, constant spelling
- Design doc: soften Class.new + raise wording; note name.nil? guard
makes the :b_return leak path unreachable for named classes
- Integration test: rename "pin_tenant anywhere" to "via process_pinned_model"
(matches what the test actually does)
- Spec: TracepointTestModel → TracePointTestModel (match Ruby class name)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: henkesn <14108170+henkesn@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The deferral removes the ordering requirement, but the standard Rails convention of declaring self.table_name at the top of the class body is still best practice for readability. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… note No official Rails convention for self.table_name placement exists (verified via Rails guides, RuboCop Layout/ClassStructure, and community style guides). Recommend "early for readability" without claiming a convention that doesn't exist. Also mention table_name_prefix and table_name_suffix coverage. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…e includes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add test_fixture_cleanup config attribute (default: true) * Add Apartment::TestFixtures to deregister tenant pools before fixture setup Rails' setup_shared_connection_pool assumes every registered shard has a :writing pool_config; Apartment's role-keyed tenant shards violate this, raising ArgumentError. This module strips tenant pools from the ConnectionHandler and PoolManager before super runs, with a re-entrant guard to prevent double-cleanup from AR's notification subscriber. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Wire TestFixtures cleanup via Railtie on_load(:active_record_fixtures) * Add design doc and implementation plan for TestFixtures compatibility Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address PR review: add config validation, extract public reset_tenant_pools! - Add boolean validation for test_fixture_cleanup in Config#validate! (matches pattern of all other boolean config attributes) - Extract public Apartment.reset_tenant_pools! method encapsulating the three-step cleanup (deregister + clear + reset) - Update TestFixtures to call the public API instead of send(:deregister_all_tenant_pools) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Restore Railtie on_load(:active_record_fixtures) hook The previous review-fix commit (8fabf00) accidentally reverted the Railtie wiring added in 35eda8c. The hook was lost because the working tree had a stale railtie.rb when the review fixes were staged. Without this hook, test_fixture_cleanup config is validated but never read, and downstream apps don't get the automatic cleanup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address remaining review items - Restore config_spec default assertion for test_fixture_cleanup (lost in external file modification) - Add comment explaining re-entry test is a unit-level approximation of the !connection.active_record subscriber scenario - Improve YARD doc on reset_tenant_pools! for discoverability Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix TestFixtures subscriber re-entry crash (Trigger B) PR #379 guarded cleanup but still called super on re-entry. When the !connection.active_record subscriber fires mid-test (after the elevator creates a tenant pool), super iterates shard_names and crashes on the apartment shard that has no :writing pool_config. Fix: return early (skip both cleanup AND super) when the guard is set. Apartment pools should not participate in Rails' writing/reading pool config swap; the subscriber still pins and leases the connection independently. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Clarify guard comment: first call vs re-entry behavior Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Document cross-tenant transactional fixture limitation; delete completed plans Add testing guide to v4-test-fixtures-compatibility.md explaining why pool-per-tenant breaks transactional fixtures for cross-tenant specs, why pinned models don't fix transaction visibility, and the recommended per-context use_transactional_tests = false strategy. Delete completed plan files (elevators, migrations, railtie-test-infra, deferred-pin-tenant, test-fixtures-compatibility) — design docs are the permanent reference. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address review feedback: reconcile Trigger B with #380, soften scope - Trigger B step 5: describe post-#380 behavior (early return), note pre-#380 behavior parenthetically - Scope section: clarify all strategies have cross-connection visibility constraints, not just PG schema - Add DatabaseCleaner note to cleanup guidance - Replace brittle line number reference with descriptive text Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release: v4.0.0.alpha2
Merges
maininto4-0-alphato trigger the gem publish workflow.What's new since alpha1
Tenant.eachfor iterating tenantselevator_insert_before; auto-insert afterActionDispatch::Callbackssearch_pathto support hyphens and special charsApartment.tenant_namesand.excluded_modelsconvenience methodsdefault_tenantto'public'for schema strategyforce_separate_pinned_poolconfig escape hatch. SOLID refactor ofApartment::Modelconcern.persistent_schemasas PostgreSQL identifiers; remove deadenforce_search_path_resetoverview.mddead linkspin_tenantprocessing —TracePoint(:end)deferral soself.table_namecan appear anywhere in the class body. AR-only guard, anonymous class warning, nil-adapter warning. Rails main CI canary.Breaking changes (alpha)
force_separate_pinned_pool: true. Seedocs/upgrading-to-v4.md> "Pinned Model Connections".elevator_insert_beforeremoved (Remove elevator_insert_before; auto-insert after ActionDispatch::Callbacks #369): Elevator auto-inserts afterActionDispatch::Callbacks. Apps that usedelevator_insert_beforeshould remove it from config.enforce_search_path_resetremoved (Validate persistent_schemas as PostgreSQL identifiers #376): Dead config option from v3 mental model. v4 uses pool-per-tenant; no runtimeSET search_pathto reset.pin_tenantnow requires ActiveRecord::Base subclass (Defer pin_tenant processing until class body closes #378): RaisesArgumentErrorif called on a non-AR class or module. Anonymous classes (Class.new) warn and skip deferral.Acknowledgments
Special thank you to @henkesn for #367, which identified the pinned model connection pool problem and proposed the
shared_connection_supported?/qualify_pinned_table_namearchitecture. That design became the foundation for #374 — the largest change in this release.Test plan
main(unit + integration across Ruby 3.3/3.4/4.0 x Rails 7.2/8.0/8.1/main)gem build ros-apartment.gemspecsucceedsgem-publish.yml> RubyGems push