ros-apartment 3.1.0 → 3.3.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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +142 -8
  4. data/.ruby-version +1 -1
  5. data/Appraisals +125 -30
  6. data/CLAUDE.md +210 -0
  7. data/CODE_OF_CONDUCT.md +71 -0
  8. data/Gemfile +15 -0
  9. data/README.md +49 -31
  10. data/Rakefile +44 -25
  11. data/docs/adapters.md +177 -0
  12. data/docs/architecture.md +274 -0
  13. data/docs/elevators.md +226 -0
  14. data/docs/images/log_example.png +0 -0
  15. data/lib/apartment/CLAUDE.md +300 -0
  16. data/lib/apartment/active_record/connection_handling.rb +2 -2
  17. data/lib/apartment/active_record/postgres/schema_dumper.rb +20 -0
  18. data/lib/apartment/active_record/postgresql_adapter.rb +19 -4
  19. data/lib/apartment/adapters/CLAUDE.md +314 -0
  20. data/lib/apartment/adapters/abstract_adapter.rb +24 -15
  21. data/lib/apartment/adapters/jdbc_mysql_adapter.rb +1 -1
  22. data/lib/apartment/adapters/jdbc_postgresql_adapter.rb +3 -3
  23. data/lib/apartment/adapters/mysql2_adapter.rb +2 -2
  24. data/lib/apartment/adapters/postgresql_adapter.rb +55 -36
  25. data/lib/apartment/adapters/sqlite3_adapter.rb +7 -7
  26. data/lib/apartment/console.rb +1 -1
  27. data/lib/apartment/custom_console.rb +7 -7
  28. data/lib/apartment/deprecation.rb +2 -5
  29. data/lib/apartment/elevators/CLAUDE.md +292 -0
  30. data/lib/apartment/elevators/domain.rb +1 -1
  31. data/lib/apartment/elevators/generic.rb +1 -1
  32. data/lib/apartment/elevators/host_hash.rb +3 -3
  33. data/lib/apartment/elevators/subdomain.rb +9 -5
  34. data/lib/apartment/log_subscriber.rb +1 -1
  35. data/lib/apartment/migrator.rb +17 -5
  36. data/lib/apartment/model.rb +1 -1
  37. data/lib/apartment/railtie.rb +3 -3
  38. data/lib/apartment/tasks/enhancements.rb +1 -1
  39. data/lib/apartment/tasks/task_helper.rb +6 -4
  40. data/lib/apartment/tenant.rb +3 -3
  41. data/lib/apartment/version.rb +1 -1
  42. data/lib/apartment.rb +23 -11
  43. data/lib/generators/apartment/install/install_generator.rb +1 -1
  44. data/lib/generators/apartment/install/templates/apartment.rb +2 -2
  45. data/lib/tasks/apartment.rake +25 -25
  46. data/ros-apartment.gemspec +10 -35
  47. metadata +44 -245
  48. data/.rubocop_todo.yml +0 -439
  49. /data/{CHANGELOG.md → legacy_CHANGELOG.md} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2494257d1e615c2ef2e79647f3d5b62762dec8c51af19f2c5fe352b01572e752
4
- data.tar.gz: 23b24db12c93d35cf10e34e1c71455a6fe05c9afe162dca6de5115212cb052b7
3
+ metadata.gz: 22097d27933cc7eee7bf0c5e3ad5cf1b84d9243dcfc04be00d6db07a91c9857b
4
+ data.tar.gz: 28e20f76b32b532db8db681503753216ca2847c66ab80e902c200ea43a82b05d
5
5
  SHA512:
6
- metadata.gz: e13fe607631fe0953b16ec42a64ba162deb3fc1522b70ac09b152acbc47b6bebd63a25cc35a6873a3190304de40166f89e9782ceced0259932116739890526f2
7
- data.tar.gz: 035b1b87cdf43f463a6895981d63100a238a2a8b3ad0e0b529d74fa261f6138005e0f5aecd425e857d9ba10ee11c613cb8d6b3002cd35484b34b58f4df367e9a
6
+ metadata.gz: 5f007c6b72251b371b02380628d53b5cf1c7b62e5620fb659b596e9dec1a62e7a2fc0bf0ad1f5253d99b852f31091ddcedf073ed5e64457c5430c58c79c8aa83
7
+ data.tar.gz: 53ccf42974cebbcc6f874f9a631fe366a3952ff36d993127173df34563848359f409a3beeaa37596896a75e8c85cccd63a5ce4a7ec70b7d3278bf7e3b8c7e394
data/.gitignore CHANGED
@@ -13,3 +13,4 @@ cookbooks
13
13
  tmp
