From 16f1fdd3c35375859d121fae33863c1a40bc950f Mon Sep 17 00:00:00 2001 From: maebeale Date: Wed, 17 Jun 2026 11:54:19 -0400 Subject: [PATCH] Add sector descriptions and refresh the canonical sector list Sectors gain an optional description so a service area can carry a short clarification, the way categories already do. On the public registration form the description shows as subtext under each checkbox in the additional service-areas list, and is folded into a "Name (description)" label in the single primary service-area dropdown, which can't show subtext. The canonical SECTOR_TYPES list is refreshed to the current service-area tags, with the parenthetical clarifications moved into seeded descriptions. Sectors no longer on the list are unpublished rather than destroyed, preserving historical taggings; the "Other" catch-all stays published. Co-Authored-By: Claude Opus 4.8 --- app/controllers/sectors_controller.rb | 2 +- app/helpers/application_helper.rb | 2 +- app/models/sector.rb | 34 +++++++++++++------ .../public_registrations/_form_field.html.erb | 11 +++--- app/views/sectors/_form.html.erb | 9 +++++ app/views/sectors/index.html.erb | 4 +++ app/views/sectors/show.html.erb | 7 ++++ ...260616130000_add_description_to_sectors.rb | 9 +++++ db/schema.rb | 3 +- db/seeds.rb | 26 +++++++++++++- spec/helpers/application_helper_spec.rb | 10 ++++++ spec/requests/sectors_spec.rb | 6 ++-- 12 files changed, 103 insertions(+), 20 deletions(-) create mode 100644 db/migrate/20260616130000_add_description_to_sectors.rb diff --git a/app/controllers/sectors_controller.rb b/app/controllers/sectors_controller.rb index ece19f646..e7ee0454e 100644 --- a/app/controllers/sectors_controller.rb +++ b/app/controllers/sectors_controller.rb @@ -75,7 +75,7 @@ def set_sector # Strong parameters def sector_params params.require(:sector).permit( - :name, :published + :name, :published, :description ) end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 9954a19f6..254b8153a 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -175,7 +175,7 @@ def form_section_bar_class def dynamic_form_field_options(field) case field.field_identifier when *FormField::SERVICE_AREA_FIELD_IDENTIFIERS - field.service_area_sectors.map { |sector| [ sector.name, sector.id.to_s ] } + field.service_area_sectors.map { |sector| [ sector.name, sector.id.to_s, sector.description ] } when *FormField::DYNAMIC_FIELD_CATEGORY_TYPES.keys field.dynamic_categories.map { |category| [ category.name, category.id.to_s, category.description ] } end diff --git a/app/models/sector.rb b/app/models/sector.rb index c71acc70a..55107e9fa 100644 --- a/app/models/sector.rb +++ b/app/models/sector.rb @@ -1,26 +1,40 @@ class Sector < ApplicationRecord include NameFilterable, Publishable + # Canonical service-area sector tags, in display order. "Other" is kept at the end + # as the catch-all free-text fallback for additional service areas (see + # OTHER_SECTOR_NAME below) — it isn't a selectable tag itself. Descriptions for + # these (the parenthetical clarifications) live in db/seeds.rb. SECTOR_TYPES = [ - "Child Abuse", - "Community Oppression/Violence", - "Criminal/Legal", - "Disability", + "Batterers Intervention", + "Child Abuse/Neglect", + "Climate/Environmental", + "Community Violence", + "Court/Legal System", + "Disability Services", "Domestic Violence", - "Education/Schools", + "Education", "Foster Care/Adoption", - "Homeless", + "Fundraising/Donor Engagement", + "Grief/Loss", + "Health/Medical", + "Homelessness", "Human Trafficking", "Immigration", "Incarceration", "Indigenous/Tribal Nation", "LGBTQIA+", "Mental Health", - "Reproductive", + "Military/Veterans", + "Private Practice/Sole Proprietor", + "Racial/Social Justice", + "Religious/Faith Based", + "Reproductive Services", "Restorative/Transformative Justice", + "Self-Care/Personal Growth", "Sexual Assault", - "Student", - "Substance Use", - "Veterans & Military", + "Staff/Organizational Development", + "Substance Use/Recovery", + "Systems/Policy Change", "Other" ] diff --git a/app/views/events/public_registrations/_form_field.html.erb b/app/views/events/public_registrations/_form_field.html.erb index ef17eb61a..6e1cbe22a 100644 --- a/app/views/events/public_registrations/_form_field.html.erb +++ b/app/views/events/public_registrations/_form_field.html.erb @@ -116,8 +116,11 @@ class="<%= input_classes %>" <%= "required" if field.required %>> - <% dropdown_options.each do |option_label, option_value| %> - + <%# A dropdown can't show subtext, so an option's description is folded into + the label as "Name (description)". %> + <% dropdown_options.each do |option_label, option_value, option_description| %> + <% display_label = option_description.present? ? "#{option_label} (#{option_description})" : option_label %> + <% end %> @@ -125,8 +128,8 @@ <% checkbox_name = "public_registration[form_fields][#{field.id}][]" %> <% selected_values = Array(value) %> <%# Dynamic fields (e.g. primary_service_area) source options from Sector/Category - data; everything else uses the field's own stored answer options. Category - options carry an optional description, shown as subtext under the label. %> + data; everything else uses the field's own stored answer options. Sector and + Category options carry an optional description, shown as subtext under the label. %> <% checkbox_options = dynamic_form_field_options(field) || field.form_field_answer_options.includes(:answer_option).map { |ffao| [ ffao.answer_option.name, ffao.answer_option.name ] } %> <% has_specify = checkbox_options.any? { |option_label, _option_value| specify_placeholder(option_label).present? } %> diff --git a/app/views/sectors/_form.html.erb b/app/views/sectors/_form.html.erb index 0d0ea8a23..48f5d4294 100644 --- a/app/views/sectors/_form.html.erb +++ b/app/views/sectors/_form.html.erb @@ -23,6 +23,15 @@ + +
+ <%= f.input :description, + as: :text, + label: "Description", + hint: "Optional. Shown under this sector on the public registration form to clarify its meaning when the name alone isn't enough.", + input_html: { class: "form-control", rows: 2 } %> +
+
<% if allowed_to?(:destroy?, f.object) %> <%= link_to "Delete", @sector, class: "btn btn-danger-outline", diff --git a/app/views/sectors/index.html.erb b/app/views/sectors/index.html.erb index 0a6834b4c..814750699 100644 --- a/app/views/sectors/index.html.erb +++ b/app/views/sectors/index.html.erb @@ -47,6 +47,10 @@ <%= sector.name %> + <% if sector.description.present? %> + + <% end %> diff --git a/app/views/sectors/show.html.erb b/app/views/sectors/show.html.erb index 1e99aa0dc..ebcc3ab32 100644 --- a/app/views/sectors/show.html.erb +++ b/app/views/sectors/show.html.erb @@ -25,6 +25,13 @@
+ + <% if @sector.description.present? %> +
+

