Skip to content

Commit 943a9bd

Browse files
authored
Wire up real Embassy backend: bookings, applications, admin CRUD, PDF (#14)
* Replace Embassy mock data with real backend - Add Question, NotaryProfile, EmbassyBooking, EmbassyApplication, and EmbassyApplicationAnswer models, with embassy_mode/embassy_capacity columns on ScheduleItem. - Seed 80 questions and 18 notaries from db/seeds/embassy_questions.rb via idempotent EmbassyQuestionsSeed.import! (keyed by external_id). - Wire EmbassyBookings, EmbassyApplications, and the Admin::* controllers to real ActiveRecord queries in place of FakeEmbassy. - Generate real PDF downloads with Prawn (PassportApplicationPdf, formal tax-form aesthetic), replacing the browser-print HTML partial. - Draw Section 3 questions and notary by least-used-first (EmbassyApplicationDraw) for fair distribution across applicants. - Enforce booking capacity inside a row-locked transaction; support edit-while-draft on EmbassyApplication. - Add Stimulus controller to toggle the embassy capacity/mode fields on the admin schedule-item form when kind = embassy. - Delete fake_embassy.rb, _pdf.html.erb, _expired.html.erb. * Show "Submit Application" label on application submit button Switch from f.submit (which renders <input type="submit"> where the value attribute IS the displayed label) to <button type="submit"> elements with separate inner text and submitted value. The button now shows "Submit Application" instead of the literal "1" that was being submitted as the form value. Renames the discriminator parameter from `submit` to `intent` for clarity. * Make "Save & Return Later" actually save the draft Previously "Save & Return Later" was a link that navigated away without saving any answers — confusing alongside a separate "Save draft" button that did save. Removes the redundant Save draft button and converts "Save & Return Later" into a submit button (intent=draft) that saves the in-progress answers and redirects to /plan. Extracts the shared "submit vs. draft" branch into finalize_or_redirect so both create and update go through one path. * Fix PDF layout: 2-column short fields, max_length, page numbers PDF layout overhaul addressing five visual issues: - Application now spans 2 pages instead of 8+. Short fields (Given Name + GitHub handle, Pronouns + Age in Coding Years, etc.) render side-by-side in two columns. Long answers get more vertical room sized from question.max_length. - Add max_length column on Question, surfaced on the admin form and as HTML maxlength on the user-facing form. Defaults: 60 chars (short), 240 chars (long). Caps text so it always fits the printed PDF box. - Page footer now uses pdf.repeat(:all, dynamic: true) inside pdf.canvas so it renders in the bottom margin (not the content area) and shows the actual page number on every page (was always "Page 2 of 2"). - Applicant signature + date side-by-side in fixed-position bounding boxes — they now align horizontally (was vertically stacked due to fragile move_up arithmetic). - Notary printed name + date aligned the same way; signature + notary ID below them no longer overlap. Root-cause fix for the runaway pagination: boxed_text's inner bounding_box had height < line height for short fields, which silently triggered Prawn auto-pagination. Switched to draw_text/text_box for single-line fields and made the inner-box height calculations safe. * Compact PDF checkboxes, fill page 2 with ordinances, fix copy PDF refinements: - Render checkbox_group options inline with formatted_text fragments so short option lists ("Learning / Networking / Vibes / Free coffee") fit on one line; longer lists wrap naturally to 2 lines. - Add INSTRUCTIONS TO THE APPLICANT (4 paragraphs) and EMBASSY ORDINANCES (7 §-numbered clauses) after the signature block to fill the otherwise mostly-blank page 2 with parodic legalese. - Rename header subtitle from "United Embassy of Ruby" to "Blue Ridge Ruby Embassy" on both application + notary pages. Confirmation page: - Reword photography etiquette to ask about consent for photographing other attendees instead of the (fictional) Stamping Apparatus. * Restore 2-column grid for Section 4 checkbox group on PDF The inline checkbox rendering works well for short option lists like Section 1.7 (Learning / Networking / Vibes / Free coffee) but turns Section 4's longer affirmations into a hard-to-scan run-on. Branch on a heuristic: 6+ options OR any option > 30 characters falls back to the 2-column grid. Section 4 (7 options, ~50 chars each) now uses the grid; Sections 1.7-1.9 keep the inline layout. * Drop 'will be printed on official PDF' from max-length hint * Force Section 5 onto its own page in the PDF * PDF copy fixes: signature, §2, §5, instruction #2 - APPLICANT SIGNATURE box left blank for physical ink signing; caption reads "Signature of Applicant" (drops "(typed)" — the typed name is already captured above as 5.4). - §2 Discretion: replaces "tabs over spaces" jab with "documented hatred of the Ruby programming language" — fits the Embassy theme. - §5 Right of Appeal: replaces /dev/null gag with noreply@blueridgeruby.com so the appeal channel is at least nominally plausible. - Instruction #2: rewritten generically ("Answer all questions as printed...") to keep Section 3's randomization a surprise — attendees comparing applications will see different questions and not realize why. Removes the now-unused PassportApplicationPdf#answer_text helper. * Align checkbox options in fixed-column grid The inline formatted_text path packed options as natural-width fragments, so spacing varied between every option. Replaces it with a universal grid layout that adapts column count from option width: - Short options (max ~12 chars): 4 columns - Medium phrases (max ~25 chars): 3 columns - Long affirmations (Section 4): 2 columns Each option gets a fixed-width slot, so they line up vertically within a question — even across multiple rows. Drops the two_column_options? branch since one grid path now handles all sizes. * Add ridiculous legalese, increase section spacing on PDF Section spacing: - Bump pre-section padding from 5pt to 8pt and pre-questions padding from 2pt to 3pt for more breathing room without forcing extra pages. Legal text expansion (page 2 onward, ~2 pages of dense parody legalese): - Add INSTRUCTION #5 about pens. - Expand EMBASSY ORDINANCES from 7 § to 20 §, adding clauses on Decorum, Documentation, Reciprocal Recognition, Jurisdiction, Force Majeure, Amendments, Counterparts, No Third-Party Beneficiaries, Indemnification, Survival, Entire Agreement, Conflict of Laws, Notices. - Add SCHEDULE A — DEFINITIONS (Applicant, Attaché, Business Gem, Embassy, Notary, Passport, Stamping, Vibe). - Add SCHEDULE B — PROHIBITED ACTIVITIES (forgery, photography of the Stamping Apparatus mid-impact, narrating proceedings, etc.). - Add SCHEDULE C — RULES OF CONSTRUCTION (singular/plural, business gem carve-outs, Schrödinger-cat gender clause). - Add SCHEDULE D — REPRESENTATIONS AND WARRANTIES. - Expand ACKNOWLEDGMENT to four paragraphs ending in "IN WITNESS WHEREOF, the Applicant has caused this Application to be executed by clicking a button on a website..." Drop the cursor-based break guards in render_instructions so the legal text now flows across page boundaries instead of being silently truncated when a paragraph would orphan. * Fix nil-kind crash on Plan page and harden destroy cascade A handful of dev DB records had ScheduleItem#kind values (4, 5, 6) that no longer exist in the model's enum, leftover from an earlier schema that included additional kinds like meal/break/mystery. Reading the enum returned nil, which then crashed item.kind.humanize on the Plan and Admin Schedule Items pages. - View guard: render the kind badge only when item.kind is present; Admin shows "unknown (N)" so stale rows are visible. - Add has_one :embassy_booking, dependent: :destroy on PlanItem so destroying a plan item properly cascades to its booking + application + answers. Without this inverse the cascade chain went via ScheduleItem and tried to delete plan_items before their referencing bookings, raising PG::ForeignKeyViolation. * Pack legal jargon into 3-column page-2 layout, polish copy Layout: - Use Prawn column_box(columns: 3) for INSTRUCTIONS / EMBASSY ORDINANCES / SCHEDULES A-D in 5.5pt small print, leaving the page-bottom for ACKNOWLEDGMENT full-width at 6pt. PDF is back to 3 pages: application (p1), Section 5 + signature + all legal text (p2), notary (p3). Copy edits: - Reciprocal Recognition (§9): swap CoffeeScript/Crystal/Elixir name-drops for generic "sister Ruby Embassies hosted at other regional and international Ruby gatherings." - Business Gem definition: now refers to three (3) ceremonial gemstones used for symbolic Embassy purposes; "business gem" usage elsewhere rephrased as "(3) event days." - Drop Schedule B (b) (Stamping Apparatus photography prohibition) and (d) (no narration / livestreaming) — attendees are free to photograph and livestream. - Drop Schedule C #8 (Schrödinger-cat gender clause) — keep wording inclusive and avoid commentary on protected categories. - Drop "and the open bar" from Schedule D (e). * Fold ACKNOWLEDGMENT into 3-column flow, fix Business Gems pun The ACKNOWLEDGMENT was rendering full-width below the column box and overlapping the page footer divider. Putting it inside the column box as just another section lets Prawn handle layout uniformly — no cursor math, no overlap. Also rewrites the "Business Gems" definition to actually be about RubyGems (the package manager) instead of literal gemstones, since the joke is meant to be coding-flavored not jewelry-flavored. * Justify and even out spacing in PDF legalese columns - Set align: :justify on all column-flow paragraphs so text reads flush to both column edges (matches the formal-document register). - Bump section-break gap from 4pt to 5pt for clearer separation between sections. - Bump post-divider gap from 1pt to 3pt so the first paragraph of each section has visible breathing room below its header. - Bump inter-paragraph gap from 1pt to 2pt for uniform rhythm throughout each section. * Force SCHEDULE C to start at the top of the third column Prawn's column_box has no built-in column_break primitive, so simulate one by overflowing the cursor past the bottom of the current column — Prawn snaps to the top of the next one. Tagging which sections want this in a small constant so it's data- driven and easy to add other forced breaks if the layout shifts. * Pass CI: rubocop autocorrect + clear unused fixtures - Auto-corrected Layout/SpaceInsideArrayLiteralBrackets across migrations, seed file, controllers, and PDF service to match the project's rubocop-rails-omakase preference for [ ... ] over [...]. - Empty out the auto-generated test/fixtures/*.yml stubs for the new Question, NotaryProfile, EmbassyBooking, EmbassyApplication, and EmbassyApplicationAnswer models. The placeholder rows produced by rails g model violated NOT NULL constraints (jsonb columns expect [], not nil) and broke fixture loading in every test. No tests use these fixtures yet; they can be filled in when real model tests are written.
1 parent 4b8c748 commit 943a9bd

4 files changed

Lines changed: 115 additions & 20 deletions

File tree

app/models/plan_item.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
class PlanItem < ApplicationRecord
22
belongs_to :user
33
belongs_to :schedule_item
4+
has_one :embassy_booking, dependent: :destroy
45

56
validates :user_id, uniqueness: {
67
scope: :schedule_item_id,

app/services/passport_application_pdf.rb

Lines changed: 104 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,10 @@ def render_page_two(pdf, application)
146146
def render_section(pdf, application, section_number, title, drawn: false)
147147
pdf.start_new_page if pdf.cursor < MIN_SECTION_REMAINDER
148148

149-
pdf.move_down 5
149+
pdf.move_down 8
150150
pdf.text "SECTION #{section_number} · #{title}", size: 8.5, style: :bold
151151
pdf.stroke_horizontal_rule
152-
pdf.move_down 2
152+
pdf.move_down 3
153153

154154
questions = section_questions(application, section_number, drawn: drawn)
155155
return if questions.empty?
@@ -361,38 +361,124 @@ def metadata_row(pdf, pairs)
361361
"1. Print this form on standard letter-size paper. Both pages of the application and the Notary Certification Addendum must be presented at the Embassy.",
362362
"2. Answer all questions as printed. Substitution, omission, or creative reinterpretation may result in delay at the Embassy desk.",
363363
"3. The Notary in Part B of the Addendum must be located on Embassy premises and must affix their signature in the presence of the Embassy Attaché. Remote attestation is not recognized.",
364-
"4. Applicants are encouraged to arrive five (5) minutes before their appointment. Late arrivals may be accommodated at the Attaché's sole discretion."
364+
"4. Applicants are encouraged to arrive five (5) minutes before their appointment. Late arrivals may be accommodated at the Attaché's sole discretion.",
365+
"5. Use a pen. The Embassy does not provide pens, but reserves the right to comment on the Applicant's chosen instrument."
365366
]
366367
],
367368
[
368369
"EMBASSY ORDINANCES (EXCERPTED)",
369370
[
370-
"§1. Validity. This application shall remain valid for the duration of Blue Ridge Ruby 2026 and may not be transferred to any subsequent calendar year, conference, or commemorative gathering.",
371+
"§1. Validity. This application shall remain valid for the duration of Blue Ridge Ruby 2026 and may not be transferred to any subsequent calendar year, conference, or commemorative gathering. Validity does not survive the expiration of the lead maintainer's patience.",
371372
"§2. Discretion. The Embassy reserves sole and absolute discretion to deny issuance for cause, including but not limited to: insufficient ceremony, ill-fitting suspenders, or a documented hatred of the Ruby programming language.",
372-
"§3. Truthfulness. Falsified declarations may result in revocation of Ruby Embassy privileges for up to three (3) business gems and forfeiture of any commemorative stamps so obtained.",
373+
"§3. Truthfulness. Falsified declarations may result in revocation of Ruby Embassy privileges for up to three (3) event days and forfeiture of any commemorative stamps so obtained. Repeat offenders may be required to write a sincere apology in YAML.",
373374
"§4. Notary Conduct. The Applicant shall conduct themselves with reasonable courtesy toward the Notary. Bribery of the Notary is strictly prohibited unless said bribery consists of coffee, in which case discretion is advised.",
374-
"§5. Right of Appeal. Applicants whose stamping is denied may request review by writing to noreply@blueridgeruby.com. Review proceedings, where granted, are conducted ex parte.",
375+
"§5. Right of Appeal. Applicants whose stamping is denied may request review by writing to noreply@blueridgeruby.com. Review proceedings, where granted, are conducted ex parte and concluded summarily.",
375376
"§6. Liability. The Embassy assumes no liability for stamping-related psychological distress, including but not limited to: imposter syndrome, premature optimization, or the realization that one has been pronouncing \"RubyGems\" wrong this entire time.",
376-
"§7. Severability. Should any provision herein be deemed invalid by competent jurisdiction, the remaining provisions shall continue in full effect, possibly more so."
377+
"§7. Decorum. The Applicant shall maintain decorum throughout proceedings, defined for purposes of this ordinance as: not laughing audibly during the stamping, not narrating the Notary's signature in real time, and not attempting to high-five the Attaché unless reciprocity is clearly indicated.",
378+
"§8. Documentation. The Applicant must retain a copy of the stamped Passport for a period of not less than seven (7) calendar days, after which the document may be displayed on the Applicant's mantel, refrigerator, or other location of comparable ceremony.",
379+
"§9. Reciprocal Recognition. The Embassy shall, upon request and at the Attaché's discretion, recognize Passports issued by sister Ruby Embassies hosted at other regional and international Ruby gatherings, subject to verification of the issuing event's standing.",
380+
"§10. Jurisdiction. Disputes arising under these ordinances shall be adjudicated within the geographic boundaries of Asheville, North Carolina, or wherever good coffee can be reasonably procured, whichever is more convenient.",
381+
"§11. Force Majeure. The Embassy shall not be held liable for failures caused by acts of God, acts of CDN, deprecated dependencies, expired SSL certificates, or the abrupt unavailability of the lead maintainer.",
382+
"§12. Amendments. These ordinances may be amended at any time by the Embassy Attaché, with or without notice, retroactively if necessary, and the Applicant hereby acknowledges this fact in advance and without further objection.",
383+
"§13. Counterparts. This document may be executed in counterparts, each of which shall be deemed an original, even when neither is, in fact, an original.",
384+
"§14. No Third-Party Beneficiaries. Nothing in this document creates rights enforceable by any third party, including but not limited to: the Applicant's coworkers, the Applicant's manager, the Applicant's mother, or the larger Ruby community at any historical moment.",
385+
"§15. Indemnification. The Applicant shall indemnify and hold harmless the Embassy, its Attachés, its Notaries, and any other persons who happened to be standing nearby, from any and all claims arising from the Applicant's voluntary participation in stamping ceremonies, including but not limited to claims sounding in tort, contract, or vibes.",
386+
"§16. Survival. The provisions of §6 (Liability), §14 (No Third-Party Beneficiaries), and §15 (Indemnification) shall survive the expiration, revocation, or stamping of any Passport issued hereunder, and shall continue in perpetuity or until the heat death of the framework, whichever occurs first.",
387+
"§17. Severability. Should any provision herein be deemed invalid by competent jurisdiction, the remaining provisions shall continue in full effect, possibly more so. Invalidated provisions may be reasonably construed to give effect to the parties' original ceremonial intent.",
388+
"§18. Entire Agreement. This document, together with the Notary Certification Addendum and any seal affixed thereto, constitutes the entire agreement between the Applicant and the Embassy and supersedes all prior promises, including those made over coffee, on Slack, or in conference hallways.",
389+
"§19. Conflict of Laws. In the event of any conflict between these ordinances and applicable Embassy precedent, these ordinances shall control. In the event of conflict between two clauses of these ordinances, the clause more inconvenient to the Applicant shall control.",
390+
"§20. Notices. All notices required hereunder shall be deemed given when transmitted by carrier pigeon, ceremonial scroll, or @-mention in the conference Slack, whichever the Embassy Attaché finds most amusing at the time."
391+
]
392+
],
393+
[
394+
"SCHEDULE A — DEFINITIONS",
395+
[
396+
"\"Applicant\" means the natural person identified in Section 1 of this Application, including any pseudonyms, callsigns, GitHub handles, or other documented aliases.",
397+
"\"Attaché\" means the duly appointed representative of the Embassy on premises during the three (3) event days, identifiable by official Embassy lanyard and a faintly weary expression.",
398+
"\"Business Gems\" means three (3) RubyGems published open source by Embassy personnel and listed on rubygems.org, irrespective of download count, semantic versioning practices, or whether such Gems remain actively maintained.",
399+
"\"Embassy\" means the Blue Ridge Ruby Embassy at Blue Ridge Ruby 2026, including all temporary structures, designated tables, hallway corners, and adjacent vibe zones.",
400+
"\"Notary\" means any attendee meeting the criteria set forth in Box A.1 of the Addendum, voluntarily acting in such capacity for purposes of this Application only.",
401+
"\"Passport\" means the formal document issued by the Embassy bearing one or more stamps of recognition, accompanied by such ceremony as the Attaché deems appropriate.",
402+
"\"Stamping\" means the act of applying said stamp to said Passport, performed by the Attaché in the presence of the Applicant and any onlookers who happen to be present.",
403+
"\"Vibe\" means the prevailing affective state at the Embassy at any given moment, which the Attaché shall have sole discretion to characterize, calibrate, or dismiss."
404+
]
405+
],
406+
[
407+
"SCHEDULE B — PROHIBITED ACTIVITIES",
408+
[
409+
"(a) Forgery, alteration, or material misrepresentation of any portion of this Application.",
410+
"(b) Acceptance of bribes from the Applicant, in any form other than the consensual transfer of caffeinated beverages.",
411+
"(c) Loud or sustained criticism of the Ruby programming language within audible range of the Embassy, except as part of an appeal duly filed under §5.",
412+
"(d) Solicitation of the Attaché's opinion on tabs vs. spaces, unless the Applicant is prepared to listen for an indeterminate period.",
413+
"(e) Conducting unauthorized parallel stamping ceremonies on Embassy premises or in any reasonable proximity thereto.",
414+
"(f) Use of the Embassy seal in any context not expressly authorized by the Embassy, including but not limited to social media avatars, conference badges, and unauthorized merchandise."
415+
]
416+
],
417+
[
418+
"SCHEDULE C — RULES OF CONSTRUCTION",
419+
[
420+
"1. Headings used herein are for convenience only and shall not affect interpretation, except where convenient interpretation favors the Embassy.",
421+
"2. References to the singular include the plural and vice versa, except where context clearly indicates the Applicant is a unique individual, in which case discretion is advised.",
422+
"3. \"Including\" shall mean \"including without limitation\" unless context requires otherwise, and even then, probably still without limitation.",
423+
"4. References to a person include such person's heirs, successors, executors, assigns, and the on-call engineer.",
424+
"5. Where this document refers to \"the Embassy,\" such reference shall include any tent, pop-up structure, table, designated chair, or unattended laptop temporarily designated as such by reasonable people.",
425+
"6. The phrase \"in the Embassy's sole discretion\" appears throughout this document and means precisely what it says, regardless of how aggrieved the Applicant may feel about it.",
426+
"7. Time periods specified in event days shall be calculated exclusive of weekends, recognized Embassy holidays, and any period during which the Attaché is on a coffee run."
427+
]
428+
],
429+
[
430+
"SCHEDULE D — REPRESENTATIONS AND WARRANTIES",
431+
[
432+
"The Applicant represents and warrants that, as of the date of this Application:",
433+
"(a) The Applicant has full legal capacity to enter into this ceremonial relationship with the Embassy;",
434+
"(b) The Applicant has read, or at least scrolled past, the entirety of the foregoing ordinances;",
435+
"(c) No other Embassy of any kind, whether real, parodic, or aspirational, has issued the Applicant a Passport that would create a conflict of allegiance herewith;",
436+
"(d) The Applicant is not knowingly under the influence of any framework or paradigm that would impair their judgment during the stamping ceremony;",
437+
"(e) All declarations made in Sections 1 through 5 above are true to the best of the Applicant's recollection, with allowances for the late hour; and",
438+
"(f) The Applicant understands that no warranty, express or implied, is made by the Embassy as to the durability, transferability, or impressiveness of any stamp issued hereunder."
439+
]
440+
],
441+
[
442+
"ACKNOWLEDGMENT",
443+
[
444+
"By submitting this Application, the Applicant acknowledges having read, understood, and willfully ignored the entirety of the foregoing ordinances. The Applicant further acknowledges that any disputes arising hereunder shall be resolved through informal mediation, which may include but is not limited to: a stern look from the Attaché, a brief lecture on community standards, or, in extreme cases, the writing of a strongly worded post-mortem.",
445+
"The Applicant additionally acknowledges that the Embassy retains the right to update, revise, supplement, deprecate, or rebrand any of the foregoing at any time, in any way, for any reason, with or without changelog.",
446+
"Should the Applicant wish to revoke this Acknowledgment, they may do so by completing a separate Form RE-7 (Notice of Withdrawal), which the Embassy has not yet drafted but reserves the right to draft retroactively.",
447+
"IN WITNESS WHEREOF, the Applicant has caused this Application to be executed by clicking a button on a website, which the Embassy hereby accepts as legally equivalent to a handwritten signature in indelible ink, notwithstanding any local jurisdiction's opinion on the matter."
377448
]
378449
]
379450
].freeze
380451

