Class: RailsMultisite::ConnectionManagement

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_multisite/connection_management.rb

Constant Summary collapse

DEFAULT =
'default'
SPEC_KLASS =
ActiveRecord::ConnectionAdapters::ConnectionSpecification

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config_filename) ⇒ ConnectionManagement

Returns a new instance of ConnectionManagement.



126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/rails_multisite/connection_management.rb', line 126

def initialize(config_filename)
  @config_filename = config_filename

  @connection_handlers = {}
  @db_spec_cache = {}

  @default_spec = SPEC_KLASS::Resolver.new(ActiveRecord::Base.configurations).spec(Rails.env.to_sym)
  @default_connection_handler = ActiveRecord::Base.connection_handler
  @established_default = false

  @reload_mutex = Mutex.new

  load_config!
end

Instance Attribute Details

#config_filename ⇒ Object (readonly)

Returns the value of attribute config_filename.



124
125
126
# File 'lib/rails_multisite/connection_management.rb', line 124

def config_filename
  @config_filename
end

Class Method Details

.all_dbs ⇒ Object



78
79
80
81
82
83
84
# File 'lib/rails_multisite/connection_management.rb', line 78

def self.all_dbs
  if @instance
    @instance.all_dbs
  else
    [DEFAULT]
  end
end

.clear_settings! ⇒ Object



13
14
15
# File 'lib/rails_multisite/connection_management.rb', line 13

def self.clear_settings!
  @instance = nil
end

.config_filename ⇒ Object



34
35
36
# File 'lib/rails_multisite/connection_management.rb', line 34

def self.config_filename
  @instance.config_filename
end

.config_filename=(config_filename) ⇒ Object



26
27
28
29
30
31
32
# File 'lib/rails_multisite/connection_management.rb', line 26

def self.config_filename=(config_filename)
  if config_filename.nil?
    @instance = nil
  else
    @instance = new(config_filename)
  end
end

.connection_spec(opts) ⇒ Object



108
109
110
111
112
113
114
# File 'lib/rails_multisite/connection_management.rb', line 108

def self.connection_spec(opts)
  if @instance
    @instance.connection_spec(opts)
  else
    ActiveRecord::Base.connection_pool.spec
  end
end

.current_db ⇒ Object



86
87
88
89
90
91
92
# File 'lib/rails_multisite/connection_management.rb', line 86

def self.current_db
  if @instance
    instance.current_db
  else
    DEFAULT
  end
end

.current_db_hostnames ⇒ Object



101
102
103
104
105
106
# File 'lib/rails_multisite/connection_management.rb', line 101

def self.current_db_hostnames
  spec = @instance.connection_spec(db: self.current_db) if @instance
  spec ||= ActiveRecord::Base.connection_pool.spec
  config = spec.config
  config[:host_names].nil? ? [config[:host]] : config[:host_names]
end

.current_hostname ⇒ Object



94
95
96
97
98
99
# File 'lib/rails_multisite/connection_management.rb', line 94

def self.current_hostname
  spec = @instance.connection_spec(db: self.current_db) if @instance
  spec ||= ActiveRecord::Base.connection_pool.spec
  config = spec.config
  config[:host_names].nil? ? config[:host] : config[:host_names].first
end

.default_config_filename ⇒ Object



9
10
11
# File 'lib/rails_multisite/connection_management.rb', line 9

def self.default_config_filename
  File.absolute_path(Rails.root.to_s + "/config/multisite.yml")
end

.each_connection(opts = nil, &blk) ⇒ Object



70
71
72
73
74
75
76
# File 'lib/rails_multisite/connection_management.rb', line 70

def self.each_connection(opts = nil, &blk)
  if @instance
    @instance.each_connection(opts, &blk)
  else
    with_connection(&blk)
  end
end

.establish_connection(opts) ⇒ Object



47
48
49
# File 'lib/rails_multisite/connection_management.rb', line 47

def self.establish_connection(opts)
  @instance.establish_connection(opts) if @instance
end

.has_db?(db) ⇒ Boolean

Returns:

  • (Boolean)


42
43
44
45
# File 'lib/rails_multisite/connection_management.rb', line 42

def self.has_db?(db)
  return true if db == DEFAULT
  !!(@instance && @instance.has_db?(db))
end