Description:

+

<%= @sector.description %>

+
+ <% end %> diff --git a/db/migrate/20260616130000_add_description_to_sectors.rb b/db/migrate/20260616130000_add_description_to_sectors.rb new file mode 100644 index 000000000..dedd28844 --- /dev/null +++ b/db/migrate/20260616130000_add_description_to_sectors.rb @@ -0,0 +1,9 @@ +class AddDescriptionToSectors < ActiveRecord::Migration[8.1] + def up + add_column :sectors, :description, :text unless column_exists?(:sectors, :description) + end + + def down + remove_column :sectors, :description, :text, if_exists: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 77744abe2..171ebad30 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2026_06_16_120000) do +ActiveRecord::Schema[8.1].define(version: 2026_06_16_130000) do create_table "action_text_mentions", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| t.bigint "action_text_rich_text_id", null: false t.datetime "created_at", null: false @@ -1099,6 +1099,7 @@ create_table "sectors", id: :integer, charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| t.datetime "created_at", precision: nil, null: false + t.text "description" t.string "name" t.boolean "published", default: false t.datetime "updated_at", precision: nil, null: false diff --git a/db/seeds.rb b/db/seeds.rb index 922d21e88..8181fa759 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -149,11 +149,35 @@ def find_or_create_by_name!(klass, name, **attrs, &block) end puts "Creating Sectors…" +# Optional descriptions clarify a sector on the public registration form: shown as +# subtext under the checkbox in the additional service-areas list, and folded into +# the "Name (description)" label in the single primary service-area dropdown (which +# can't show subtext). They come from the parenthetical clarifications on the +# canonical sector list. Names without an entry below have no description. Admins +# can edit each from the Sectors admin once seeded. +sector_descriptions = { + "Climate/Environmental" => "fire recovery, disaster response, environmental trauma", + "Community Violence" => "gang violence, police violence, mass shootings, etc.", + "Health/Medical" => "hospitals, first responders, illness and chronic disease", + "Immigration" => "family separation, deportation, refugees/asylees, etc.", + "Incarceration" => "including re-entry services", + "Reproductive Services" => "birth trauma, perinatal care, challenges conceiving, etc.", + "Restorative/Transformative Justice" => "individual and community reconciliation", + "Staff/Organizational Development" => "Secondary/Vicarious Trauma", + "Systems/Policy Change" => "Advocating at state/government levels for policy change" +} Sector::SECTOR_TYPES.each do |sector_type| sector = find_or_create_by_name!(Sector, sector_type) - sector.update!(published: true) unless sector.published? + sector.update!(published: true, description: sector_descriptions[sector_type]) end +# Unpublish any sector no longer on the canonical list, preserving its historical +# taggings rather than destroying them. SECTOR_TYPES already includes the "Other" +# catch-all, so it stays published. +canonical_names = Sector::SECTOR_TYPES.map(&:downcase) +Sector.reject { |sector| canonical_names.include?(sector.name.downcase) } + .each { |sector| sector.update!(published: false) } + puts "Creating CategoryTypes/Categories…" category_type_categories = [ [ "AgeRange", "3-5" ], diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index b028c9b46..e996d431b 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -423,6 +423,16 @@ expect(labels).to include("Other") end + it "carries each sector's description as the third tuple element" do + create(:sector, :published, name: "Domestic Violence", description: "DV services") + create(:sector, :published, name: "Mental Health", description: nil) + field = create(:form_field, form: form, answer_type: :multi_select_checkbox, field_identifier: "primary_service_area") + + descriptions = helper.dynamic_form_field_options(field).to_h { |name, _id, desc| [ name, desc ] } + expect(descriptions["Domestic Violence"]).to eq("DV services") + expect(descriptions["Mental Health"]).to be_nil + end + it "omits the Mixed-age groups category for the primary age group field" do type = create(:category_type, name: "AgeRange") create(:category, :published, category_type: type, name: "3-5") diff --git a/spec/requests/sectors_spec.rb b/spec/requests/sectors_spec.rb index 75e49d581..c84ee3c3a 100644 --- a/spec/requests/sectors_spec.rb +++ b/spec/requests/sectors_spec.rb @@ -122,7 +122,8 @@ context "with valid parameters" do let(:new_attributes) do valid_attributes.merge( - name: "Updated Sector Name" + name: "Updated Sector Name", + description: "Clarifying subtext for this sector" ) end @@ -130,7 +131,8 @@ sector = Sector.create! valid_attributes patch sector_url(sector), params: { sector: new_attributes } sector.reload - skip("Add assertions for updated state") + expect(sector.name).to eq("Updated Sector Name") + expect(sector.description).to eq("Clarifying subtext for this sector") end it "redirects to the sector" do