14
14
  spec/dummy/db/*.sqlite3
15
15
  .DS_Store
16
+ .claude/
data/.rubocop.yml CHANGED
@@ -1,11 +1,5 @@
1
- inherit_from: .rubocop_todo.yml
2
-
3
- require:
4
- - rubocop-rails
5
- - rubocop-performance
6
- - rubocop-rspec
7
-
8
1
  AllCops:
2
+ NewCops: enable
9
3
  Exclude:
10
4
  - vendor/bundle/**/*
11
5
  - gemfiles/**/*.gemfile
@@ -13,13 +7,41 @@ AllCops:
13
7
  - spec/dummy_engine/dummy_engine.gemspec
14
8
  - spec/schemas/**/*.rb
15
9
 
16
- NewCops: enable
10
+ plugins:
11
+ - rubocop-rails
12
+ - rubocop-performance
13
+ - rubocop-thread_safety
14
+ - rubocop-rake
15
+ - rubocop-rspec
17
16
 
18
17
  Gemspec/RequiredRubyVersion:
19
18
  Exclude:
20
19
  - 'ros-apartment.gemspec'
21
20
 
21
+ Layout/MultilineMethodCallIndentation:
22
+ EnforcedStyle: indented
23
+
22
24
  Metrics/BlockLength:
25
+ Max: 30
26
+ Exclude:
27
+ - spec/**/*.rb
28
+ - lib/tasks/**/*.rake
29
+ - Rakefile
30
+
31
+ Metrics/MethodLength:
32
+ Max: 15
33
+ Exclude:
34
+ - spec/**/*.rb
35
+ - lib/apartment/tenant.rb
36
+
37
+ Metrics/AbcSize:
38
+ Max: 20
39
+ Exclude:
40
+ - spec/**/*.rb
41
+ - lib/apartment/adapters/postgresql_adapter.rb
42
+
43
+ Metrics/ClassLength:
44
+ Max: 155
23
45
  Exclude:
24
46
  - spec/**/*.rb
25
47
 
@@ -33,4 +55,116 @@ Rails/Output:
33
55
  Enabled: false
34
56
 
35
57
  Style/Documentation:
58
+ Enabled: false
59
+
60
+ Style/StringLiterals:
61
+ EnforcedStyle: single_quotes
62
+
63
+ Style/InlineComment:
64
+ Enabled: false
65
+
66
+ Style/FrozenStringLiteralComment:
67
+ Enabled: true
68
+ Exclude:
69
+ - Gemfile
70
+
71
+ Style/MethodCallWithArgsParentheses:
72
+ Enabled: true
73
+ EnforcedStyle: require_parentheses
74
+ AllowedPatterns:
75
+ - 'puts'
76
+ - 'info'
77
+ - 'warn'
78
+ - 'debug'
79
+ - 'error'
80
+ - 'fatal'
81
+ - 'fail'
82
+
83
+ Style/TrailingCommaInArrayLiteral:
84
+ EnforcedStyleForMultiline: comma
85
+
86
+ Style/TrailingCommaInHashLiteral:
87
+ EnforcedStyleForMultiline: comma
88
+
89
+ Style/ClassAndModuleChildren:
90
+ EnforcedStyle: nested
91
+ AutoCorrect: true
92
+
93
+ Style/CollectionMethods:
94
+ PreferredMethods:
95
+ collect: 'map'
96
+ collect!: 'map!'
97
+ inject: 'reduce'
98
+ detect: 'detect'
99
+ find_all: 'select'
100
+
101
+ # RSpec style preferences - disable for mature test suite
102
+ RSpec/NamedSubject:
103
+ Enabled: false
104
+
105
+ RSpec/MultipleExpectations:
106
+ Enabled: false
107
+
108
+ RSpec/MessageSpies:
109
+ Enabled: false
110
+
111
+ RSpec/NestedGroups:
112
+ Enabled: false
113
+
114
+ RSpec/ContextWording:
115
+ Enabled: false
116
+
117
+ RSpec/ExampleLength:
118
+ Enabled: false
119
+
120
+ RSpec/InstanceVariable:
121
+ Enabled: false
122
+
123
+ RSpec/SpecFilePathFormat:
124
+ Enabled: false
125
+
126
+ RSpec/DescribeClass:
127
+ Enabled: false
128
+
129
+ RSpec/IndexedLet:
130
+ Enabled: false
131
+
132
+ RSpec/AnyInstance:
133
+ Enabled: false
134
+
135
+ RSpec/BeforeAfterAll:
136
+ Enabled: false
137
+
138
+ RSpec/LeakyConstantDeclaration:
139
+ Enabled: false
140
+
141
+ RSpec/VerifiedDoubles:
142
+ Enabled: false
143
+
144
+ RSpec/NoExpectationExample:
145
+ Enabled: false
146
+
147
+ # ThreadSafety - intentional design for configuration
148
+ ThreadSafety/ClassInstanceVariable:
149
+ Exclude:
150
+ - lib/apartment.rb
151
+ - lib/apartment/model.rb
152
+ - lib/apartment/elevators/*.rb
153
+ - spec/support/config.rb
154
+
155
+ ThreadSafety/ClassAndModuleAttributes:
156
+ Exclude:
157
+ - lib/apartment.rb
158
+ - lib/apartment/active_record/postgresql_adapter.rb
159
+
160
+ ThreadSafety/DirChdir:
161
+ Exclude:
162
+ - ros-apartment.gemspec
163
+
164
+ ThreadSafety/NewThread:
165
+ Exclude:
166
+ - spec/tenant_spec.rb
167
+
168
+ # Rake cops
169
+ Rake/DuplicateTask:
36
170
  Enabled: false
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.3.1
1
+ 3.3.10
data/Appraisals CHANGED
@@ -1,50 +1,145 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- appraise 'rails-6-1' do
3
+ appraise 'rails-6-1-postgresql' do
4
4
  gem 'rails', '~> 6.1.0'
5
- platforms :ruby do
6
- gem 'sqlite3', '~> 1.4'
5
+ gem 'pg', '~> 1.5'
6
+ end
7
+
8
+ appraise 'rails-6-1-mysql' do
9
+ gem 'rails', '~> 6.1.0'
10
+ gem 'mysql2', '~> 0.5'
11
+ end
12
+
13
+ appraise 'rails-6-1-sqlite3' do
14
+ gem 'rails', '~> 6.1.0'
15
+ gem 'sqlite3', '~> 1.4'
16
+ end
17
+
18
+ appraise 'rails-6-1-jdbc-postgresql' do
19
+ gem 'rails', '~> 6.1.0'
20
+ platforms :jruby do
21
+ gem 'activerecord-jdbc-adapter', '~> 61.3'
22
+ gem 'activerecord-jdbcpostgresql-adapter', '~> 61.3'
23
+ gem 'jdbc-postgres'
7
24
  end
25
+ end
26
+
27
+ appraise 'rails-6-1-jdbc-mysql' do
28
+ gem 'rails', '~> 6.1.0'
8
29
  platforms :jruby do
9
- gem 'activerecord-jdbc-adapter', '~> 61.0'
10
- gem 'activerecord-jdbcpostgresql-adapter', '~> 61.0'
11
- gem 'activerecord-jdbcmysql-adapter', '~> 61.0'
30
+ gem 'activerecord-jdbc-adapter', '~> 61.3'
31
+ gem 'activerecord-jdbcmysql-adapter', '~> 61.3'
32
+ gem 'jdbc-mysql'
12
33
  end
13
34
  end
14
35
 
15
- appraise 'rails-7-0' do
16
- gem 'rails', '~> 7.0.0'
17
- platforms :ruby do
18
- gem 'sqlite3', '~> 1.4'
36
+ appraise 'rails-6-1-jdbc-sqlite3' do
37
+ gem 'rails', '~> 6.1.0'
38
+ platforms :jruby do
39
+ gem 'activerecord-jdbc-adapter', '~> 61.3'
40
+ gem 'activerecord-jdbcsqlite3-adapter', '~> 61.3'
41
+ gem 'jdbc-sqlite3'
19
42
  end
43
+ end
44
+
45
+ appraise 'rails-7-0-postgresql' do
46
+ gem 'rails', '~> 7.0.0'
47
+ gem 'pg', '~> 1.5'
48
+ end
49
+
50
+ appraise 'rails-7-0-mysql' do
51
+ gem 'rails', '~> 7.0.0'
52
+ gem 'mysql2', '~> 0.5'
53
+ end
54
+
55
+ appraise 'rails-7-0-sqlite3' do
56
+ gem 'rails', '~> 7.0.0'
57
+ gem 'sqlite3', '~> 1.4'
58
+ end
59
+
60
+ appraise 'rails-7-0-jdbc-postgresql' do
61
+ gem 'rails', '~> 7.0.0'
20
62
  platforms :jruby do
21
63
  gem 'activerecord-jdbc-adapter', '~> 70.0'
22
64
  gem 'activerecord-jdbcpostgresql-adapter', '~> 70.0'
23
- gem 'activerecord-jdbcmysql-adapter', '~> 70.0'
65
+ gem 'jdbc-postgres'
24
66
  end
25
67
  end
26
68
 
27
- appraise 'rails-7-1' do
28
- gem 'rails', '~> 7.1.0'
29
- platforms :ruby do
30
- gem 'sqlite3', '~> 1.6'
69
+ appraise 'rails-7-0-jdbc-mysql' do
70
+ gem 'rails', '~> 7.0.0'
71
+ platforms :jruby do
72
+ gem 'activerecord-jdbc-adapter', '~> 70.0'
73
+ gem 'activerecord-jdbcmysql-adapter', '~> 70.0'
74
+ gem 'jdbc-mysql'
31
75
  end
76
+ end
77
+
78
+ appraise 'rails-7-0-jdbc-sqlite3' do
79
+ gem 'rails', '~> 7.0.0'
32
80
  platforms :jruby do
33
- gem 'activerecord-jdbc-adapter', '~> 71.0'
34
- gem 'activerecord-jdbcpostgresql-adapter', '~> 71.0'
35
- gem 'activerecord-jdbcmysql-adapter', '~> 71.0'
81
+ gem 'activerecord-jdbc-adapter', '~> 70.0'
82
+ gem 'activerecord-jdbcsqlite3-adapter', '~> 70.0'
83
+ gem 'jdbc-sqlite3'
36
84
  end
37
85
  end
38
86
 
39
- # Install Rails from the main branch are failing
40
- # appraise 'rails-master' do
41
- # gem 'rails', git: 'https://github.com/rails/rails.git'
42
- # platforms :ruby do
43
- # gem 'sqlite3', '~> 2.0'
44
- # end
45
- # platforms :jruby do
46
- # gem 'activerecord-jdbc-adapter', '~> 61.0'
47
- # gem 'activerecord-jdbcpostgresql-adapter', '~> 61.0'
48
- # gem 'activerecord-jdbcmysql-adapter', '~> 61.0'
49
- # end
50
- # end
87
+ appraise 'rails-7-1-postgresql' do
88
+ gem 'rails', '~> 7.1.0'
89
+ gem 'pg', '~> 1.5'
90
+ end
91
+
92
+ appraise 'rails-7-1-mysql' do
93
+ gem 'rails', '~> 7.1.0'
94
+ gem 'mysql2', '~> 0.5'
95
+ end
96
+
97
+ appraise 'rails-7-1-sqlite3' do
98
+ gem 'rails', '~> 7.1.0'
99
+ gem 'sqlite3', '~> 2.1'
100
+ end
101
+
102
+ appraise 'rails-7-2-postgresql' do
103
+ gem 'rails', '~> 7.2.0'
104
+ gem 'pg', '~> 1.5'
105
+ end
106
+
107
+ appraise 'rails-7-2-mysql' do
108
+ gem 'rails', '~> 7.2.0'
109
+ gem 'mysql2', '~> 0.5'
110
+ end
111
+
112
+ appraise 'rails-7-2-sqlite3' do
113
+ gem 'rails', '~> 7.2.0'
114
+ gem 'sqlite3', '~> 2.1'
115
+ end
116
+
117
+ appraise 'rails-8-0-postgresql' do
118
+ gem 'rails', '~> 8.0.0'
119
+ gem 'pg', '~> 1.5'
120
+ end
121
+
122
+ appraise 'rails-8-0-mysql' do
123
+ gem 'rails', '~> 8.0.0'
124
+ gem 'mysql2', '~> 0.5'
125
+ end
126
+
127
+ appraise 'rails-8-0-sqlite3' do
128
+ gem 'rails', '~> 8.0.0'
129
+ gem 'sqlite3', '~> 2.1'
130
+ end
131
+
132
+ appraise 'rails-8-1-postgresql' do
133
+ gem 'rails', '~> 8.1.0'
134
+ gem 'pg', '~> 1.6.0'
135
+ end
136
+
137
+ appraise 'rails-8-1-mysql' do
138
+ gem 'rails', '~> 8.1.0'
139
+ gem 'mysql2', '~> 0.5'
140
+ end
141
+
142
+ appraise 'rails-8-1-sqlite3' do
143
+ gem 'rails', '~> 8.1.0'
144
+ gem 'sqlite3', '~> 2.8'
145
+ end
data/CLAUDE.md ADDED
@@ -0,0 +1,210 @@
1
+ # CLAUDE.md - Apartment v3 Understanding Guide
2
+
3
+ **Version**: 3.x (Current Development Branch)
4
+ **Maintained by**: CampusESP
5
+ **Gem Name**: `ros-apartment`
6
+
7
+ ## What This Documentation Covers
8
+
9
+ This branch contains v3 (current stable release). A v4 refactor with different architecture exists on `man/spec-restart` branch.
10
+
11
+ **Goal**: Understand v3 deeply enough to maintain it and plan v4 migration.
12
+
13
+ ## Where to Start
14
+
15
+ 1. **README.md** - Installation, basic usage, configuration options
16
+ 2. **docs/architecture.md** - Core design decisions and WHY they were made
17
+ 3. **docs/adapters.md** - Database strategy trade-offs
18
+ 4. **docs/elevators.md** - Middleware design rationale
19
+ 5. **lib/apartment/CLAUDE.md** - Implementation file guide
20
+ 6. **spec/CLAUDE.md** - Test organization and patterns
21
+
22
+ ## Core Concepts
23
+
24
+ ### Multi-Tenancy via Database Isolation
25
+
26
+ **Problem**: Single application needs to serve multiple customers with data completely separated.
27
+
28
+ **v3 Solution**: Thread-local tenant switching. Each request/thread tracks which tenant it's serving.
29
+
30
+ **Key limitation**: Not fiber-safe (fibers share thread-local storage).
31
+
32
+ ### Two Main Strategies
33
+
34
+ **PostgreSQL (schemas)**: Multiple namespaces in single database. Fast, scales to 100+ tenants.
35
+
36
+ **MySQL (databases)**: Separate database per tenant. Complete isolation, slower switching.
37
+
38
+ **See**: `docs/adapters.md` for trade-offs.
39
+
40
+ ### Automatic Tenant Detection
41
+
42
+ **Middleware ("Elevators")**: Rack middleware extracts tenant from request (subdomain, domain, header).
43
+
44
+ **Critical**: Must position before session middleware to avoid data leakage.
45
+
46
+ **See**: `docs/elevators.md` for design decisions.
47
+
48
+ ## Key Architecture Decisions
49
+
50
+ ### 1. Thread-Local Adapter Storage
51
+
52
+ **Why**: Concurrent requests need isolated tenant contexts without global locks.
53
+
54
+ **Implementation**: `Thread.current[:apartment_adapter]`
55
+
56
+ **Trade-off**: Not fiber-safe, but works for 99% of Rails deployments.
57
+
58
+ **See**: `Apartment::Tenant.adapter` method in `tenant.rb`, `docs/architecture.md`
59
+
60
+ ### 2. Block-Based Tenant Switching
61
+
62
+ **Why**: Automatic cleanup even on exceptions prevents tenant context leakage.
63
+
64
+ **Pattern**: `Apartment::Tenant.switch(tenant) { ... }` with ensure block
65
+
66
+ **Alternative rejected**: Manual switch/reset - too error-prone.
67
+
68
+ **See**: `AbstractAdapter#switch` method in `adapters/abstract_adapter.rb`
69
+
70
+ ### 3. Excluded Models
71
+
72
+ **Why**: Some models (User, Company) exist globally across all tenants.
73
+
74
+ **Implementation**: Separate connections that bypass tenant switching.
75
+
76
+ **Limitation**: Can't use `has_and_belongs_to_many` - must use `has_many :through`.
77
+
78
+ **See**: `AbstractAdapter#process_excluded_models` method in `adapters/abstract_adapter.rb`
79
+
80
+ ### 4. Adapter Pattern
81
+
82
+ **Why**: PostgreSQL uses schemas, MySQL uses databases - fundamentally different.
83
+
84
+ **Implementation**: Abstract base class with database-specific subclasses.
85
+
86
+ **Benefit**: Unified API hides database differences.
87
+
88
+ **See**: `lib/apartment/adapters/`, `docs/adapters.md`
89
+
90
+ ### 5. Callback System
91
+
92
+ **Why**: Users need logging/notification hooks without modifying gem code.
93
+
94
+ **Implementation**: ActiveSupport::Callbacks on `:create` and `:switch`.
95
+
96
+ **See**: Callback definitions in `AbstractAdapter` class in `adapters/abstract_adapter.rb`
97
+
98
+ ## File Organization
99
+
100
+ **Core logic**: `lib/apartment.rb` (configuration), `lib/apartment/tenant.rb` (public API)
101
+
102
+ **Adapters**: `lib/apartment/adapters/*.rb` - Database-specific implementations
103
+
104
+ **Elevators**: `lib/apartment/elevators/*.rb` - Rack middleware for auto-switching
105
+
106
+ **Tests**: `spec/` - Adapter tests, elevator tests, integration tests
107
+
108
+ **See folder CLAUDE.md files for details on each directory.**
109
+
110
+ ## Configuration Philosophy
111
+
112
+ **Dynamic tenant discovery**: `tenant_names` can be callable (proc/lambda) that queries database. Why? Tenants change at runtime.
113
+
114
+ **Fail-safe boot**: Rescue database errors during config loading. Why? App should start even if tenant table doesn't exist yet (pending migrations).
115
+
116
+ **Environment isolation**: Optional `prepend_environment`/`append_environment` to prevent cross-environment tenant name collisions.
117
+
118
+ **See**: `Apartment.extract_tenant_config` method in `lib/apartment.rb`
119
+
120
+ ## Common Pitfalls
121
+
122
+ **Elevator positioning**: Must be before session/auth middleware. Otherwise session data leaks across tenants.
123
+
124
+ **Not using blocks**: `switch!` without block requires manual cleanup. Easy to forget. Always prefer `switch` with block.
125
+
126
+ **HABTM with excluded models**: Doesn't work. Must use `has_many :through` instead.
127
+
128
+ **Assuming fiber safety**: v3 uses thread-local storage. Not safe for fiber-based async frameworks.
129
+
130
+ **See**: `docs/architecture.md` for detailed analysis
131
+
132
+ ## Performance Characteristics
133
+
134
+ **PostgreSQL schemas**:
135
+ - Switch: <1ms
136
+ - Scalability: 100+ tenants
137
+ - Memory: Constant
138
+
139
+ **MySQL databases**:
140
+ - Switch: 10-50ms
141
+ - Scalability: 10-50 tenants
142
+ - Memory: Linear with active tenants
143
+
144
+ **See**: `docs/adapters.md` for benchmarks and trade-offs
145
+
146
+ ## Testing the Gem
147
+
148
+ **Spec organization**: `spec/adapters/` for database tests, `spec/unit/elevators/` for middleware tests
149
+
150
+ **Database selection**: `DB=postgresql rspec` or `DB=mysql` or `DB=sqlite3`
151
+
152
+ **Key test pattern**: Create test tenant, switch to it, verify isolation, cleanup
153
+
154
+ **See**: `spec/CLAUDE.md` for testing patterns
155
+
156
+ ## Debugging Techniques
157
+
158
+ **Check current tenant**: `Apartment::Tenant.current`
159
+
160
+ **Inspect adapter**: `Apartment::Tenant.adapter.class`
161
+
162
+ **List tenants**: `Apartment.tenant_names`
163
+
164
+ **Enable logging**: `config.active_record_log = true`
165
+
166
+ **PostgreSQL search path**: `SHOW search_path` in SQL console
167
+
168
+ **See**: Inline code comments for context-specific debugging
169
+
170
+ ## Migration to v4
171
+
172
+ **v4 branch**: `man/spec-restart`
173
+
174
+ **Major changes**: Connection pool per tenant (vs thread-local switching), fiber-safe via CurrentAttributes, immutable connection descriptors
175
+
176
+ **Why v4**: Better performance (no switching overhead), true fiber safety, simpler mental model
177
+
178
+ **Migration strategy**: Understand v3 architecture first (this branch), then contrast with v4 approach
179
+
180
+ ## Design Principles
181
+
182
+ **Open for extension**: Users can create custom adapters and elevators without modifying gem.
183
+
184
+ **Closed for modification**: Core logic shouldn't need changes for new use cases.
185
+
186
+ **Fail fast**: Configuration errors raise at boot. Tenant not found raises at runtime.
187
+
188
+ **Graceful degradation**: If rollback fails, fall back to default tenant rather than crash.
189
+
190
+ **See**: `docs/architecture.md` for rationale
191
+
192
+ ## Getting Help
193
+
194
+ **Issues**: https://github.com/rails-on-services/apartment/issues
195
+
196
+ **Discussions**: https://github.com/rails-on-services/apartment/discussions
197
+
198
+ **Code**: Read the actual implementation files - they're well-commented
199
+
200
+ ## Documentation Philosophy
201
+
202
+ **This documentation focuses on WHY, not HOW**:
203
+ - Design decisions and trade-offs
204
+ - Architecture rationale
205
+ - Pitfalls and constraints
206
+ - References to actual source files
207
+
208
+ **For HOW (implementation details)**: Read the well-commented source code in `lib/`.
209
+
210
+ **For WHAT (API reference)**: See README.md and RDoc comments.
@@ -0,0 +1,71 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a safe, welcoming, and inclusive experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behavior that contributes to a positive environment for our community include:
12
+
13
+ - Demonstrating empathy and kindness toward other people
14
+ - Being respectful of differing opinions, viewpoints, and experiences
15
+ - Giving and gracefully accepting constructive feedback
16
+ - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
+ - Focusing on what is best not just for us as individuals, but for the overall community
18
+
19
+ Examples of unacceptable behavior include:
20
+
21
+ - The use of sexualized language or imagery, and sexual attention or advances of any kind
22
+ - Trolling, insulting or derogatory comments, and personal or political attacks
23
+ - Public or private harassment
24
+ - Publishing others’ private information, such as a physical or email address, without their explicit permission
25
+ - Other conduct which could reasonably be considered inappropriate in a professional setting
26
+
27
+ ## Enforcement Responsibilities
28
+
29
+ Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
30
+
31
+ Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned with this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
32
+
33
+ ## Scope
34
+
35
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
36
+
37
+ ## Enforcement
38
+
39
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [mauricio@campusesp.com]. All complaints will be reviewed and investigated promptly and fairly.
40
+
41
+ All community leaders are obligated to respect the privacy and security of the reporter of any incident.
42
+
43
+ ## Enforcement Guidelines
44
+
45
+ Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
46
+
47
+ ### 1. Correction
48
+ **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
49
+
50
+ **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
51
+
52
+ ### 2. Warning
53
+ **Community Impact**: A violation through a single incident or series of actions.
54
+
55
+ **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period. Violating these terms may lead to a temporary or permanent ban.
56
+
57
+ ### 3. Temporary Ban
58
+ **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
59
+
60
+ **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period.
61
+
62
+ ### 4. Permanent Ban
63
+ **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
64
+
65
+ **Consequence**: A permanent ban from any sort of public interaction within the community.
66
+
67
+ ## Attribution
68
+
69
+ This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1, available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
70
+
71
+ For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq.
data/Gemfile CHANGED
@@ -3,3 +3,18 @@
3
3
  source 'http://rubygems.org'
4
4
 
5
5
  gemspec
6
+
7
+ gem 'appraisal', '~> 2.3'
8
+ gem 'bundler', '< 3.0'
9
+ gem 'pry', '~> 0.13'
10
+ gem 'rake', '< 14.0'
11
+ gem 'rspec', '~> 3.10'
12
+ gem 'rspec_junit_formatter', '~> 0.4'
13
+ gem 'rspec-rails', '>= 6.1.0', '< 8.1'
14
+ gem 'rubocop', '~> 1.12'
15
+ gem 'rubocop-performance', '~> 1.10'
16
+ gem 'rubocop-rails', '~> 2.10'
17
+ gem 'rubocop-rake', '~> 0.5'
18
+ gem 'rubocop-rspec', '~> 3.1'
19
+ gem 'rubocop-thread_safety', '~> 0.4'
20
+ gem 'simplecov', require: false