.host(env) ⇒ Object



116
117
118
119
120
121
122
# File 'lib/rails_multisite/connection_management.rb', line 116

def self.host(env)
  if @instance
    @instance.host(env)
  else
    env["HTTP_HOST"]
  end
end

.instance ⇒ Object



22
23
24
# File 'lib/rails_multisite/connection_management.rb', line 22

def self.instance
  @instance
end

.load_settings! ⇒ Object



17
18
19
20
# File 'lib/rails_multisite/connection_management.rb', line 17

def self.load_settings!
  # no op only here backwards compat
  STDERR.puts "RailsMultisite::ConnectionManagement.load_settings! is deprecated"
end

.reload ⇒ Object



38
39
40
# File 'lib/rails_multisite/connection_management.rb', line 38

def self.reload
  @instance.reload
end

.with_connection(db = DEFAULT, &blk) ⇒ Object



59
60
61
62
63
64
65
66
67
68
# File 'lib/rails_multisite/connection_management.rb', line 59

def self.with_connection(db = DEFAULT, &blk)
  if @instance
    @instance.with_connection(db, &blk)
  else
    connected = ActiveRecord::Base.connection_pool.connected?
    result = blk.call db
    ActiveRecord::Base.clear_active_connections! unless connected
    result
  end
end

.with_hostname(hostname, &blk) ⇒ Object



51
52
53
54
55
56
57
# File 'lib/rails_multisite/connection_management.rb', line 51

def self.with_hostname(hostname, &blk)
  if @instance
    @instance.with_hostname(hostname, &blk)
  else
    blk.call hostname
  end
end

Instance Method Details

#all_dbs ⇒ Object



328
329
330
# File 'lib/rails_multisite/connection_management.rb', line 328

def all_dbs
  [DEFAULT] + @db_spec_cache.keys.to_a
end

#connection_spec(opts) ⇒ Object



351
352
353
354
355
356
357
# File 'lib/rails_multisite/connection_management.rb', line 351

def connection_spec(opts)
  if opts[:host]
    @host_spec_cache[opts[:host]]
  else
    @db_spec_cache[opts[:db]]
  end
end

#current_db ⇒ Object



332
333
334
# File 'lib/rails_multisite/connection_management.rb', line 332

def current_db
  ActiveRecord::Base.connection_pool.spec.config[:db_key] || DEFAULT
end

#current_hostname ⇒ Object



336
337
338
# File 'lib/rails_multisite/connection_management.rb', line 336

def current_hostname
  ConnectionManagement.current_hostname
end

#each_connection(opts = nil, &blk) ⇒ Object



271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/rails_multisite/connection_management.rb', line 271

def each_connection(opts = nil, &blk)

  old = current_db
  connected = ActiveRecord::Base.connection_pool.connected?

  queue = nil
  threads = nil

  if (opts && (threads = opts[:threads]))
    queue = Queue.new
    all_dbs.each { |db| queue << db }
  end

  errors = nil

  if queue
    threads.times.map do
      Thread.new do

        while true
          begin
            db = queue.deq(true)
          rescue ThreadError
            db = nil
          end

          break unless db

          establish_connection(db: db)
          # no choice but to rescue, should probably log

          begin
            blk.call(db)
          rescue => e
            (errors ||= []) << e
          end
          ActiveRecord::Base.connection_handler.clear_active_connections!
        end
      end
    end.map(&:join)
  else
    all_dbs.each do |db|
      establish_connection(db: db)
      blk.call(db)
      ActiveRecord::Base.connection_handler.clear_active_connections!
    end
  end

  if errors && errors.length > 0
    raise StandardError, "Failed to run queries #{errors.inspect}"
  end

ensure
  establish_connection(db: old)
  ActiveRecord::Base.connection_handler.clear_active_connections! unless connected
end

#establish_connection(opts) ⇒ Object



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/rails_multisite/connection_management.rb', line 206

def establish_connection(opts)
  opts[:db] = opts[:db].to_s

  if opts[:db] != DEFAULT
    spec = connection_spec(opts)

    if (!spec && opts[:raise_on_missing])
      raise "ERROR: #{opts[:db]} not found!"
    end
  end

  spec ||= @default_spec
  handler = nil
  if spec != @default_spec
    handler = @connection_handlers[spec]
    unless handler
      handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
      handler_establish_connection(handler, spec)
      @connection_handlers[spec] = handler
    end
  else
    handler = @default_connection_handler
    if !@established_default
      handler_establish_connection(handler, spec)
      @established_default = true
    end
  end

  ActiveRecord::Base.connection_handler = handler