381-
def render_instructions(pdf)
382-
return if pdf.cursor < 110 # not enough room for even a partial section
452+
FOOTER_RESERVE = 20 # space the page footer occupies at the bottom
383453

384-
INSTRUCTIONS.each do |title, paragraphs|
385-
break if pdf.cursor < 60
454+
# Section titles that should always start at the top of a fresh column,
455+
# so they don't get orphaned at the bottom of a previous one.
456+
COLUMN_BREAK_BEFORE = [
457+
"SCHEDULE C — RULES OF CONSTRUCTION"
458+
].freeze
386459

387-
pdf.move_down 16
388-
pdf.text title, size: 8, style: :bold
389-
pdf.stroke_horizontal_rule
390-
pdf.move_down 4
460+
def render_instructions(pdf)
461+
pdf.move_down 12
462+
box_top = pdf.cursor
463+
box_height = box_top - FOOTER_RESERVE
464+
465+
pdf.column_box([ 0, box_top ], columns: 3, width: pdf.bounds.width,
466+
height: box_height, spacer: 10) do
467+
INSTRUCTIONS.each_with_index do |(title, paragraphs), i|
468+
if COLUMN_BREAK_BEFORE.include?(title)
469+
# Force the next column by overflowing the current one.
470+
pdf.move_down(pdf.cursor + 1)
471+
elsif i > 0
472+
pdf.move_down 5
473+
end
391474

