Class: Apartment::Config

Inherits:
Object
  • Object
show all
Defined in:
lib/apartment/config.rb

Overview

Configuration object for Apartment v4. Created via Apartment.configure block; validated after the block yields.

Constant Summary collapse

VALID_STRATEGIES =

rubocop:disable Metrics/ClassLength

%i[schema database_name shard database_config].freeze
VALID_ENVIRONMENTIFY_STRATEGIES =
[nil, :prepend, :append].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeConfig

rubocop:disable Metrics/AbcSize



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/apartment/config.rb', line 31

def initialize # rubocop:disable Metrics/AbcSize
  @tenant_strategy = nil
  @tenants_provider = nil
  @default_tenant = nil
  @default_tenant_switch_allowed = true
  @excluded_models = []
  @tenant_pool_size = nil
  @pool_idle_timeout = 300
  @reaper_interval = nil
  @max_total_connections = nil
  @pool_overflow_policy = :evict_idle
  @seed_after_create = false
  @seed_data_file = nil
  @schema_load_strategy = nil
  @schema_file = nil
  @parallel_migration_threads = 0
  @environmentify_strategy = nil
  @elevator = nil
  @elevator_options = {}
  @tenant_not_found_handler = nil
  @tenant_validator = nil
  @active_record_log = false
  @sql_query_tags = false
  @postgres_config = nil
  @mysql_config = nil
  @shard_key_prefix = 'apartment'
  @migration_role = nil
  @app_role = nil
  @schema_cache_per_tenant = false
  @check_pending_migrations = true
  @force_separate_pinned_pool = false
  @test_fixture_cleanup = true
end

Instance Attribute Details

#active_record_logObject

Returns the value of attribute active_record_log.



17
18
19
# File 'lib/apartment/config.rb', line 17

def active_record_log
  @active_record_log
end

#app_roleObject

Returns the value of attribute app_role.



17
18
19
# File 'lib/apartment/config.rb', line 17

def app_role
  @app_role
end

#check_pending_migrationsObject

Returns the value of attribute check_pending_migrations.



17
18
19
# File 'lib/apartment/config.rb', line 17

def check_pending_migrations
  @check_pending_migrations
end

#default_tenantObject

Returns the value of attribute default_tenant.



17
18
19
# File 'lib/apartment/config.rb', line 17

def default_tenant
  @default_tenant
end

#default_tenant_switch_allowedObject

Returns the value of attribute default_tenant_switch_allowed.



17
18
19
# File 'lib/apartment/config.rb', line 17

def default_tenant_switch_allowed
  @default_tenant_switch_allowed
end

#elevatorObject

Returns the value of attribute elevator.



17
18
19
# File 'lib/apartment/config.rb', line 17

def elevator
  @elevator
end

#elevator_optionsObject

Returns the value of attribute elevator_options.



17
18
19
# File 'lib/apartment/config.rb', line 17

def elevator_options
  @elevator_options
end

#environmentify_strategyObject

Returns the value of attribute environmentify_strategy.



14
15
16
# File 'lib/apartment/config.rb', line 14

def environmentify_strategy
  @environmentify_strategy
end

#excluded_modelsObject

Returns the value of attribute excluded_models.



14
15
16
# File 'lib/apartment/config.rb', line 14

def excluded_models
  @excluded_models
end

#force_separate_pinned_poolObject

Returns the value of attribute force_separate_pinned_pool.



17
18
19
# File 'lib/apartment/config.rb', line 17

def force_separate_pinned_pool
  @force_separate_pinned_pool
end

#max_total_connectionsObject

Returns the value of attribute max_total_connections.



17
18
19
# File 'lib/apartment/config.rb', line 17

def max_total_connections
  @max_total_connections
end

#migration_roleObject

Returns the value of attribute migration_role.



17
18
19
# File 'lib/apartment/config.rb', line 17

def migration_role
  @migration_role
end

#mysql_configObject (readonly)

Returns the value of attribute mysql_config.



14
15
16
# File 'lib/apartment/config.rb', line 14

def mysql_config
  @mysql_config
end

#parallel_migration_threadsObject

Returns the value of attribute parallel_migration_threads.



17
18
19
# File 'lib/apartment/config.rb', line 17

def parallel_migration_threads
  @parallel_migration_threads
end

#pool_idle_timeoutObject

Returns the value of attribute pool_idle_timeout.



17
18
19
# File 'lib/apartment/config.rb', line 17

def pool_idle_timeout
  @pool_idle_timeout
end

#pool_overflow_policyObject

Returns the value of attribute pool_overflow_policy.



17
18
19
# File 'lib/apartment/config.rb', line 17

def pool_overflow_policy
  @pool_overflow_policy
end

#postgres_configObject (readonly)

Returns the value of attribute postgres_config.



