ros-apartment 4.0.0.alpha6 → 4.0.0.alpha7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7ce0a7124b9df22be74f2899b8a38046f52fcd7e1e0b068b3ebd778e16f57969
4
- data.tar.gz: 0f40a39d073ae13bd59dce2e72e3e2aaa3759f2ffc270491f6dbbfcc7ef623fe
3
+ metadata.gz: '093f09466c1e39d4344764096cec982394980fb08c8b72712c96bd8ad30fdcc7'
4
+ data.tar.gz: 402f7ef009203bb95296a90d7c6ff3dd34eeebca2d70c7da86cdb398ecc6fa60
5
5
  SHA512:
6
- metadata.gz: ebbc9775707e60c7e7bd896e6f1efa02487017519770dda6558cd89552e53558f47a39df31b73f4c90f6c7039e86399536ce586ce1bd30ac263ad67071c7407a
7
- data.tar.gz: 8339cc7eb7b07adcb5df1f923c55f7e3aa96bad5faa47a13975550563af0d77191736b4afe0a8aa97f6a446adc59cc148a21739ff0c36224d3608371e6a0efe0
6
+ metadata.gz: 99343f0efe17d444cc23b244581c56135a293371c97ee3fc5d2d2931e51c688c50d9d3d93d8ae55489ec36d3a6a43a910cacdc85d22c43a6c7e80a1bad189faa
7
+ data.tar.gz: 2ccd4c456cf1a5ce190d4b50dedfc131231cd1f02518c0444313987ef0a5fda8acc00c1376bfc5daac791768f1b13685d0bb47a08ae5ba755943a2cd6eb5d04b
data/README.md CHANGED
@@ -40,6 +40,25 @@ This gem is a maintained fork of the original [Apartment gem](https://github.com
40
40
  - Rails 7.2+
41
41
  - PostgreSQL 14+, MySQL 8.4+, or SQLite3
42
42
 
43
+ ### Ruby version manager
44
+
45
+ `.ruby-version` is the single source of truth for the local Ruby version (the
46
+ same file `ruby/setup-ruby` reads in CI). Both [mise](https://mise.jdx.dev) and
47
+ [rbenv](https://github.com/rbenv/rbenv) honor it — use whichever you prefer:
48
+
49
+ ```bash
50
+ # mise (recommended)
51
+ brew install mise # then add `eval "$(mise activate zsh)"` to ~/.zshrc
52
+ mise trust && mise install # one-time trust per clone, then install the pinned Ruby
53
+
54
+ # rbenv (also works, unchanged)
55
+ rbenv install "$(cat .ruby-version)"
56
+ ```
57
+
58
+ The committed `mise.toml` carries settings only (never a version): it tells mise
59
+ to honor `.ruby-version` without any per-developer global config. `mise trust`
60
+ is required once per clone; `bin/dev/setup-worktree` handles it for new worktrees.
61
+
43
62
  ### Setup
44
63
 
45
64
  ```ruby
@@ -5,6 +5,13 @@ require 'thor'
5
5
  module Apartment
6
6
  class CLI < Thor
7
7
  class Migrations < Thor
8
+ # The raised Thor::Error message is the low-context surface a monitor or
9
+ # CI-log tail captures even when the stdout summary is dropped/unindexed.
10
+ # Name a few failed tenants there (capped, so a 600-tenant run does not
11
+ # produce a wall of text); the full per-tenant detail is in the summary
12
+ # and in the migrate_tenant_failed.apartment events.
13
+ FAILED_TENANTS_PREVIEW = 5
14
+
8
15
  def self.exit_on_failure? = true
9
16
 
10
17
  desc 'migrate [TENANT]', 'Run migrations for tenants'
@@ -62,7 +69,15 @@ module Apartment
62
69
  say(result.summary)
63
70
 
64
71
  trigger_schema_dump if result.success?
65
- raise(Thor::Error, "Migration failed for #{result.failed.size} tenant(s)") unless result.success?
72
+ raise(Thor::Error, migration_failure_message(result.failed)) unless result.success?
73
+ end
74
+
75
+ def migration_failure_message(failures)
76
+ names = failures.map(&:tenant)
77
+ shown = names.first(FAILED_TENANTS_PREVIEW)
78
+ more = names.size - shown.size
79
+ suffix = more.positive? ? ", and #{more} more" : ''
80
+ "Migration failed for #{names.size} tenant(s): #{shown.join(', ')}#{suffix} (see summary above)"
66
81
  end
67
82
 
68
83
  # Rollback bypasses the Migrator's parallelism and Result tracking but
@@ -3,8 +3,8 @@
3
3
  require 'active_support/notifications'
4
4
 
5
5
  module Apartment
6
- # Thin wrapper around ActiveSupport::Notifications.
7
- # Known events: create, drop, evict (all namespaced as *.apartment).
6
+ # Thin wrapper around ActiveSupport::Notifications. All events are namespaced
7
+ # *.apartment; see docs/observability.md for the authoritative event catalog.
8
8
  module Instrumentation
9
9
  def self.instrument(event, payload = {}, &block)
10
10
  event_name = "#{event}.apartment"
@@ -121,9 +121,15 @@ module Apartment
121
121
  duration: monotonic_now - start, error: nil, versions_run: versions
122
122
  )
123
123
  rescue StandardError => e
124
+ duration = monotonic_now - start
125
+ # Symmetric with the :migrate_tenant success event above: the gem holds the
126
+ # full structured error here, so emit it for subscribers rather than
127
+ # stranding it in the returned Result. See migrate_tenant's rescue.
128
+ instrument_failure(tenant_name, e, duration)
129
+
124
130
  Result.new(
125
131
  tenant: tenant_name, status: :failed,
126
- duration: monotonic_now - start, error: e, versions_run: []
132
+ duration: duration, error: e, versions_run: []
127
133
  )
128
134
  end
129
135
 
@@ -166,9 +172,17 @@ module Apartment
166
172
  end
167
173
  end
168
174
  rescue StandardError => e
175
+ duration = monotonic_now - start
176
+ # Failure counterpart to the :migrate_tenant success event. On SUCCESS the
177
+ # gem instruments; on FAILURE it previously only returned a failed Result,
178
+ # leaving adopters no hook to observe the (structured) error. This closes
179
+ # that asymmetry — generic payload (tenant + error + duration); routing to
180
+ # an error tracker is the subscriber's job.
181
+ instrument_failure(tenant, e, duration)
182
+
169
183
  Result.new(
170
184
  tenant: tenant, status: :failed,
171
- duration: monotonic_now - start, error: e, versions_run: []
185
+ duration: duration, error: e, versions_run: []
172
186
  )
173
187
  ensure
174
188
  Apartment::Current.migrating = false
@@ -242,6 +256,34 @@ module Apartment
242
256
  conn.instance_variable_set(ADVISORY_LOCKS_IVAR, original) if conn&.instance_variable_defined?(ADVISORY_LOCKS_IVAR)
243
257
  end
244
258
 
259
+ # Emit the failure event without letting a raising subscriber break the
260
+ # Migrator's non-raising contract: the failed Result MUST still be returned.
261
+ # ActiveSupport::Notifications propagates subscriber exceptions through
262
+ # instrument (verified against AS 8.0), and this fires from inside a rescue
263
+ # block, so an un-isolated call would convert a captured per-tenant failure
264
+ # into an unhandled raise out of #run.
265
+ #
266
+ # Scope of the guarantee: a subscriber raising a StandardError is swallowed
267
+ # and warned. Process-control exceptions (SystemExit, SignalException /
268
+ # Interrupt) are deliberately NOT rescued — they must propagate so exit and
269
+ # Ctrl-C still work mid-migration; a subscriber raising a bare Exception
270
+ # subclass is itself a bug (Ruby errors should descend from StandardError).
271
+ # The warn is nested-rescued so a broken $stderr (IOError is a StandardError)
272
+ # cannot re-escape the handler. Success-path instrumentation is left
273
+ # un-isolated by design — only the failure path carries the hard no-raise
274
+ # guarantee, and swallowing there would mask real subscriber bugs.
275
+ def instrument_failure(tenant, error, duration)
276
+ Instrumentation.instrument(:migrate_tenant_failed, tenant: tenant, error: error, duration: duration)
277
+ rescue StandardError => e
278
+ # Nested rescue: a sibling `rescue` on this method would NOT catch an
279
+ # exception raised by warn (a broken $stderr), so the warn gets its own.
280
+ begin
281
+ warn "[Apartment::Migrator] migrate_tenant_failed subscriber raised #{e.class}: #{e.message}"
282
+ rescue StandardError
283
+ nil
284
+ end
285
+ end
286
+
245
287
  def monotonic_now
246
288
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
247
289
  end
@@ -34,6 +34,13 @@ namespace :apartment do
34
34
  else
35
35
  Apartment::CLI::Migrations.new.invoke(:migrate)
36
36
  end
37
+ # Parity with apartment:drop: `.new.invoke` bypasses Thor.start's
38
+ # exit_on_failure? handling, so an unrescued Thor::Error (raised on
39
+ # migration failure) would surface as a raw `rake aborted!` backtrace that
40
+ # buries the actionable message. abort re-emits it as a clean one-liner
41
+ # with a non-zero exit.
42
+ rescue Thor::Error => e
43
+ abort(e.message)
37
44
  end
38
45
 
39
46
  desc 'Seed all tenants'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Apartment
4
- VERSION = '4.0.0.alpha6'
4
+ VERSION = '4.0.0.alpha7'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ros-apartment
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0.alpha6
4
+ version: 4.0.0.alpha7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Brunner