studio-engine 0.69.3 → 0.69.4
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 +4 -4
- data/CHANGELOG.md +28 -0
- data/app/controllers/studio/onboarding_controller.rb +39 -8
- data/lib/studio/name_parts.rb +50 -0
- data/lib/studio/version.rb +1 -1
- data/lib/studio.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 12d0b8a6c255a4f9e18d6a3a588abefdd7dd5d54a3801deb9b52eb99da2d78fe
|
|
4
|
+
data.tar.gz: 3467bf1af09d79805053d721c1d0d6028fd0c5b0f806c94d6a2cdc7864f048f1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3fd8ea14026738113f2348ea267b2cde306afa6a37d1b37dbf23e426013cb632be62164413e64516893f913e9f8a1618559ef326187e328001d6effceffe4187
|
|
7
|
+
data.tar.gz: 1498813d632e019fc574a6467e87fa73965f0e018001c845cac52a22d21443cfad8426e50a94ec9d37e874a6f1d059770dd97696665141853fb2a9c458b97242
|
data/CHANGELOG.md
CHANGED
|
@@ -55,6 +55,34 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
|
|
|
55
55
|
|
|
56
56
|
### Fixed
|
|
57
57
|
|
|
58
|
+
- **Onboarding no longer stores a two-word answer as the whole first name.**
|
|
59
|
+
`Studio::OnboardingController#first_name` writes with `update_columns`, which
|
|
60
|
+
skips callbacks — so the host's `before_save :set_name_parts` never ran and
|
|
61
|
+
someone answering "Ada Lovelace" landed `first_name = "Ada Lovelace"`,
|
|
62
|
+
`last_name` NULL. It never self-healed either: `set_name_parts` is gated on
|
|
63
|
+
`name_changed?`, and no later save sees a name change. The derivation now
|
|
64
|
+
comes to the writer instead — a new pure primitive, **`Studio::NameParts.from`**
|
|
65
|
+
(`lib/studio/name_parts.rb`), carrying the same rule the host callback runs,
|
|
66
|
+
including the deliberate OMISSION of `last_name` for a one-word name so a
|
|
67
|
+
surname already on file survives a first-name prompt.
|
|
68
|
+
**`update_columns` STAYS**, and the fix does not touch it: `Sluggable`'s
|
|
69
|
+
`before_save :set_slug` is UNGATED and mcritchie-studio's `User#name_slug` is
|
|
70
|
+
built from `name`, so a full save here would re-point the slug the account
|
|
71
|
+
answers on — on a uniquely-indexed column, at every signup.
|
|
72
|
+
`test/integration/onboarding_name_parts_test.rb` carries that as a live
|
|
73
|
+
control (`update!` moves `pat-pat@example.com` → `ada-lovelace-pat@…`), not as
|
|
74
|
+
a claim. `last_name` is written only where the column exists: the engine's own
|
|
75
|
+
standard-columns migration does not carry it, so mcritchie-industries,
|
|
76
|
+
moms-app and acquisition-studio would otherwise have taken a
|
|
77
|
+
`MissingAttributeError` 500 on every hyphen-free two-word answer
|
|
78
|
+
(`test/integration/onboarding_thin_host_test.rb`).
|
|
79
|
+
NO DATA REPAIR SHIPS: measured 2026-09-05 across both production databases
|
|
80
|
+
(mcritchie-studio 5 users, turf-monster 43) and both QA ones, **zero rows**
|
|
81
|
+
carry a space in `first_name`. Repairing rows whose halves merely disagree
|
|
82
|
+
with `name` would be data loss — `/profile` writes those columns directly —
|
|
83
|
+
and a fossil repair belongs to the consuming app that owns its `users` table,
|
|
84
|
+
not to the gem.
|
|
85
|
+
|
|
58
86
|
- **Knowledge layer hardening** — the five findings from the 0.67.0 reviews:
|
|
59
87
|
upload keys carry a random suffix (same-named uploads in the same second no
|
|
60
88
|
longer overwrite the first object before the unique index can refuse);
|
|
@@ -42,17 +42,35 @@ module Studio
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
rescue_and_log(target: current_user) do
|
|
45
|
-
# update_columns, not update
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
|
|
45
|
+
# update_columns, not update!, for THREE reasons — all still true:
|
|
46
|
+
#
|
|
47
|
+
# 1. This runs seconds after signup, on an account that may be
|
|
48
|
+
# mid-onboarding, and a validation failure elsewhere on the record
|
|
49
|
+
# (a grandfathered reserved username, say) must not block a name.
|
|
50
|
+
# 2. It steps around any host before_save that DERIVES first_name FROM
|
|
51
|
+
# name — set_name_parts does exactly that — which would discard the
|
|
52
|
+
# value we were just handed.
|
|
53
|
+
# 3. THE SLUG. Sluggable's `before_save :set_slug` is UNGATED, and
|
|
54
|
+
# mcritchie-studio's User#name_slug is built from `name`. A full
|
|
55
|
+
# save right after this writes `name` would therefore re-point the
|
|
56
|
+
# slug the account answers on — a URL change, on a column carrying
|
|
57
|
+
# a unique index, for every signup. That is the constraint this
|
|
58
|
+
# endpoint has always been protecting; it is not a shortcut.
|
|
59
|
+
#
|
|
60
|
+
# WHAT SKIPPING CALLBACKS USED TO COST. Because set_name_parts never
|
|
61
|
+
# ran, `first_name` got the WHOLE typed value: someone answering "Ada
|
|
62
|
+
# Lovelace" landed first_name="Ada Lovelace", last_name NULL — and it
|
|
63
|
+
# never self-healed, because set_name_parts is gated on `name_changed?`
|
|
64
|
+
# and no later save sees a name change. So the derivation comes to the
|
|
65
|
+
# writer instead: Studio::NameParts is the same rule the callback runs.
|
|
66
|
+
attrs = name_columns(value)
|
|
52
67
|
attrs[:name] = value if current_user.name.blank?
|
|
53
68
|
current_user.update_columns(attrs)
|
|
54
69
|
|
|
55
|
-
|
|
70
|
+
# The STORED first name, not the typed string — they differ for exactly
|
|
71
|
+
# the case above, and a response that disagreed with the row would be
|
|
72
|
+
# the same bug wearing a different hat.
|
|
73
|
+
render json: { ok: true, first_name: attrs[:first_name], next: remaining_steps }
|
|
56
74
|
end
|
|
57
75
|
end
|
|
58
76
|
|
|
@@ -68,6 +86,19 @@ module Studio
|
|
|
68
86
|
|
|
69
87
|
private
|
|
70
88
|
|
|
89
|
+
# The name halves to write, derived exactly as the host's set_name_parts
|
|
90
|
+
# would derive them from the same string.
|
|
91
|
+
#
|
|
92
|
+
# `last_name` is dropped for a host that has no such column —
|
|
93
|
+
# mcritchie-industries' users table is eight columns wide and update_columns
|
|
94
|
+
# on an absent column raises. Same respond_to? guard /profile already
|
|
95
|
+
# carries for the same reason.
|
|
96
|
+
def name_columns(value)
|
|
97
|
+
parts = Studio::NameParts.from(value)
|
|
98
|
+
parts.delete(:last_name) unless current_user.respond_to?(:last_name)
|
|
99
|
+
parts
|
|
100
|
+
end
|
|
101
|
+
|
|
71
102
|
# What is left AFTER this write. The host's resolver decides; the engine's
|
|
72
103
|
# default is "nothing further", which is the right answer for an app whose
|
|
73
104
|
# only onboarding ask is the name.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Studio
|
|
4
|
+
# The two halves of a person's name, derived from the one string they typed.
|
|
5
|
+
#
|
|
6
|
+
# WHY THIS IS A PRIMITIVE AND NOT THREE LINES IN A CONTROLLER. Every Studio
|
|
7
|
+
# host derives `first_name`/`last_name` from `name` in a `before_save`
|
|
8
|
+
# (`set_name_parts` — byte-identical in mcritchie-studio and turf-monster), so
|
|
9
|
+
# any writer that steps around callbacks owes the SAME derivation or the row
|
|
10
|
+
# lands split differently depending on which door it came through. The engine
|
|
11
|
+
# has exactly such a writer: Studio::OnboardingController writes with
|
|
12
|
+
# `update_columns`, and it does so for a reason worth keeping (see there).
|
|
13
|
+
# Parity is the whole requirement, so the rule lives in one named, tested
|
|
14
|
+
# place instead of being retyped at each door.
|
|
15
|
+
#
|
|
16
|
+
# PURE ON PURPOSE — no record, no columns, no ActiveRecord. That is what lets
|
|
17
|
+
# the engine's pure-Ruby unit lane exercise it directly, and what lets a host
|
|
18
|
+
# callback delegate to it later without inheriting anything.
|
|
19
|
+
#
|
|
20
|
+
# `last_name` is OMITTED, not nil, for a one-word name. That is parity, not
|
|
21
|
+
# tidiness: the host callback has always left an existing last name standing
|
|
22
|
+
# (`self.last_name = parts.last if parts.size > 1`), so a hash that nulled it
|
|
23
|
+
# would make the callback-free writer disagree with the callback in the
|
|
24
|
+
# opposite direction — someone with a last name on file would lose it by
|
|
25
|
+
# answering a first-name prompt. Whether that carry-over is itself the right
|
|
26
|
+
# rule is a separate question from this one; matching it is this file's job.
|
|
27
|
+
#
|
|
28
|
+
# Mirrors mcritchie-studio's `User.name_parts`, which the hub extracted for its
|
|
29
|
+
# own callback-free writers (seeds and the identity migrations). The engine
|
|
30
|
+
# cannot call that one — it ships to hosts that do not define it, and
|
|
31
|
+
# mcritchie-industries has no `first_name` column at all — so it carries its
|
|
32
|
+
# own copy of a rule both must agree on. test/lib/studio/name_parts_test.rb
|
|
33
|
+
# pins the parity for every shape of name.
|
|
34
|
+
module NameParts
|
|
35
|
+
module_function
|
|
36
|
+
|
|
37
|
+
# A hash ready for `update_columns` / `assign_attributes`.
|
|
38
|
+
#
|
|
39
|
+
# Studio::NameParts.from("Ada Lovelace") # => { first_name: "Ada", last_name: "Lovelace" }
|
|
40
|
+
# Studio::NameParts.from("Ada") # => { first_name: "Ada" }
|
|
41
|
+
# Studio::NameParts.from("Ada B. Lovelace") # => { first_name: "Ada", last_name: "Lovelace" }
|
|
42
|
+
# Studio::NameParts.from("") # => { first_name: nil }
|
|
43
|
+
def from(name)
|
|
44
|
+
words = name.to_s.strip.split(" ")
|
|
45
|
+
parts = { first_name: words.first }
|
|
46
|
+
parts[:last_name] = words.last if words.size > 1
|
|
47
|
+
parts
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/studio/version.rb
CHANGED
data/lib/studio.rb
CHANGED
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.69.
|
|
4
|
+
version: 0.69.4
|
|
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-09-
|
|
11
|
+
date: 2026-09-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -576,6 +576,7 @@ files:
|
|
|
576
576
|
- lib/studio/link_token.rb
|
|
577
577
|
- lib/studio/log_rotation.rb
|
|
578
578
|
- lib/studio/mail_transport.rb
|
|
579
|
+
- lib/studio/name_parts.rb
|
|
579
580
|
- lib/studio/newsletter.rb
|
|
580
581
|
- lib/studio/oauth_identity.rb
|
|
581
582
|
- lib/studio/profile_image.rb
|