Module: TalkToYourApp::ConnectionRegistry
- Defined in:
- lib/talk_to_your_app/connection_registry.rb
Overview
The named-connection registry. Operators declare connections in the initializer (‘config.connection :name, database:, role:`); plugins reference them by the gem-internal name. The registry’s job is twofold:
1. Fail closed at boot — if a plugin requires a connection that was never
declared, or a declared connection points at a database.yml key that
does not exist, raise during boot rather than at the first request.
2. Switch connections at call time via Rails' `connected_to`, so each tool
runs against the role its plugin declared (reads on a reader, writes on
a writer) without coupling plugin code to the host's database.yml.
Defined Under Namespace
Classes: ConnectionSpec
Constant Summary collapse
- BUILD_MUTEX =
Guards lazy pool-class construction against concurrent first-calls.
Mutex.new
Class Method Summary collapse
-
.build_connection_class(spec, index) ⇒ Object
‘connects_to` rejects anonymous classes, so each pool class gets a stable constant name.
-
.connection_class_for(spec) ⇒ Object
Lazily builds (and caches) an abstract ActiveRecord class wired to the spec’s database under its role.
- .connection_classes ⇒ Object
- .database_config_exists?(db_key) ⇒ Boolean
- .env_name ⇒ Object
- .fetch(name) ⇒ Object
- .registered?(name) ⇒ Boolean
-
.reset! ⇒ Object
Test seam: drop the cached pool classes and their constants so a reconfiguration in a later test does not reuse a stale pool.
- .specs ⇒ Object
-
.validate!(requirements) ⇒ Object
Fail-closed boot check.
-
.with(name) ⇒ Object
Runs the block against the connection declared under ‘name`, switched to the spec’s role.
Class Method Details
.build_connection_class(spec, index) ⇒ Object
‘connects_to` rejects anonymous classes, so each pool class gets a stable constant name. The index suffix guarantees uniqueness even when two database keys differ only in characters `W`-normalization would collapse.
98 99 100 101 102 103 104 105 |
# File 'lib/talk_to_your_app/connection_registry.rb', line 98 def build_connection_class(spec, index) const_name = "Conn#{index}_#{spec.database}_#{spec.role}".gsub(/\W/, "_") remove_const(const_name) if const_defined?(const_name, false) klass = Class.new(ActiveRecord::Base) { self.abstract_class = true } const_set(const_name, klass) klass.connects_to(database: { spec.role => spec.database }) klass end |
.connection_class_for(spec) ⇒ Object
Lazily builds (and caches) an abstract ActiveRecord class wired to the spec’s database under its role. Cached by (database, role) so the same declared connection reuses one pool across calls. The build is guarded by a mutex so concurrent first-calls under a threaded server cannot create two pools for the same key.
85 86 87 88 89 90 91 92 93 |
# File 'lib/talk_to_your_app/connection_registry.rb', line 85 def connection_class_for(spec) cache_key = [spec.database, spec.role] existing = connection_classes[cache_key] return existing if existing BUILD_MUTEX.synchronize do connection_classes[cache_key] ||= build_connection_class(spec, connection_classes.size) end end |
.connection_classes ⇒ Object
127 128 129 |
# File 'lib/talk_to_your_app/connection_registry.rb', line 127 def connection_classes @connection_classes ||= {} end |
.database_config_exists?(db_key) ⇒ Boolean
117 118 119 120 121 |
# File 'lib/talk_to_your_app/connection_registry.rb', line 117 def database_config_exists?(db_key) ActiveRecord::Base.configurations .configs_for(env_name: env_name, include_hidden: true) .any? { |config| config.name.to_sym == db_key.to_sym } end |
.env_name ⇒ Object
123 124 125 |
# File 'lib/talk_to_your_app/connection_registry.rb', line 123 def env_name (defined?(Rails) && Rails.respond_to?(:env) && Rails.env.to_s) || ENV["RAILS_ENV"] || "test" end |
.fetch(name) ⇒ Object
41 42 43 44 45 |
# File 'lib/talk_to_your_app/connection_registry.rb', line 41 def fetch(name) specs[name.to_sym] || raise(ConfigurationError, "talk_to_your_app: connection #{name.inspect} is not registered. " \ "Declare it with `config.connection #{name.inspect}, database: ..., role: ...` in your initializer.") end |
.registered?(name) ⇒ Boolean
37 38 39 |
# File 'lib/talk_to_your_app/connection_registry.rb', line 37 def registered?(name) specs.key?(name.to_sym) end |
.reset! ⇒ Object
Test seam: drop the cached pool classes and their constants so a reconfiguration in a later test does not reuse a stale pool. Wired into TalkToYourApp.reset_configuration!.
110 111 112 113 114 115 |
# File 'lib/talk_to_your_app/connection_registry.rb', line 110 def reset! BUILD_MUTEX.synchronize do constants(false).grep(/\AConn\d+_/).each { |const| remove_const(const) } @connection_classes = {} end end |
.specs ⇒ Object
33 34 35 |
# File 'lib/talk_to_your_app/connection_registry.rb', line 33 def specs TalkToYourApp.configuration.connections end |
.validate!(requirements) ⇒ Object
Fail-closed boot check. ‘requirements` is an array of
- connection_name, requester_label
-
pairs gathered from enabled plugins.
Raises ConfigurationError naming every missing connection and who needs it.
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/talk_to_your_app/connection_registry.rb', line 50 def validate!(requirements) missing = requirements.reject { |name, _requester| registered?(name) } unless missing.empty? details = missing.map { |name, requester| "#{name.inspect} (required by #{requester})" }.join(", ") raise ConfigurationError, "talk_to_your_app: missing required connection(s): #{details}. " \ "Declare them with `config.connection ...` in config/initializers/talk_to_your_app.rb." end requirements.each do |name, requester| spec = fetch(name) next if database_config_exists?(spec.database) raise ConfigurationError, "talk_to_your_app: connection #{name.inspect} (required by #{requester}) references database " \ "#{spec.database.inspect}, which is not configured in database.yml for the #{env_name.inspect} environment." end end |
.with(name) ⇒ Object
Runs the block against the connection declared under ‘name`, switched to the spec’s role. The connection is yielded; the role switch is unwound when the block returns, so nothing leaks into the surrounding request.
72 73 74 75 76 77 78 |
# File 'lib/talk_to_your_app/connection_registry.rb', line 72 def with(name) spec = fetch(name) klass = connection_class_for(spec) klass.connected_to(role: spec.role) do yield klass.connection end end |