end

#has_db?(db) ⇒ Boolean

Returns:

  • (Boolean)


201
202
203
204
# File 'lib/rails_multisite/connection_management.rb', line 201

def has_db?(db)
  return true if db == DEFAULT
  @db_spec_cache[db]
end

#host(env) ⇒ Object



340
341
342
343
344
345
346
347
348
349
# File 'lib/rails_multisite/connection_management.rb', line 340

def host(env)
  if host = env["RAILS_MULTISITE_HOST"]
    return host
  end

  request = Rack::Request.new(env)
  host = request['__ws'] || request.host

  env["RAILS_MULTISITE_HOST"] = host
end

#load_config! ⇒ Object



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
# File 'lib/rails_multisite/connection_management.rb', line 141

def load_config!
  configs = YAML::load(File.open(@config_filename))

  no_prepared_statements = ActiveRecord::Base.configurations[Rails.env]["prepared_statements"] == false

  configs.each do |k, v|
    raise ArgumentError.new("Please do not name any db default!") if k == DEFAULT
    v[:db_key] = k
    v[:prepared_statements] = false if no_prepared_statements
  end

  resolve_configs = configs

  # rails 6 needs to use a proper object for the resolver
  if defined?(ActiveRecord::DatabaseConfigurations)
    resolve_configs = ActiveRecord::DatabaseConfigurations.new(configs)
  end

  resolver = SPEC_KLASS::Resolver.new(resolve_configs)

  # Build a hash of db name => spec
  new_db_spec_cache = Hash[*configs.map { |k, _| [k, resolver.spec(k.to_sym)] }.flatten]
  new_db_spec_cache.each do |k, v|
    # If spec already existed, use the old version
    if v&.to_hash == @db_spec_cache[k]&.to_hash
      new_db_spec_cache[k] = @db_spec_cache[k]
    end
  end

  # Build a hash of hostname => spec
  new_host_spec_cache = {}
  configs.each do |k, v|
    next unless v["host_names"]
    v["host_names"].each do |host|
      new_host_spec_cache[host] = new_db_spec_cache[k]
    end
  end

  # Add the default hostnames as well
  ActiveRecord::Base.configurations[Rails.env]["host_names"].each do |host|
    new_host_spec_cache[host] = @default_spec
  end

  removed_dbs = @db_spec_cache.keys - new_db_spec_cache.keys
  removed_specs = @db_spec_cache.values_at(*removed_dbs)

  @host_spec_cache = new_host_spec_cache
  @db_spec_cache = new_db_spec_cache

  # Clean up connection handler cache.
  # (@connection_handlers is a hash of spec => handler)
  removed_specs.each { |s| @connection_handlers.delete(s) }
end

#reload ⇒ Object



195
196
197
198
199
# File 'lib/rails_multisite/connection_management.rb', line 195

def reload
  @reload_mutex.synchronize do
    load_config!
  end
end

#with_connection(db = DEFAULT) ⇒ Object



254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/rails_multisite/connection_management.rb', line 254

def with_connection(db = DEFAULT)
  old = current_db
  connected = ActiveRecord::Base.connection_pool.connected?

  establish_connection(db: db) unless connected && db == old
  rval = yield db

  unless connected && db == old
    ActiveRecord::Base.connection_handler.clear_active_connections!

    establish_connection(db: old)
    ActiveRecord::Base.connection_handler.clear_active_connections! unless connected
  end

  rval
end

#with_hostname(hostname) ⇒ Object



237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/rails_multisite/connection_management.rb', line 237

def with_hostname(hostname)
  old = current_hostname
  connected = ActiveRecord::Base.connection_pool.connected?

  establish_connection(host: hostname) unless connected && hostname == old
  rval = yield hostname

  unless connected && hostname == old
    ActiveRecord::Base.connection_handler.clear_active_connections!

    establish_connection(host: old)
    ActiveRecord::Base.connection_handler.clear_active_connections! unless connected
  end

  rval
end