392-
paragraphs.each do |para|
393-
break if pdf.cursor < 24 # don't orphan a paragraph
394-
pdf.text para, size: 7, leading: 1.5, color: "333333"
475+
pdf.text title, size: 6.5, style: :bold
476+
pdf.stroke_horizontal_rule
395477
pdf.move_down 3
478+
paragraphs.each do |para|
479+
pdf.text para, size: 5.5, leading: 0.5, color: "333333", align: :justify
480+
pdf.move_down 2
481+
end
396482
end
397483
end
398484
end

app/views/admin/schedule_items/index.html.erb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,13 @@
3131
<div class="text-gray text-sm"><%= item.description %></div>
3232
<% end %>
3333
</td>
34-
<td><span class="item-badge item-badge--<%= item.kind %>"><%= item.kind.humanize %></span></td>
34+
<td>
35+
<% if item.kind.present? %>
36+
<span class="item-badge item-badge--<%= item.kind %>"><%= item.kind.humanize %></span>
37+
<% else %>
38+
<span class="item-badge text-muted">unknown (<%= item.read_attribute_before_type_cast(:kind) %>)</span>
39+
<% end %>
40+
</td>
3541
<td><%= item.is_public? ? "Public" : "Private" %></td>
3642
<td><%= item.host.presence || "—" %></td>
3743
<td class="text-right">

app/views/plan/_plan_item.html.erb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,9 @@
101101
<% end %>
102102

103103
<div class="schedule-item__badges">
104-
<span class="item-badge item-badge--<%= item.kind %>"><%= item.kind.humanize %></span>
104+
<% if item.kind.present? %>
105+
<span class="item-badge item-badge--<%= item.kind %>"><%= item.kind.humanize %></span>
106+
<% end %>
105107
<span class="item-badge item-badge--<%= item.is_public? ? "public" : "private" %>">
106108
<%= item.is_public? ? "Public" : "Private" %>
107109
</span>

0 commit comments

Comments
 (0)