studio-engine 0.8.0 → 0.9.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: f4e53e209d300c3d55aa211ae40024421ee78734929e1e1db1fd683fa84ffeb0
4
- data.tar.gz: 372749c4b18ba3072cd4977ee5e2481d18b870ba14b93e44cca9863f5781c1f6
3
+ metadata.gz: 31ed2941026e5bf0114872ba16dd7a9b94b1a472d5407ff82ec604ba2e659962
4
+ data.tar.gz: 1fda4b1328315323b8afcee644a68acc8678e756762a88b289ea10e0fc46a6cf
5
5
  SHA512:
6
- metadata.gz: b984b2a14f1065b8c021a87a28ea50f447870361a43b027bb26538d7c360eb5a89c75f3aa8241b0889578af54325421727b6967f8c2a3ec6e3daaf64c36c5295
7
- data.tar.gz: bee489407d99c8c5b6d7beda43161b01de9e06014453eb9b80a890b9c913563c2520059c36c18b67d1aaa2dbaddbf933a047878fd4d7abffb90514d8a4ef744d
6
+ metadata.gz: 6431e1dafbd0a614e1c0a6abe25004ab3888a3d1e7c263ab97d7883e10bcedde60da03f16c98b262d9926173749343b00c33d6a5580db9fc299c8b1b355a9665
7
+ data.tar.gz: 7513d2735c2535ed520530eab4adc0b1ce60de617488e4fa934e43beb31ef330e7e49c8837e3310aa92467559dbabf714fab43b81d86a0d52cebce8f85674e35
data/CHANGELOG.md CHANGED
@@ -4,6 +4,24 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.9.0 — 2026-06-23
8
+
9
+ ### Added
10
+ - **`Studio::Enumeral`** — a shared, DB-backed enumeration table for the whole
11
+ ecosystem. Every "list of fixed, labeled, colored values" (Pokémon types first;
12
+ statuses/tiers/roles later) is rows in ONE `studio_enumerals` table, grouped by
13
+ `category`: a stable machine `key`, a human `label`, a `color` hex, a display
14
+ `position`, a sparse `rank` (gappy 100/200/… domain ranking — e.g. most-common
15
+ first — distinct from display order), and `metadata` (jsonb) for
16
+ category-specific extras. Class helpers `catalog`, `lookup`, `color_map` (one
17
+ query → `{ key => color }`), `color_for(category, key, fallback:)`, and a
18
+ metadata-backed `emoji` reader + `emoji_map` (decorative glyphs live in
19
+ `metadata`, not a column); scopes `ordered` / `by_rank`; `available?` degrades
20
+ to empty when the table isn't installed. Shipped by the gem like `Studio::Link` /
21
+ `Studio::EmailDelivery` — reference migration `create_studio_enumerals`
22
+ installed per app (copy into `db/migrate`). No behavior is attached: a consumer
23
+ adopts a new category by seeding rows, no code change.
24
+
7
25
  ## 0.8.0 — 2026-06-21
8
26
 
9
27
  ### Added
