studio-engine 0.139.0 → 0.140.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9937bbeb00f70902af7404d9ded16f10216e7123dba4d929eef0d04fe8c1f80
4
- data.tar.gz: ff26fdac225b730cc611af898fe0fc786ee968545a5ee674f89131cfe183f035
3
+ metadata.gz: 607b184e22c3660009dfc7e342922ca3bdae6b13a7ca7ed32dc618957dfc819a
4
+ data.tar.gz: 26a6741ca01d65b37d6f16a663f35c6d253f87254af05fd41836d23e2a5ce48b
5
5
  SHA512:
6
- metadata.gz: d3721562277466c4af36db5835ad05ad5fba414d4a091c9a447787f95d5d8bbe8ae053b5ff0a0563422a732169130aedd77e2bf5f721e6026b099e99172b7e6f
7
- data.tar.gz: 2b2405866b62361954dc69c30d66b5dbcd56536ef40b9e26d5b64094d3ed7a07bfb36cda26483ae872cc370f95a1f7f69a7a79dd4c24c4744c1d2ce1a59a8504
6
+ metadata.gz: 1f6a8450e4862aea8e8f80c214353442eafcf0c56bbcd0d78848dd291fab92ebc95ed41b836b01ba11a6601a66a652cf06153ef97f7e7c4f31910603457572d1
7
+ data.tar.gz: 24cdd966d0cad48ba9efb03fe6d54c2ec801051c7ccf23f104411fe3a4cf4eac7ea7155b453fb308ba5625be4bd30d7945c83bc2d822026ae4f6b3de194b2f76
data/CHANGELOG.md CHANGED
@@ -4,6 +4,13 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.140.0 — 2026-11-09
8
+
9
+ ### Fixed
10
+
11
+ - **`AddUsersRoleKnownCheck` no longer sets `users.role` NOT NULL, and writes its CHECK in the stable form.** The 0.138.0 migration's NOT NULL changed `t.string "role", default: "viewer"` in six consumers' schema dumps, which the release's schema-dump guard refuses, and an old dyno inserting a user without a role would fail during a rollback. It still fills NULL roles with `viewer` and adds CHECK `users_role_known`, now spelled `(role)::text = ANY (ARRAY['viewer'::character varying::text, ...])` on Postgres, so a dump after `db:migrate` equals the dump after `db:schema:load`. The migration was edited in place: 0.138.0 published it, but no app had installed it. Install it with `bin/rails studio_engine:install:migrations`; NOT NULL comes in a later migration.
12
+ - **`RequireSlugsOnEngineTables` no longer makes `slug` NOT NULL.** 0.138.0's migration set `slug` NOT NULL on `error_logs`, `theme_settings`, `studio_geo_settings` and `studio_site_identities`. Heroku runs the migration while old dynos still serve, and 0.137.0's `ErrorLog.capture!` inserts a NULL slug and sets it after, so every error write raised until the restart and again after a `heroku rollback`. The migration (edited in place; no host had installed 0.138.0) still backfills NULL and blank slugs, de-dupes, and adds the unique index, which allows many NULLs. Its `down` now changes nothing. A host adopts in one release: `bin/rails studio_engine:install:migrations && bin/rails db:migrate`, then commit `db/schema.rb`; the two-release note under 0.138.0 no longer applies. NOT NULL comes in a later engine migration, in the release after every host runs the Sluggable `ErrorLog`.
13
+
7
14
  ## 0.139.0 — 2026-11-08
8
15
 
9
16
  ### Added
@@ -1,8 +1,9 @@
1
1
  class ErrorLog < ApplicationRecord
2
- # `slug` is NOT NULL and unique (RequireSlugsOnEngineTables), so every insert
3
- # carries one, including a host's bare `ErrorLog.create!`. Sluggable writes a
4
- # unique placeholder before the insert and settles it to `error-log-<id>` right
5
- # after, inside the create, once the id exists.
2
+ # `slug` is unique (RequireSlugsOnEngineTables) and becomes NOT NULL a release
3
+ # later, so every insert carries one, including a host's bare
4
+ # `ErrorLog.create!`. Sluggable writes a unique placeholder before the insert
5
+ # and settles it to `error-log-<id>` right after, inside the create, once the
6
+ # id exists.
6
7
  include Sluggable