14
15
16
# File 'lib/apartment/config.rb', line 14

def postgres_config
  @postgres_config
end

#reaper_intervalObject

Returns the value of attribute reaper_interval.



17
18
19
# File 'lib/apartment/config.rb', line 17

def reaper_interval
  @reaper_interval
end

#schema_cache_per_tenantObject

Returns the value of attribute schema_cache_per_tenant.



17
18
19
# File 'lib/apartment/config.rb', line 17

def schema_cache_per_tenant
  @schema_cache_per_tenant
end

#schema_fileObject

Returns the value of attribute schema_file.



17
18
19
# File 'lib/apartment/config.rb', line 17

def schema_file
  @schema_file
end

#schema_load_strategyObject

Returns the value of attribute schema_load_strategy.



17
18
19
# File 'lib/apartment/config.rb', line 17

def schema_load_strategy
  @schema_load_strategy
end

#seed_after_createObject

Returns the value of attribute seed_after_create.



17
18
19
# File 'lib/apartment/config.rb', line 17

def seed_after_create
  @seed_after_create
end

#seed_data_fileObject

Returns the value of attribute seed_data_file.



17
18
19
# File 'lib/apartment/config.rb', line 17

def seed_data_file
  @seed_data_file
end

#shard_key_prefixObject

Returns the value of attribute shard_key_prefix.



17
18
19
# File 'lib/apartment/config.rb', line 17

def shard_key_prefix
  @shard_key_prefix
end

#sql_query_tagsObject

Returns the value of attribute sql_query_tags.



17
18
19
# File 'lib/apartment/config.rb', line 17

def sql_query_tags
  @sql_query_tags
end

#tenant_not_found_handlerObject

Returns the value of attribute tenant_not_found_handler.



17
18
19
# File 'lib/apartment/config.rb', line 17

def tenant_not_found_handler
  @tenant_not_found_handler
end

#tenant_pool_sizeObject

Returns the value of attribute tenant_pool_size.



17
18
19
# File 'lib/apartment/config.rb', line 17

def tenant_pool_size
  @tenant_pool_size
end

#tenant_strategyObject

Returns the value of attribute tenant_strategy.



14
15
16
# File 'lib/apartment/config.rb', line 14

def tenant_strategy
  @tenant_strategy
end

#tenant_validatorObject

Returns the value of attribute tenant_validator.



17
18
19
# File 'lib/apartment/config.rb', line 17

def tenant_validator
  @tenant_validator
end

#tenants_providerObject

Returns the value of attribute tenants_provider.



17
18
19
# File 'lib/apartment/config.rb', line 17

def tenants_provider
  @tenants_provider
end

#test_fixture_cleanupObject

Returns the value of attribute test_fixture_cleanup.



17
18
19
# File 'lib/apartment/config.rb', line 17

def test_fixture_cleanup
  @test_fixture_cleanup
end

Instance Method Details

#apply_defaults!Object

Apply derived defaults that depend on user-set values. Runs after the configure block yields and before validate!, so validate! stays read-only.



120
121
122
123
124
125
126
# File 'lib/apartment/config.rb', line 120

def apply_defaults!
  # PostgreSQL's default schema is 'public'; avoid forcing every user to set it.
  @default_tenant ||= 'public' if @tenant_strategy == :schema
  # Reap on the idle-timeout cadence unless an explicit interval decouples
  # the two (reap more often without shrinking the idle window).
  @reaper_interval = @pool_idle_timeout if @reaper_interval.nil?
end

#configure_mysql {|@mysql_config| ... } ⇒ Object

Configure MySQL-specific options via block.

Yields:



100
101
102
103
104
# File 'lib/apartment/config.rb', line 100

def configure_mysql
  @mysql_config = Configs::MysqlConfig.new
  yield(@mysql_config) if block_given?
  @mysql_config
end

#configure_postgres {|@postgres_config| ... } ⇒ Object

Configure PostgreSQL-specific options via block.

Yields:



93
94
95
96
97
# File 'lib/apartment/config.rb', line 93

def configure_postgres
  @postgres_config = Configs::PostgresqlConfig.new
  yield(@postgres_config) if block_given?
  @postgres_config
end

#freeze!Object

Deep-freeze the config after validation to prevent post-boot mutation. Freezes mutable collections and sub-configs, then freezes self.



108
109
110
111
112
113
114
115
116
# File 'lib/apartment/config.rb', line 108

def freeze!
  @excluded_models.freeze
  @elevator_options.freeze
  @postgres_config&.freeze!
  @mysql_config&.freeze!
  # schema_file is a simple string, no deep freeze needed
  @app_role.freeze if @app_role.is_a?(String)
  freeze
end

#rails_env_nameObject

Returns the current Rails environment name, falling back to env vars and a safe default.



232
233
234
# File 'lib/apartment/config.rb', line 232

