pgbouncerhero 2.0.0 → 3.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +64 -0
- data/README.md +155 -58
- data/app/assets/builds/pgbouncerhero/application.css +2 -0
- data/app/assets/stylesheets/pgbouncerhero/application.css +1 -0
- data/app/controllers/pg_bouncer_hero/application_controller.rb +6 -9
- data/app/controllers/pg_bouncer_hero/database_controller.rb +39 -13
- data/app/controllers/pg_bouncer_hero/home_controller.rb +0 -2
- data/app/helpers/pg_bouncer_hero/application_helper.rb +19 -13
- data/app/javascript/pgbouncerhero/application.js +6 -0
- data/app/javascript/pgbouncerhero/controllers/polling_controller.js +27 -0
- data/app/views/layouts/pg_bouncer_hero/application.html.erb +19 -22
- data/app/views/pg_bouncer_hero/database/_actions.html.erb +9 -9
- data/app/views/pg_bouncer_hero/database/_clients.html.erb +21 -23
- data/app/views/pg_bouncer_hero/database/_conf.html.erb +21 -23
- data/app/views/pg_bouncer_hero/database/_databases.html.erb +21 -23
- data/app/views/pg_bouncer_hero/database/_menu.html.erb +20 -18
- data/app/views/pg_bouncer_hero/database/_pools.html.erb +21 -23
- data/app/views/pg_bouncer_hero/database/_state.html.erb +11 -0
- data/app/views/pg_bouncer_hero/database/_stats.html.erb +27 -33
- data/app/views/pg_bouncer_hero/database/state.html.erb +4 -0
- data/app/views/pg_bouncer_hero/database/summary.html.erb +3 -0
- data/app/views/pg_bouncer_hero/home/_card.html.erb +9 -22
- data/app/views/pg_bouncer_hero/home/_card_content.html.erb +33 -33
- data/app/views/pg_bouncer_hero/home/_card_loading_content.html.erb +5 -5
- data/app/views/pg_bouncer_hero/home/index.html.erb +13 -37
- data/app/views/shared/_alert.html.erb +2 -5
- data/config/importmap.rb +5 -0
- data/config/routes.rb +10 -2
- data/lib/generators/pgbouncerhero/templates/config.yml +13 -8
- data/lib/pgbouncerhero/connection.rb +53 -16
- data/lib/pgbouncerhero/database.rb +69 -4
- data/lib/pgbouncerhero/engine.rb +13 -10
- data/lib/pgbouncerhero/group.rb +0 -2
- data/lib/pgbouncerhero/methods/basics.rb +28 -12
- data/lib/pgbouncerhero/version.rb +1 -1
- data/lib/pgbouncerhero.rb +124 -41
- metadata +187 -46
- data/.gitignore +0 -19
- data/Gemfile +0 -4
- data/app/assets/images/pgbouncerhero/short-paragraph.png +0 -0
- data/app/assets/javascripts/pgbouncerhero/application.js +0 -3
- data/app/assets/stylesheets/pgbouncerhero/application.css.scss +0 -15
- data/app/views/pg_bouncer_hero/database/summary.js.erb +0 -5
- data/doc/screenshot-1.png +0 -0
- data/doc/screenshot-2.png +0 -0
- data/doc/screenshot-3.png +0 -0
- data/pgbouncerhero.gemspec +0 -32
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
module PgBouncerHero
|
|
2
2
|
class Connection
|
|
3
|
-
|
|
4
3
|
def initialize(host, port, user, password, dbname)
|
|
5
4
|
@host = host
|
|
6
5
|
@port = port
|
|
@@ -11,22 +10,60 @@ module PgBouncerHero
|
|
|
11
10
|
end
|
|
12
11
|
|
|
13
12
|
def connection
|
|
14
|
-
@connection
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
13
|
+
disconnect! if @connection && connection_invalid?(@connection)
|
|
14
|
+
@connection ||= connect
|
|
15
|
+
rescue StandardError => e
|
|
16
|
+
Rails.logger.error("[PGBouncerHero] Host:#{@host} | Database Name:#{@dbname} | Timeout: #{@timeout}s => #{e}")
|
|
17
|
+
nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def connected?
|
|
21
|
+
!connection.nil?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def with_connection
|
|
25
|
+
raw_connection = connection
|
|
26
|
+
return unless raw_connection
|
|
27
|
+
|
|
28
|
+
yield raw_connection
|
|
29
|
+
rescue PG::Error
|
|
30
|
+
disconnect!
|
|
31
|
+
raise
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def disconnect!
|
|
35
|
+
@connection&.finish unless @connection&.finished?
|
|
36
|
+
rescue PG::Error
|
|
37
|
+
nil
|
|
38
|
+
ensure
|
|
39
|
+
@connection = nil
|
|
29
40
|
end
|
|
30
41
|
|
|
42
|
+
def method_missing(method_name, *, **, &)
|
|
43
|
+
with_connection { |raw_connection| raw_connection.public_send(method_name, *, **, &) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
47
|
+
PG::Connection.public_instance_methods(include_private).include?(method_name) || super
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def connect
|
|
53
|
+
PG.connect(
|
|
54
|
+
host: @host,
|
|
55
|
+
port: @port,
|
|
56
|
+
user: @user,
|
|
57
|
+
password: @password,
|
|
58
|
+
dbname: @dbname,
|
|
59
|
+
connect_timeout: @timeout
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def connection_invalid?(connection)
|
|
64
|
+
connection.finished? || connection.status != PG::CONNECTION_OK
|
|
65
|
+
rescue PG::Error
|
|
66
|
+
true
|
|
67
|
+
end
|
|
31
68
|
end
|
|
32
69
|
end
|
|
@@ -1,15 +1,23 @@
|
|
|
1
|
+
require "connection_pool"
|
|
2
|
+
require "monitor"
|
|
3
|
+
|
|
1
4
|
module PgBouncerHero
|
|
2
5
|
class Database
|
|
3
|
-
|
|
4
6
|
include Methods::Basics
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
DEFAULT_POOL_SIZE = 5
|
|
9
|
+
DEFAULT_POOL_TIMEOUT = 5
|
|
10
|
+
|
|
11
|
+
attr_reader :id, :config, :group, :pool_size, :pool_timeout
|
|
7
12
|
|
|
8
13
|
def initialize(group, id, config)
|
|
9
14
|
@id = id
|
|
10
15
|
@config = config || {}
|
|
11
|
-
@url = URI.parse(config["url"])
|
|
16
|
+
@url = URI.parse(@config["url"].to_s)
|
|
12
17
|
@group = group
|
|
18
|
+
@pool_size = positive_integer_setting("pool_size", "PGBOUNCERHERO_POOL_SIZE", DEFAULT_POOL_SIZE)
|
|
19
|
+
@pool_timeout = positive_float_setting("pool_timeout", "PGBOUNCERHERO_POOL_TIMEOUT", DEFAULT_POOL_TIMEOUT)
|
|
20
|
+
@pool_monitor = Monitor.new
|
|
13
21
|
end
|
|
14
22
|
|
|
15
23
|
def name
|
|
@@ -17,7 +25,28 @@ module PgBouncerHero
|
|
|
17
25
|
end
|
|
18
26
|
|
|
19
27
|
def connection
|
|
20
|
-
|
|
28
|
+
proxy = connection_proxy
|
|
29
|
+
proxy if with_connection { true }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def with_connection
|
|
33
|
+
pool = connection_pool
|
|
34
|
+
pool.with do |managed_connection|
|
|
35
|
+
managed_connection.with_connection { |raw_connection| yield raw_connection }
|
|
36
|
+
end
|
|
37
|
+
rescue ConnectionPool::TimeoutError => e
|
|
38
|
+
Rails.logger.error("[PGBouncerHero] #{name} connection pool timed out after #{pool_timeout}s: #{e.message}")
|
|
39
|
+
nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def disconnect!
|
|
43
|
+
pool = @pool_monitor.synchronize do
|
|
44
|
+
current_pool = @connection_pool
|
|
45
|
+
@connection_pool = nil
|
|
46
|
+
@connection_proxy = nil
|
|
47
|
+
current_pool
|
|
48
|
+
end
|
|
49
|
+
pool&.shutdown(&:disconnect!)
|
|
21
50
|
end
|
|
22
51
|
|
|
23
52
|
def host
|
|
@@ -42,6 +71,26 @@ module PgBouncerHero
|
|
|
42
71
|
|
|
43
72
|
private
|
|
44
73
|
|
|
74
|
+
def execute(command)
|
|
75
|
+
with_connection { |raw_connection| raw_connection.exec(command) }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def connection_pool
|
|
79
|
+
@pool_monitor.synchronize do
|
|
80
|
+
@connection_pool ||= ConnectionPool.new(size: pool_size, timeout: pool_timeout) { build_connection }
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def connection_proxy
|
|
85
|
+
@pool_monitor.synchronize do
|
|
86
|
+
@connection_proxy ||= ConnectionPool::Wrapper.new(pool: connection_pool)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def build_connection
|
|
91
|
+
connection_model.new(host, port, user, password, dbname)
|
|
92
|
+
end
|
|
93
|
+
|
|
45
94
|
def connection_model
|
|
46
95
|
@connection_model ||= begin
|
|
47
96
|
Class.new(PgBouncerHero::Connection) do
|
|
@@ -51,5 +100,21 @@ module PgBouncerHero
|
|
|
51
100
|
end
|
|
52
101
|
end
|
|
53
102
|
end
|
|
103
|
+
|
|
104
|
+
def positive_integer_setting(config_key, environment_key, default)
|
|
105
|
+
value = config.fetch(config_key, ENV.fetch(environment_key, default))
|
|
106
|
+
parsed = Integer(value)
|
|
107
|
+
raise ArgumentError, "#{config_key} must be greater than zero" unless parsed.positive?
|
|
108
|
+
|
|
109
|
+
parsed
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def positive_float_setting(config_key, environment_key, default)
|
|
113
|
+
value = config.fetch(config_key, ENV.fetch(environment_key, default))
|
|
114
|
+
parsed = Float(value)
|
|
115
|
+
raise ArgumentError, "#{config_key} must be a finite number greater than zero" unless parsed.positive? && parsed.finite?
|
|
116
|
+
|
|
117
|
+
parsed
|
|
118
|
+
end
|
|
54
119
|
end
|
|
55
120
|
end
|
data/lib/pgbouncerhero/engine.rb
CHANGED
|
@@ -2,18 +2,21 @@ module PgBouncerHero
|
|
|
2
2
|
class Engine < ::Rails::Engine
|
|
3
3
|
isolate_namespace PgBouncerHero
|
|
4
4
|
|
|
5
|
-
initializer "pgbouncerhero"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
initializer "pgbouncerhero.assets" do |app|
|
|
6
|
+
app.config.assets.paths << root.join("app/assets/builds")
|
|
7
|
+
app.config.assets.paths << root.join("app/javascript")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
initializer "pgbouncerhero.importmap", before: "importmap" do |app|
|
|
11
|
+
PgBouncerHero.importmap.draw root.join("config/importmap.rb")
|
|
12
|
+
PgBouncerHero.importmap.cache_sweeper watches: root.join("app/javascript")
|
|
13
|
+
|
|
14
|
+
ActiveSupport.on_load(:action_controller_base) do
|
|
15
|
+
before_action { PgBouncerHero.importmap.cache_sweeper.execute_if_updated }
|
|
15
16
|
end
|
|
17
|
+
end
|
|
16
18
|
|
|
19
|
+
initializer "pgbouncerhero.configuration" do
|
|
17
20
|
PgBouncerHero.time_zone = PgBouncerHero.config["time_zone"] if PgBouncerHero.config["time_zone"]
|
|
18
21
|
end
|
|
19
22
|
end
|
data/lib/pgbouncerhero/group.rb
CHANGED
|
@@ -1,42 +1,58 @@
|
|
|
1
1
|
module PgBouncerHero
|
|
2
2
|
module Methods
|
|
3
3
|
module Basics
|
|
4
|
+
SHUTDOWN_MODES = {
|
|
5
|
+
nil => "",
|
|
6
|
+
immediate: "",
|
|
7
|
+
wait_for_clients: " WAIT_FOR_CLIENTS",
|
|
8
|
+
wait_for_servers: " WAIT_FOR_SERVERS"
|
|
9
|
+
}.freeze
|
|
10
|
+
|
|
4
11
|
def summary
|
|
5
12
|
if connection
|
|
6
13
|
l = lists
|
|
7
14
|
d = databases
|
|
8
15
|
l = l.as_json
|
|
9
|
-
d = d.as_json.reject { |a| a[
|
|
10
|
-
l.push({databases_details: d})
|
|
16
|
+
d = d.as_json.reject { |a| a["name"] == "pgbouncer" }
|
|
17
|
+
l.push({ databases_details: d })
|
|
11
18
|
l
|
|
12
19
|
end
|
|
13
20
|
end
|
|
14
21
|
def databases
|
|
15
|
-
|
|
22
|
+
execute("SHOW databases")
|
|
16
23
|
end
|
|
17
24
|
def stats
|
|
18
|
-
|
|
25
|
+
execute("SHOW stats")
|
|
19
26
|
end
|
|
20
27
|
def lists
|
|
21
|
-
|
|
28
|
+
execute("SHOW lists")
|
|
22
29
|
end
|
|
23
30
|
def pools
|
|
24
|
-
|
|
31
|
+
execute("SHOW pools")
|
|
25
32
|
end
|
|
26
33
|
def clients
|
|
27
|
-
|
|
34
|
+
execute("SHOW clients")
|
|
28
35
|
end
|
|
29
36
|
def conf
|
|
30
|
-
|
|
37
|
+
execute("SHOW config")
|
|
38
|
+
end
|
|
39
|
+
def state
|
|
40
|
+
execute("SHOW state")
|
|
31
41
|
end
|
|
32
42
|
def reload
|
|
33
|
-
|
|
43
|
+
execute("RELOAD")
|
|
34
44
|
end
|
|
35
45
|
def suspend
|
|
36
|
-
|
|
46
|
+
execute("SUSPEND")
|
|
47
|
+
end
|
|
48
|
+
def resume
|
|
49
|
+
execute("RESUME")
|
|
37
50
|
end
|
|
38
|
-
def shutdown
|
|
39
|
-
|
|
51
|
+
def shutdown(mode = nil)
|
|
52
|
+
suffix = SHUTDOWN_MODES.fetch(mode)
|
|
53
|
+
execute("SHUTDOWN#{suffix}")
|
|
54
|
+
rescue KeyError
|
|
55
|
+
raise ArgumentError, "unsupported shutdown mode: #{mode.inspect}"
|
|
40
56
|
end
|
|
41
57
|
end
|
|
42
58
|
end
|
data/lib/pgbouncerhero.rb
CHANGED
|
@@ -1,27 +1,43 @@
|
|
|
1
|
-
require "
|
|
1
|
+
require "erb"
|
|
2
|
+
require "monitor"
|
|
3
|
+
require "pathname"
|
|
4
|
+
require "yaml"
|
|
5
|
+
require "rails"
|
|
6
|
+
require "pg"
|
|
7
|
+
require "importmap-rails"
|
|
8
|
+
require "turbo-rails"
|
|
9
|
+
require "stimulus-rails"
|
|
2
10
|
|
|
3
|
-
|
|
11
|
+
require "pgbouncerhero/version"
|
|
4
12
|
require "pgbouncerhero/methods/basics"
|
|
5
|
-
|
|
13
|
+
require "pgbouncerhero/connection"
|
|
6
14
|
require "pgbouncerhero/group"
|
|
7
|
-
require "pgbouncerhero/engine"
|
|
15
|
+
require "pgbouncerhero/engine"
|
|
8
16
|
|
|
9
|
-
|
|
10
|
-
|
|
17
|
+
module PgBouncerHero
|
|
18
|
+
class ConfigurationError < StandardError; end
|
|
11
19
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
BOOLEAN_SETTINGS = {
|
|
21
|
+
true => true,
|
|
22
|
+
false => false,
|
|
23
|
+
"1" => true,
|
|
24
|
+
"0" => false,
|
|
25
|
+
"true" => true,
|
|
26
|
+
"false" => false,
|
|
27
|
+
"yes" => true,
|
|
28
|
+
"no" => false,
|
|
29
|
+
"on" => true,
|
|
30
|
+
"off" => false
|
|
31
|
+
}.freeze
|
|
32
|
+
private_constant :BOOLEAN_SETTINGS
|
|
15
33
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
end
|
|
21
|
-
self.env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
|
34
|
+
STATE_MONITOR = Monitor.new
|
|
35
|
+
private_constant :STATE_MONITOR
|
|
36
|
+
|
|
37
|
+
mattr_accessor :importmap, default: Importmap::Map.new
|
|
22
38
|
|
|
23
39
|
class << self
|
|
24
|
-
|
|
40
|
+
attr_reader :env
|
|
25
41
|
|
|
26
42
|
def time_zone=(time_zone)
|
|
27
43
|
@time_zone = time_zone.is_a?(ActiveSupport::TimeZone) ? time_zone : ActiveSupport::TimeZone[time_zone.to_s]
|
|
@@ -31,38 +47,105 @@ module PgBouncerHero
|
|
|
31
47
|
@time_zone || Time.zone
|
|
32
48
|
end
|
|
33
49
|
|
|
50
|
+
def env=(env)
|
|
51
|
+
STATE_MONITOR.synchronize do
|
|
52
|
+
@env = env.to_s
|
|
53
|
+
reset!
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def config_path
|
|
58
|
+
STATE_MONITOR.synchronize { @config_path || default_config_path }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def config_path=(path)
|
|
62
|
+
STATE_MONITOR.synchronize do
|
|
63
|
+
@config_path = path && Pathname(path)
|
|
64
|
+
reset!
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
34
68
|
def config
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
config[env]
|
|
43
|
-
elsif config["pgbouncers"] # preferred format
|
|
44
|
-
config
|
|
45
|
-
else
|
|
46
|
-
{
|
|
47
|
-
"pgbouncers" => {
|
|
48
|
-
"default" => {
|
|
49
|
-
"primary" => {
|
|
50
|
-
"url" => ENV["PGBOUNCERHERO_DATABASE_URL"]
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
69
|
+
STATE_MONITOR.synchronize { @config ||= selected_config(load_config) }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def groups
|
|
73
|
+
STATE_MONITOR.synchronize do
|
|
74
|
+
@groups ||= config.fetch("pgbouncers").to_h do |group_id, _databases|
|
|
75
|
+
[ group_id.parameterize, PgBouncerHero::Group.new(group_id, config.fetch("pgbouncers")) ]
|
|
55
76
|
end
|
|
56
77
|
end
|
|
57
78
|
end
|
|
58
79
|
|
|
59
|
-
def
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
80
|
+
def read_only?
|
|
81
|
+
value = ENV.fetch("PGBOUNCERHERO_READ_ONLY") { config.fetch("read_only", false) }
|
|
82
|
+
BOOLEAN_SETTINGS.fetch(value.is_a?(String) ? value.downcase : value)
|
|
83
|
+
rescue KeyError
|
|
84
|
+
raise ConfigurationError, "read_only must be a boolean"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def disconnect!
|
|
88
|
+
STATE_MONITOR.synchronize do
|
|
89
|
+
@groups&.each_value do |group|
|
|
90
|
+
group.databases.each(&:disconnect!)
|
|
63
91
|
end
|
|
64
|
-
Hash[mapped]
|
|
65
92
|
end
|
|
66
93
|
end
|
|
94
|
+
|
|
95
|
+
def reset!
|
|
96
|
+
STATE_MONITOR.synchronize do
|
|
97
|
+
disconnect!
|
|
98
|
+
@config = nil
|
|
99
|
+
@groups = nil
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
def default_config_path
|
|
106
|
+
(Rails.root || Pathname.pwd).join("config/pgbouncerhero.yml")
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def load_config
|
|
110
|
+
return {} unless config_path.file?
|
|
111
|
+
|
|
112
|
+
loaded = YAML.safe_load(ERB.new(config_path.read).result, aliases: true)
|
|
113
|
+
return {} if loaded.nil?
|
|
114
|
+
return loaded if loaded.is_a?(Hash)
|
|
115
|
+
|
|
116
|
+
raise ConfigurationError, "#{config_path} must contain a YAML mapping"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def selected_config(loaded)
|
|
120
|
+
selected = loaded.fetch(env, loaded)
|
|
121
|
+
unless selected.is_a?(Hash)
|
|
122
|
+
raise ConfigurationError, "configuration for #{env.inspect} must be a YAML mapping"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
return default_config unless selected.key?("pgbouncers")
|
|
126
|
+
|
|
127
|
+
pgbouncers = selected["pgbouncers"]
|
|
128
|
+
unless pgbouncers.is_a?(Hash) && pgbouncers.any? && pgbouncers.values.all? { |databases| databases.is_a?(Hash) && databases.any? }
|
|
129
|
+
raise ConfigurationError, '"pgbouncers" must contain at least one group with one database'
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
selected
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def default_config
|
|
136
|
+
{
|
|
137
|
+
"pgbouncers" => {
|
|
138
|
+
"default" => {
|
|
139
|
+
"primary" => {
|
|
140
|
+
"url" => ENV["PGBOUNCERHERO_DATABASE_URL"]
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
end
|
|
67
146
|
end
|
|
147
|
+
|
|
148
|
+
self.env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
|
68
149
|
end
|
|
150
|
+
|
|
151
|
+
at_exit { PgBouncerHero.disconnect! }
|