7
8
 
8
9
  belongs_to :target, polymorphic: true, optional: true
@@ -15,8 +15,9 @@
15
15
  #
16
16
  # On every consumer that existed when this shipped, the second branch adds
17
17
  # nothing. Studio::HostSchema checks the same columns at boot. The slug
18
- # standard (NOT NULL, unique) is the later RequireSlugsOnEngineTables
19
- # (20261108160000), which tightens the column on every host.
18
+ # standard starts with the later RequireSlugsOnEngineTables (20261108160000),
19
+ # which backfills and uniquely indexes the column on every host; NOT NULL
20
+ # follows a release later, once every host runs the Sluggable ErrorLog.
20
21
  class EnsureErrorLogsTable < ActiveRecord::Migration[7.2]
21
22
  def up
22
23
  unless table_exists?(:error_logs)
@@ -6,8 +6,8 @@
6
6
  # hand-copied migration omitted it, so a theme save raises on any host built
7
7
  # from that doc (cyvasse, mcritchie-industries and moms-app when this shipped).
8
8
  # The slug is "theme-<app_name>", derived on save. Rows this left with a NULL
9
- # slug are filled, and the column made NOT NULL and unique, by the later
10
- # RequireSlugsOnEngineTables (20261108160000).
9
+ # slug are filled, and the column made unique, by the later
10
+ # RequireSlugsOnEngineTables (20261108160000); NOT NULL comes a release after.
11
11
  #
12
12
  # A baseline, not a rebuild:
13
13
  #
@@ -1,5 +1,5 @@
1
- # The engine's own settings and log tables take the hub's slug standard:
2
- # `slug` NOT NULL with a unique index, on error_logs, theme_settings,
1
+ # The engine's own settings and log tables take the first half of the hub's slug
2
+ # standard: every slug filled and a unique index on it, on error_logs, theme_settings,
3
3
  # studio_geo_settings and studio_site_identities (database audit 2026-11-07,
4
4
  # finding 6). Every host inherits the tables, so one migration reaches them all.
5
5
  #
@@ -15,13 +15,21 @@
15
15
  # error_logs) may hold a slug twice; every copy after the lowest id takes
16
16
  # `-<id>` after it. No table here has slug children, so nothing else
17
17
  # references the old value.
18
- # 3. NOT NULL, then the unique index on `slug` unless one is there. A
19
- # non-unique slug index is replaced by the unique one.
18
+ # 3. The unique index on `slug` unless one is there. A non-unique slug index
19
+ # is replaced by the unique one.
20
+ #
21
+ # NOT NULL is deliberately NOT here. The release sweep installs engine
22
+ # migrations in the same release as the gem bump, and Heroku runs them while the
23
+ # old dynos still serve: the shipped ErrorLog.capture! inserts a NULL slug and
24
+ # sets it after, so a NOT NULL would make every error write raise until the
25
+ # restart, and again after any `heroku rollback`. The unique index allows many
26
+ # NULLs, so old code keeps working. NOT NULL lands in a later engine migration,
27
+ # in the release after every host runs the Sluggable ErrorLog this release ships.
20
28
  #
21
29
  # Production on 2026-11-08 (every app with a database, read-only counts): no
22
30
  # NULL or blank slug, no duplicate, so steps 1 and 2 change no row there. The
23
- # largest table is the hub's error_logs at 606 rows, so the NOT NULL scan and
24
- # the index build are short. On Postgres each ALTER waits at most LOCK_TIMEOUT
31
+ # largest table is the hub's error_logs at 606 rows, so the index build is
32
+ # short. On Postgres each ALTER waits at most LOCK_TIMEOUT
25
33
  # for its lock; a migrate that cannot get it fails whole, changes nothing, and
26
34
  # runs again on the next release.
27
35
  class RequireSlugsOnEngineTables < ActiveRecord::Migration[7.2]
@@ -43,21 +51,14 @@ class RequireSlugsOnEngineTables < ActiveRecord::Migration[7.2]
43
51
 
44
52
  backfill(table)
45
53
  dedupe(table)
