Class: Potluck::Postgres

Inherits:
Service
  • Object
show all
Defined in:
lib/potluck/postgres.rb

Overview

A Ruby interface for controlling and connecting to Postgres. Uses [Sequel](github.com/jeremyevans/sequel) to connect and perform automatic role and database creation, as well as for utility methods such as database schema migration.

Constant Summary collapse

ROLE_NOT_FOUND_REGEX =
/role .* does not exist/.freeze
DATABASE_NOT_FOUND_REGEX =
/database .* does not exist/.freeze
STARTING_UP_STRING =
'the database system is starting up'
STARTING_UP_TIMEOUT =
30
CONNECTION_REFUSED_STRING =
'connection refused'
CONNECTION_REFUSED_TIMEOUT =
3

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config, **args) ⇒ Postgres

Creates a new instance.

  • config - Configuration hash to pass to Sequel.connect.

  • args - Arguments to pass to Potluck::Service.new (optional).



49
50
51
52
53
# File 'lib/potluck/postgres.rb', line 49

def initialize(config, **args)
  super(**args)

  @config = config
end

Instance Attribute Details

#databaseObject (readonly)

Returns the value of attribute database.



41
42
43
# File 'lib/potluck/postgres.rb', line 41

def database
  @database
end

Class Method Details

.plistObject

Content of the launchctl plist file.



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/potluck/postgres.rb', line 191

def self.plist
  super(
    <<~EOS
      <key>ProgramArguments</key>
      <array>
        <string>/usr/local/opt/postgresql/bin/postgres</string>
        <string>-D</string>
        <string>/usr/local/var/postgres</string>
      </array>
      <key>WorkingDirectory</key>
      <string>/usr/local</string>
      <key>StandardOutPath</key>
      <string>/usr/local/var/log/postgres.log</string>
      <key>StandardErrorPath</key>
      <string>/usr/local/var/log/postgres.log</string>
    EOS
  )
end

Instance Method Details

#connectObject

Connects to the configured Postgres database.



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/potluck/postgres.rb', line 66

def connect
  (tries ||= 0) && (tries += 1)
  @database = Sequel.connect(@config, logger: @logger)
rescue Sequel::DatabaseConnectionError => e
  if (dud = Sequel::DATABASES.last)
    dud.disconnect
    Sequel.synchronize { Sequel::DATABASES.delete(dud) }
  end

  message = e.message.downcase

  if message =~ ROLE_NOT_FOUND_REGEX && tries == 1
    create_database_role
    create_database
    retry
  elsif message =~ DATABASE_NOT_FOUND_REGEX && tries == 1
    create_database
    retry
  elsif message.include?(STARTING_UP_STRING) && tries < STARTING_UP_TIMEOUT
    sleep(1)
    retry
  elsif message.include?(CONNECTION_REFUSED_STRING) && tries < CONNECTION_REFUSED_TIMEOUT && manage?
    sleep(1)
    retry
  elsif message.include?(CONNECTION_REFUSED_STRING)
    raise(PostgresError.new(e.message.strip, e))
  else
    raise
  end
end

#disconnectObject

Disconnects from the database if a connection was made.



100
101
102
# File 'lib/potluck/postgres.rb', line 100

def disconnect
  @database&.disconnect
end

#migrate(dir, steps = nil) ⇒ Object

Runs database migrations by way of Sequel’s migration extension. Migration files must use the timestamp naming strategy as opposed to integers.

  • dir - Directory where migration files are located.

  • steps - Number of steps forward or backward to migrate from the current migration, otherwise will migrate to latest (optional).



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/potluck/postgres.rb', line 112

def migrate(dir, steps = nil)
  return unless File.directory?(dir)

  Sequel.extension(:migration)

  # Suppress Sequel schema migration table queries.
  original_level = @logger.level
  @logger.level = Logger::WARN if @logger.level == Logger::INFO

  args = [Sequel::Model.db, dir, {allow_missing_migration_files: true}]
  migrator = Sequel::TimestampMigrator.new(*args)

  return if migrator.files.empty?

  if steps
    all = migrator.files.map { |f| File.basename(f) }
    applied = migrator.applied_migrations
    current = applied.last

    return if applied.empty? && steps <= 0

    index = [[0, (all.index(current) || -1) + steps].max, all.size].min
    file = all[index]

    args.last[:target] = migrator.send(:migration_version_from_file, file)
  end

  migrator = Sequel::TimestampMigrator.new(*args)
  @logger.level = original_level
  migrator.run
ensure
  @logger.level = original_level if original_level
end

#stopObject

Disconnects and stops the Postgres process.



58
59
60
61
# File 'lib/potluck/postgres.rb', line 58

def stop
  disconnect
  super
end