Module: AfterMigrate

Defined in:
lib/after_migrate.rb,
lib/after_migrate/store.rb,
lib/after_migrate/railtie.rb,
lib/after_migrate/version.rb,
lib/after_migrate/executor.rb,
lib/after_migrate/collector.rb,
lib/after_migrate/adapters/sql.rb,
lib/after_migrate/adapters/mysql.rb,
lib/after_migrate/adapters/sqlite.rb,
lib/after_migrate/adapters/postgresql.rb

Defined Under Namespace

Modules: Collector, Executor, Mysql, Postgresql, Sql, Sqlite, Stores Classes: Configuration, Railtie

Constant Summary collapse

VERSION =
'0.3.0'

Class Method Summary collapse

Class Method Details

.affected_tablesObject

Persistent cross-migration store: schema_name => Concurrent::Set<table_name>



151
152
153
# File 'lib/after_migrate.rb', line 151

def affected_tables
  with_store_rescue('affected_tables', default: Concurrent::Map.new) { store.affected_tables }
end

.configurationObject



138
139
140
# File 'lib/after_migrate.rb', line 138

def configuration
  @configuration ||= Configuration.new
end

.configure {|configuration| ... } ⇒ Object

Yields:



142
143
144
# File 'lib/after_migrate.rb', line 142

def configure
  yield(configuration)
end

.log(msg) ⇒ Object



146
147
148
# File 'lib/after_migrate.rb', line 146

def log(msg)
  warn "[after_migrate] #{msg}" if AfterMigrate.configuration.verbose
end

.merge_tables(schema, table_names) ⇒ Object



155
156
157
# File 'lib/after_migrate.rb', line 155

def merge_tables(schema, table_names)
  with_store_rescue('merge_tables') { store.merge_tables(schema, table_names) }
end

.reset!Object



177
178
179
180
181
182
# File 'lib/after_migrate.rb', line 177

def reset!
  with_store_rescue('reset!') { store.reset! }
  @store_degraded = false
  @store_failure_count = 0
  nil
end

.run!(schema: nil) ⇒ Object

Trigger database maintenance on all collected tables, then clear the store. In multi-tenant setups call this once after all tenant migrations complete.



171
172
173
174
175
# File 'lib/after_migrate.rb', line 171

def run!(schema: nil)
  return unless configuration.enabled

  Executor.call(schema:)
end

.storeObject



210
211
212
213
214
# File 'lib/after_migrate.rb', line 210

def store
  @store = nil if @store_key != store_key
  @store_key = store_key
  @store ||= build_store
end

.store_degraded?Boolean

True, when at least one store operation failed and was swallowed, so the collected table list is incomplete and maintenance coverage will be partial.

Returns:

  • (Boolean)


161
162
163
# File 'lib/after_migrate.rb', line 161

def store_degraded?
  @store_degraded == true
end

.store_failure_countObject



165
166
167
# File 'lib/after_migrate.rb', line 165

def store_failure_count
  @store_failure_count.to_i
end

.with_store_rescue(operation, default: nil) ⇒ Object

A maintenance tool must never break the thing it is measuring.

Collector runs inside an sql.active_record notification subscriber, and ActiveSupport propagates subscriber exceptions to the code that emitted the event — so a store error here aborts the SQL statement, which aborts the migration.

Losing collected table names only means some tables miss their ANALYZE -- recoverable, and far cheaper than a half-applied migration. Set config.raise_on_store_error = true to opt out.

The first failure logs in full; the rest are counted only, so a long migration cannot flood the log with one line per statement.



195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/after_migrate.rb', line 195

def with_store_rescue(operation, default: nil)
  yield
rescue StandardError => e
  raise if configuration.raise_on_store_error

  @store_failure_count = store_failure_count + 1
  unless @store_degraded
    @store_degraded = true
    log("store #{operation} failed (#{e.class}: #{e.message}). Continuing without the store — " \
        'collected tables will be incomplete and some maintenance may be skipped. ' \
        'Set config.raise_on_store_error = true to make this fatal.')
  end
  default
end