46
- change_column_null table, :slug, false
47
54
  unique_slug_index(table)
48
55
  end
49
56
  end
50
57
 
51
- # Only the NOT NULL is undone. The unique index stays: most hosts had it before
52
- # this migration, and a down cannot tell which index it added. Backfilled slugs
58
+ # Nothing to undo. The unique index stays: most hosts had it before this
59
+ # migration, and a down cannot tell which index it added. Backfilled slugs
53
60
  # stay too; a slug is a valid value either way.
54
- def down
55
- TABLES.each do |table|
56
- next unless table_exists?(table) && column_exists?(table, :slug)
57
-
58
- change_column_null table, :slug, true
59
- end
60
- end
61
+ def down; end
61
62
 
62
63
  private
63
64
 
@@ -16,7 +16,14 @@
16
16
  # nothing written. A stray role is a product call (Turf's "user" meant
17
17
  # viewer); this migration does not guess it.
18
18
  #
19
- # Then it fills a NULL role with "viewer", sets NOT NULL, and adds the CHECK.
19
+ # Then it fills a NULL role with "viewer" and adds the CHECK.
20
+ #
21
+ # It sets NO NOT NULL. A dyno still on the old code during a deploy or a
22
+ # rollback may insert a user without a role; the column's default ("viewer")
23
+ # fills it, and a NULL passes a CHECK, so that write keeps working. NOT NULL is
24
+ # a later migration, once every host runs Studio::Account (the same reasoning as
25
+ # the engine-tables slug migration, engine-slug-not-null-later). Edited after
26
+ # 0.138.0 published it: no app had installed it yet.
20
27
  #
21
28
  # The list is SPELLED OUT, not read from Studio::Roles: a migration is a record
22
29
  # that must still run years from now, and a live constant may move. Widening the
@@ -40,15 +47,13 @@ class AddUsersRoleKnownCheck < ActiveRecord::Migration[7.2]
40
47
  end
41
48
 
42
49
  execute "UPDATE users SET role = 'viewer' WHERE role IS NULL"
43
- change_column_null :users, :role, false
44
- add_check_constraint :users, "role IN (#{allowed})", name: NAME
50
+ add_check_constraint :users, expression, name: NAME
45
51
  end
46
52
 
47
53
  # Removes the CHECK only when it is this migration's: one that names every
48
54
  # engine role. A host's own users_role_known, which `up` left in place, is
49
- # narrower (the hub's and Turf's name viewer and admin only) and stays. NOT
50
- # NULL stays too: `up` cannot record whether the host had it already, and a
51
- # NULL role is the one value no host wants back.
55
+ # narrower (the hub's and Turf's name viewer and admin only) and stays. The
56
+ # NULL roles `up` filled stay "viewer".
52
57
  def down
53
58
  return unless string_role?
54
59
 
@@ -60,6 +65,21 @@ class AddUsersRoleKnownCheck < ActiveRecord::Migration[7.2]
60
65
 
61
66
  private
62
67
 
68
+ # On Postgres, the spelling Postgres itself settles on once a schema.rb is
69
+ # loaded (`'x'::character varying::text`), so a host's schema dump after this
70
+ # migrate equals the dump after a load, byte for byte. An `IN (...)` list
71
+ # dumps as `ARRAY[...]::text[]` first and changes on the next load. Anywhere
72
+ # else (the engine's SQLite test host), the plain IN list. The same rule as
73
+ # 20261108160001_add_engine_state_checks.rb.
74
+ def expression
75
+ return "role IN (#{ROLES.map { |role| connection.quote(role) }.join(', ')})" unless postgres?
76
+
77
+ array = ROLES.map { |role| "#{connection.quote(role)}::character varying::text" }.join(", ")
78
+ "(role)::text = ANY (ARRAY[#{array}])"
79
+ end
80
+
81
+ def postgres? = connection.adapter_name.downcase.include?("postgres")
82
+
63
83
  def string_role?
64
84
  return false unless table_exists?(:users)
65
85
 
@@ -1,3 +1,3 @@
1
1
  module Studio
2
- VERSION = "0.139.0"
2
+ VERSION = "0.140.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: studio-engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.139.0
4
+ version: 0.140.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex McRitchie