@@ -0,0 +1,85 @@
1
+ module Studio
2
+ # A shared, DB-backed enumeration table for the whole ecosystem. Every "list of
3
+ # fixed, labeled, colored values" — Pokémon types (its first use), and later
4
+ # statuses, tiers, roles, … — is just rows in ONE studio_enumerals table,
5
+ # grouped by `category`. Each row is identity + presentation: a stable machine
6
+ # `key` ("fire"), a human `label` ("Fire"), a `color` hex ("#EE8130"), and a
7
+ # `position` for ordering within its category. `metadata` (jsonb) carries any
8
+ # category-specific extras off the columns.
9
+ #
10
+ # Shipped by the gem like Studio::Link / Studio::EmailDelivery: the model lives
11
+ # here, the table lives in each consumer app (copy the reference migration into
12
+ # db/migrate). No behavior is attached — it is pure reference data the apps
13
+ # read, so a consumer adopts a new category by seeding rows, no code change.
14
+ class Enumeral < ApplicationRecord
15
+ self.table_name = "studio_enumerals"
16
+
17
+ HEX = /\A#(?:\h{3}|\h{6})\z/
18
+
19
+ validates :category, presence: true
20
+ validates :key, presence: true,
21
+ uniqueness: { scope: :category, case_sensitive: false }
22
+ validates :color, format: { with: HEX, message: "must be a hex color like #EE8130" },
23
+ allow_blank: true
24
+
25
+ scope :in_category, ->(category) { where(category: category.to_s) }
26
+ scope :ordered, -> { order(:position, :key) }
27
+ # By `rank` (a sparse 100/200/… domain ranking), nulls last — for categories
28
+ # ordered by something other than display position, e.g. most-common-first.
29
+ scope :by_rank, -> { order(:rank) }
30
+
31
+ class << self
32
+ # True only when the table is actually migrated in this app's DB, so a
33
+ # consumer that hasn't installed the migration degrades to "empty" instead
34
+ # of crashing (mirrors Studio::EmailDelivery.available?).
35
+ def available?
36
+ connection.data_source_exists?(table_name)
37
+ rescue ActiveRecord::ActiveRecordError, NoMethodError
38
+ false
39
+ end
40
+
41
+ # The ordered list of a category's enumerals — the relation a view iterates
42
+ # (one query). Returns an empty relation when the table isn't installed yet,
43
+ # so callers can `.each` safely pre-migration.
44
+ def catalog(category)
45
+ return none unless available?
46
+
47
+ in_category(category).ordered
48
+ end
49
+
50
+ # The single enumeral for (category, key), or nil.
51
+ def lookup(category, key)
52
+ return nil unless available?
53
+
54
+ in_category(category).find_by(key: key.to_s)
55
+ end
56
+
57
+ # { key => color } for a category in ONE query — the shape a view wants:
58
+ # build it once, then look up each cell with no extra queries (avoids an
59
+ # N+1 when rendering many rows, e.g. the /pokemon type badges).
60
+ def color_map(category)
61
+ catalog(category).pluck(:key, :color).to_h
62
+ end
63
+
64
+ # Just the color for a single (category, key), with an optional fallback
65
+ # when the key is unknown or the table isn't installed yet.
66
+ def color_for(category, key, fallback: nil)
67
+ return fallback unless available?
68
+
69
+ in_category(category).where(key: key.to_s).limit(1).pick(:color) || fallback
70
+ end
71
+
72
+ # { key => emoji } for a category in ONE query — the metadata sibling of
73
+ # color_map, for decorative glyphs kept in `metadata` rather than a column.
74
+ def emoji_map(category)
75
+ catalog(category).pluck(:key, :metadata).to_h { |key, meta| [key, meta && meta["emoji"]] }
76
+ end
77
+ end
78
+
79
+ # A decorative glyph for the value, kept in `metadata` (not a column) since
80
+ # it's a presentation extra — e.g. a Pokémon type's emoji. Nil when unset.
81
+ def emoji
82
+ metadata && metadata["emoji"]
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,30 @@
1
+ # Reference migration for the Studio::Enumeral model. Like the engine's
2
+ # studio_links / studio_email_deliveries migrations, each consumer app installs
3
+ # its own copy of this into db/migrate so the table is created in the app's
4
+ # database (the model is shipped by the gem; the table lives per app).
5
+ class CreateStudioEnumerals < ActiveRecord::Migration[7.2]
6
+ def change
7
+ create_table :studio_enumerals do |t|
8
+ # `category` groups a fixed set of values (e.g. "pokemon_type"); `key` is
9
+ # the stable machine value within it (e.g. "fire"). label/color/position
10
+ # are presentation; metadata (jsonb) carries any category-specific extras.
11
+ t.string :category, null: false
12
+ t.string :key, null: false
13
+ t.string :label
14
+ t.string :color
15
+ t.integer :position, null: false, default: 0
16
+ # A sparse, gappy ordinal (100, 200, …) for a domain ranking distinct from
17
+ # display `position` — e.g. Pokémon types ranked most-common → least-common.
18
+ # The gaps leave room to insert a value between two ranks without
19
+ # renumbering. Nullable: not every enumeral is ranked.
20
+ t.integer :rank
21
+ t.jsonb :metadata, null: false, default: {}
22
+
23
+ t.timestamps
24
+ end
25
+
26
+ add_index :studio_enumerals, [:category, :key], unique: true
27
+ add_index :studio_enumerals, [:category, :position]
28
+ add_index :studio_enumerals, [:category, :rank]
29
+ end
30
+ end
@@ -1,3 +1,3 @@
1
1
  module Studio
2
- VERSION = "0.8.0"
2
+ VERSION = "0.9.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: studio-engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex McRitchie
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-06-22 00:00:00.000000000 Z
11
+ date: 2026-06-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -174,6 +174,7 @@ files:
174
174
  - app/models/image_cache.rb
175
175
  - app/models/session_context.rb
176
176
  - app/models/studio/email_delivery.rb
177
+ - app/models/studio/enumeral.rb
177
178
  - app/models/studio/link.rb
178
179
  - app/models/theme_setting.rb
179
180
  - app/services/google_oauth_validator.rb
@@ -235,6 +236,7 @@ files:
235
236
  - db/migrate/20260614000000_create_studio_email_deliveries.rb
236
237
  - db/migrate/20260620000001_create_studio_links.rb
237
238
  - db/migrate/20260620000002_allow_null_image_cache_owner.rb
239
+ - db/migrate/20260623130000_create_studio_enumerals.rb
238
240
  - lib/studio-engine.rb
239
241
  - lib/studio.rb
240
242
  - lib/studio/color_scale.rb