def rails_env_name
  (Rails.env if defined?(Rails.env)) || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'default_env'
end

#validate!Object

Validate configuration completeness and consistency. Raises ConfigurationError on invalid state.

Raises:



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/apartment/config.rb', line 130

def validate! # rubocop:disable Metrics/AbcSize
  raise(ConfigurationError, 'tenant_strategy is required') unless @tenant_strategy

  if @default_tenant.is_a?(String) && @default_tenant.strip.empty?
    raise(ConfigurationError, 'default_tenant cannot be an empty string')
  end

  unless [true, false].include?(@default_tenant_switch_allowed)
    raise(ConfigurationError,
          'default_tenant_switch_allowed must be true or false, got: ' \
          "#{@default_tenant_switch_allowed.inspect}")
  end

  unless @tenants_provider.respond_to?(:call)
    raise(ConfigurationError, 'tenants_provider must be a callable (e.g., -> { Tenant.pluck(:name) })')
  end

  if @postgres_config && @mysql_config
    raise(ConfigurationError, 'Cannot configure both Postgres and MySQL at the same time')
  end

  @postgres_config&.validate!

  if @tenant_pool_size && (!@tenant_pool_size.is_a?(Integer) || @tenant_pool_size < 1)
    raise(ConfigurationError,
          "tenant_pool_size must be a positive integer or nil, got: #{@tenant_pool_size.inspect}")
  end

  unless @pool_idle_timeout.is_a?(Numeric) && @pool_idle_timeout.positive?
    raise(ConfigurationError, "pool_idle_timeout must be a positive number, got: #{@pool_idle_timeout.inspect}")
  end

  # nil is valid pre-apply_defaults! (it derives from pool_idle_timeout); a
  # set value must be a positive number.
  if @reaper_interval && (!@reaper_interval.is_a?(Numeric) || !@reaper_interval.positive?)
    raise(ConfigurationError, "reaper_interval must be a positive number or nil, got: #{@reaper_interval.inspect}")
  end

  if @max_total_connections && (!@max_total_connections.is_a?(Integer) || @max_total_connections < 1)
    raise(ConfigurationError,
          "max_total_connections must be a positive integer or nil, got: #{@max_total_connections.inspect}")
  end

  unless %i[evict_idle raise].include?(@pool_overflow_policy)
    raise(ConfigurationError,
          'pool_overflow_policy must be :evict_idle or :raise, ' \
          "got: #{@pool_overflow_policy.inspect}")
  end

  unless [nil, :schema_rb, :sql].include?(@schema_load_strategy)
    raise(ConfigurationError, "Invalid schema_load_strategy: #{@schema_load_strategy.inspect}. " \
                              'Must be nil, :schema_rb, or :sql')
  end

  if @migration_role && !@migration_role.is_a?(Symbol)
    raise(ConfigurationError, "migration_role must be nil or a Symbol, got: #{@migration_role.inspect}")
  end

  if @app_role && !@app_role.is_a?(String) && !@app_role.respond_to?(:call)
    raise(ConfigurationError, "app_role must be nil, a String, or a callable, got: #{@app_role.inspect}")
  end

  unless [true, false].include?(@schema_cache_per_tenant)
    raise(ConfigurationError,
          "schema_cache_per_tenant must be true or false, got: #{@schema_cache_per_tenant.inspect}")
  end

  unless [true, false].include?(@check_pending_migrations)
    raise(ConfigurationError,
          "check_pending_migrations must be true or false, got: #{@check_pending_migrations.inspect}")
  end

  unless [true, false].include?(@force_separate_pinned_pool)
    raise(ConfigurationError,
          "force_separate_pinned_pool must be true or false, got: #{@force_separate_pinned_pool.inspect}")
  end

  unless [true, false].include?(@test_fixture_cleanup)
    raise(ConfigurationError,
          "test_fixture_cleanup must be true or false, got: #{@test_fixture_cleanup.inspect}")
  end

  unless @tenant_validator.nil? || @tenant_validator == false || @tenant_validator.respond_to?(:call)
    raise(ConfigurationError,
          'tenant_validator must be nil, false, or a callable, ' \
          "got: #{@tenant_validator.inspect}")
  end

  if @tenant_not_found_handler && !@tenant_not_found_handler.respond_to?(:call)
    raise(ConfigurationError,
          'tenant_not_found_handler must be nil or a callable, ' \
          "got: #{@tenant_not_found_handler.inspect}")
  end

  return if @shard_key_prefix.is_a?(String) && @shard_key_prefix.match?(/\A[a-z_][a-z0-9_]*\z/)

  raise(ConfigurationError,
        'shard_key_prefix must be a lowercase string matching /[a-z_][a-z0-9_]*/, ' \
        "got: #{@shard_key_prefix.inspect}")
end