diff --git a/app/views/workshop_ideas/index.html.erb b/app/views/workshop_ideas/index.html.erb
index 84196fb89d..27edd6b3ed 100644
--- a/app/views/workshop_ideas/index.html.erb
+++ b/app/views/workshop_ideas/index.html.erb
@@ -25,6 +25,7 @@
Title |
Timeframe |
Author |
+
Organization |
Promoted? |
Actions |
@@ -46,6 +47,7 @@
<%= workshop_idea.title %> |
<%= workshop_idea.time_frame_total %> |
<%= credited_author_link(workshop_idea, class: eyebrow_link_class) %> |
+
<%= workshop_idea.organization&.name %> |
<% if workshop_idea.workshops.any? %>
diff --git a/app/views/workshop_ideas/show.html.erb b/app/views/workshop_ideas/show.html.erb
index 779556a09f..02a4a28f8a 100644
--- a/app/views/workshop_ideas/show.html.erb
+++ b/app/views/workshop_ideas/show.html.erb
@@ -41,6 +41,17 @@
Timeframe:
<%= @workshop_idea.timeframe.presence || "—" %>
+
+ Organization:
+ <% if @workshop_idea.organization && allowed_to?(:show?, @workshop_idea.organization) %>
+ <%= link_to @workshop_idea.organization.name,
+ organization_path(@workshop_idea.organization),
+ data: { turbo_frame: "_top" },
+ class: "inline-block px-3 py-1 rounded-md text-sm font-medium #{DomainTheme.bg_class_for(:organizations, intensity: 100)} #{DomainTheme.text_class_for(:organizations)} #{DomainTheme.bg_class_for(:organizations, intensity: 100, hover: true)}" %>
+ <% else %>
+ <%= @workshop_idea.organization&.name || "—" %>
+ <% end %>
+
Created by:
<%= person_name(@workshop_idea.created_by) %>
diff --git a/app/views/workshops/_form.html.erb b/app/views/workshops/_form.html.erb
index 83bec3b2e9..5137232a01 100644
--- a/app/views/workshops/_form.html.erb
+++ b/app/views/workshops/_form.html.erb
@@ -85,6 +85,26 @@
hint: "If this workshop was created from a Workshop Idea, select it here.",
input_html: { value: f.object.workshop_idea_id,
class: "block w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm" } %>
+
+ <% organizations = @organizations || [] %>
+ <% if organizations.size == 1 %>
+ <%= f.hidden_field :organization_id,
+ value: f.object.organization_id || organizations.first.id %>
+ <% else %>
+ <%= f.input :organization_id,
+ as: :select,
+ collection: organizations,
+ label_method: :type_name,
+ value_method: :id,
+ label: "Organization",
+ required: false,
+ include_blank: "Select organization",
+ selected: (params[:organization_id].presence || f.object.organization_id),
+ hint: "Please contact AWBW if your organization is not listed.",
+ input_html: { class: "block w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm" },
+ label_html: { class: "block text-sm font-medium text-gray-700 mb-1" } %>
+ <% end %>
+
diff --git a/app/views/workshops/_index_row.html.erb b/app/views/workshops/_index_row.html.erb
index 72d9921e13..15bca80924 100644
--- a/app/views/workshops/_index_row.html.erb
+++ b/app/views/workshops/_index_row.html.erb
@@ -53,6 +53,9 @@
By:
<%= credited_author_link(workshop, class: "hover:underline", data: { turbo: false }) %>
+ <% if workshop.organization.present? %>
+ | <%= workshop.organization.name %>
+ <% end %>
| <%= workshop.date %>
diff --git a/app/views/workshops/_show_header.html.erb b/app/views/workshops/_show_header.html.erb
index 2156fd95ec..b38465937c 100644
--- a/app/views/workshops/_show_header.html.erb
+++ b/app/views/workshops/_show_header.html.erb
@@ -20,7 +20,14 @@
(Unpublished)
<% end %>
- Author: <%= credited_author_link(workshop, class: "hover:underline") %>Category: <%= workshop.windows_type&.short_name %>Added: <%= workshop.date %>
+
+ Author: <%= credited_author_link(workshop, class: "hover:underline") %>
+ <% if workshop.organization.present? %>
+ Organization: <%= link_to_if allowed_to?(:show?, workshop.organization), workshop.organization.name, organization_path(workshop.organization), class: "hover:underline" %>
+ <% end %>
+ Category: <%= workshop.windows_type&.short_name %>
+ Added: <%= workshop.date %>
+
<%= render "assets/primary_image_picker", owner: workshop %>
diff --git a/db/migrate/20261018123639_add_organization_to_workshop_ideas.rb b/db/migrate/20261018123639_add_organization_to_workshop_ideas.rb
new file mode 100644
index 0000000000..96e5e09484
--- /dev/null
+++ b/db/migrate/20261018123639_add_organization_to_workshop_ideas.rb
@@ -0,0 +1,5 @@
+class AddOrganizationToWorkshopIdeas < ActiveRecord::Migration[8.1]
+ def change
+ add_reference :workshop_ideas, :organization, type: :integer, null: true, foreign_key: true
+ end
+end
diff --git a/db/migrate/20261018123640_add_organization_to_workshops.rb b/db/migrate/20261018123640_add_organization_to_workshops.rb
new file mode 100644
index 0000000000..d71a1b17b0
--- /dev/null
+++ b/db/migrate/20261018123640_add_organization_to_workshops.rb
@@ -0,0 +1,5 @@
+class AddOrganizationToWorkshops < ActiveRecord::Migration[8.1]
+ def change
+ add_reference :workshops, :organization, type: :integer, null: true, foreign_key: true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 2c3d578b62..e55ad05c98 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -2486,8 +2486,10 @@
t.text "warm_up"
t.text "warm_up_spanish"
t.integer "windows_type_id", null: false
+ t.integer "organization_id"
t.index ["author_id"], name: "index_workshop_ideas_on_author_id"
t.index ["created_by_id"], name: "index_workshop_ideas_on_created_by_id"
+ t.index ["organization_id"], name: "index_workshop_ideas_on_organization_id"
t.index ["updated_by_id"], name: "index_workshop_ideas_on_updated_by_id"
t.index ["windows_type_id"], name: "index_workshop_ideas_on_windows_type_id"
end
@@ -2678,11 +2680,13 @@
t.integer "windows_type_id"
t.bigint "workshop_idea_id"
t.integer "year"
+ t.integer "organization_id"
t.index ["author_id"], name: "index_workshops_on_author_id"
t.index ["created_at"], name: "index_workshops_on_created_at"
t.index ["created_by_id"], name: "index_workshops_on_created_by_id"
t.index ["inactive", "led_count", "title"], name: "index_workshops_on_inactive_and_led_count_and_title"
t.index ["led_count"], name: "index_workshops_on_led_count"
+ t.index ["organization_id"], name: "index_workshops_on_organization_id"
t.index ["published"], name: "index_workshops_on_published"
t.index ["title", "full_name", "objective", "materials", "introduction", "demonstration", "opening_circle", "warm_up", "creation", "closing", "notes", "tips", "misc1", "misc2"], name: "workshop_fullsearch", type: :fulltext
t.index ["title"], name: "index_workshops_on_title", type: :fulltext
@@ -2954,6 +2958,7 @@
add_foreign_key "windows_types", "users", column: "updated_by_id"
add_foreign_key "workshop_age_ranges", "age_ranges"
add_foreign_key "workshop_age_ranges", "workshops"
+ add_foreign_key "workshop_ideas", "organizations"
add_foreign_key "workshop_ideas", "people", column: "author_id"
add_foreign_key "workshop_ideas", "users", column: "created_by_id"
add_foreign_key "workshop_ideas", "users", column: "updated_by_id"
@@ -2981,6 +2986,7 @@
add_foreign_key "workshop_variations", "windows_types"
add_foreign_key "workshop_variations", "workshop_variation_ideas"
add_foreign_key "workshop_variations", "workshops"
+ add_foreign_key "workshops", "organizations"
add_foreign_key "workshops", "people", column: "author_id"
add_foreign_key "workshops", "users", column: "created_by_id"
add_foreign_key "workshops", "users", column: "updated_by_id"
diff --git a/db/seeds/features/2026-10-22-organization-workshops-and-ideas.yml b/db/seeds/features/2026-10-22-organization-workshops-and-ideas.yml
new file mode 100644
index 0000000000..e092810db2
--- /dev/null
+++ b/db/seeds/features/2026-10-22-organization-workshops-and-ideas.yml
@@ -0,0 +1,8 @@
+name: "Workshops and ideas on organization profiles"
+area: content
+display_status: user_facing
+released_on: 2026-10-22
+action_path: "/organizations"
+summary: >-
+ Workshops and workshop ideas now link to organizations directly and display on
+ organization profiles, including content authored by affiliated facilitators.
diff --git a/spec/factories/workshop_ideas.rb b/spec/factories/workshop_ideas.rb
index d612b33b58..a672e0c102 100644
--- a/spec/factories/workshop_ideas.rb
+++ b/spec/factories/workshop_ideas.rb
@@ -1,6 +1,6 @@
FactoryBot.define do
factory :workshop_idea do
- title { "MyString" }
+ sequence(:title) { |n| "Workshop Idea #{n}" }
description { "MyText" }
staff_notes { "MyText" }
association :windows_type
diff --git a/spec/models/organization_spec.rb b/spec/models/organization_spec.rb
index 374dfd477e..77537d7664 100644
--- a/spec/models/organization_spec.rb
+++ b/spec/models/organization_spec.rb
@@ -13,6 +13,10 @@
it { should have_many(:users).through(:people) }
it { should have_many(:reports) }
it { should have_many(:workshop_logs) }
+ it { should have_many(:workshop_ideas) }
+ it { should have_many(:workshops) }
+ it { should have_many(:workshops_as_author).through(:people) }
+ it { should have_many(:workshop_ideas_as_author).through(:people) }
end
describe 'validations' do
@@ -522,6 +526,53 @@ def facilitator(primary:, additional: [])
expect(organization.active_facilitator_age_groups).to eq([ teen, young, adult ])
end
end
+
+ describe "#affiliated_workshops" do
+ let(:organization) { create(:organization) }
+ let(:affiliated_person) { create(:person) }
+ let(:windows_type) { create(:windows_type, :adult) }
+
+ before do
+ create(:affiliation, organization: organization, person: affiliated_person)
+ end
+
+ it "includes directly associated workshops and workshops authored by affiliated people without duplicates" do
+ direct_workshop = create(:workshop, title: "Direct Workshop", organization: organization, windows_type: windows_type)
+ authored_workshop = create(:workshop, title: "Authored Workshop", author: affiliated_person, windows_type: windows_type)
+ both_workshop = create(:workshop, title: "Both Workshop", organization: organization, author: affiliated_person, windows_type: windows_type)
+ unrelated_workshop = create(:workshop, title: "Unrelated Workshop", windows_type: windows_type)
+
+ results = organization.affiliated_workshops
+
+ expect(results).to include(direct_workshop, authored_workshop, both_workshop)
+ expect(results).not_to include(unrelated_workshop)
+ expect(results.where(id: both_workshop.id).count).to eq(1)
+ end
+ end
+
+ describe "#affiliated_workshop_ideas" do
+ let(:organization) { create(:organization) }
+ let(:affiliated_person) { create(:person) }
+ let(:windows_type) { create(:windows_type, :adult) }
+ let(:admin) { create(:user, :admin) }
+
+ before do
+ create(:affiliation, organization: organization, person: affiliated_person)
+ end
+
+ it "includes directly associated ideas and ideas authored by affiliated people without duplicates" do
+ direct_idea = create(:workshop_idea, title: "Direct Idea", organization: organization, windows_type: windows_type, created_by: admin)
+ authored_idea = create(:workshop_idea, title: "Authored Idea", author: affiliated_person, windows_type: windows_type, created_by: admin)
+ both_idea = create(:workshop_idea, title: "Both Idea", organization: organization, author: affiliated_person, windows_type: windows_type, created_by: admin)
+ unrelated_idea = create(:workshop_idea, title: "Unrelated Idea", windows_type: windows_type, created_by: admin)
+
+ results = organization.affiliated_workshop_ideas
+
+ expect(results).to include(direct_idea, authored_idea, both_idea)
+ expect(results).not_to include(unrelated_idea)
+ expect(results.where(id: both_idea.id).count).to eq(1)
+ end
+ end
end
RSpec.describe Organization, "scholarship index helpers" do
diff --git a/spec/models/workshop_idea_spec.rb b/spec/models/workshop_idea_spec.rb
index d93a96b609..c209e4528f 100644
--- a/spec/models/workshop_idea_spec.rb
+++ b/spec/models/workshop_idea_spec.rb
@@ -3,6 +3,23 @@
RSpec.describe WorkshopIdea, type: :model do
it_behaves_like "author_creditable", factory: :workshop_idea, org_credited: false, credits_creator: true
+ describe "associations" do
+ it { should belong_to(:organization).optional }
+ end
+
+ describe "validations" do
+ it "is valid with an organization" do
+ org = create(:organization)
+ idea = build(:workshop_idea, organization: org)
+ expect(idea).to be_valid
+ end
+
+ it "is valid without an organization" do
+ idea = build(:workshop_idea, organization: nil)
+ expect(idea).to be_valid
+ end
+ end
+
# An idea nobody named an author on still credits its submitter, who filled the form in.
describe "#author_credit" do
it "credits the creating user's person, not the generic label" do
diff --git a/spec/models/workshop_spec.rb b/spec/models/workshop_spec.rb
index 1981d72098..2a48ec6d17 100644
--- a/spec/models/workshop_spec.rb
+++ b/spec/models/workshop_spec.rb
@@ -11,6 +11,7 @@
it { should belong_to(:created_by).optional }
it { should belong_to(:author).class_name("Person").optional }
it { should belong_to(:windows_type).optional }
+ it { should belong_to(:organization).optional }
it { should have_many(:sectorable_items).dependent(:destroy).inverse_of(:sectorable) }
it { should have_many(:sectors).through(:sectorable_items) }
@@ -39,6 +40,17 @@
it { should validate_presence_of(:title) }
+ it "is valid with an organization" do
+ org = create(:organization)
+ subject.organization = org
+ expect(subject).to be_valid
+ end
+
+ it "is valid without an organization" do
+ subject.organization = nil
+ expect(subject).to be_valid
+ end
+
# Conditional presence validation for legacy workshops (month, year)
context 'when legacy is true' do
before { allow(subject).to receive(:legacy).and_return(true) }
diff --git a/spec/requests/organizations_spec.rb b/spec/requests/organizations_spec.rb
index ef0e0b42fb..64abb70223 100644
--- a/spec/requests/organizations_spec.rb
+++ b/spec/requests/organizations_spec.rb
@@ -96,6 +96,46 @@
expect(response).to be_successful
end
+ it "shows directly associated workshops and ideas on the profile" do
+ organization = Organization.create!(valid_attributes)
+ windows_type = create(:windows_type, :adult)
+ workshop = create(:workshop, organization: organization, title: "Org Pottery Workshop", windows_type: windows_type)
+ idea = create(:workshop_idea, organization: organization, title: "Org Pottery Idea", windows_type: windows_type, created_by: admin)
+
+ get organization_url(organization)
+
+ expect(response.body).to include(workshop.title)
+ expect(response.body).to include(idea.title)
+ end
+
+ it "shows workshops and ideas authored by affiliated people on the profile" do
+ organization = Organization.create!(valid_attributes)
+ affiliated_person = create(:person)
+ create(:affiliation, organization: organization, person: affiliated_person)
+ windows_type = create(:windows_type, :adult)
+ workshop = create(:workshop, author: affiliated_person, title: "Affiliated Weaving Workshop", windows_type: windows_type)
+ idea = create(:workshop_idea, author: affiliated_person, title: "Affiliated Weaving Idea", windows_type: windows_type, created_by: admin)
+
+ get organization_url(organization)
+
+ expect(response.body).to include(workshop.title)
+ expect(response.body).to include(idea.title)
+ end
+
+ it "does not duplicate workshops or ideas that are both directly and indirectly associated" do
+ organization = Organization.create!(valid_attributes)
+ affiliated_person = create(:person)
+ create(:affiliation, organization: organization, person: affiliated_person)
+ windows_type = create(:windows_type, :adult)
+ workshop = create(:workshop, organization: organization, author: affiliated_person, title: "Dual Mosaic Workshop", windows_type: windows_type)
+ idea = create(:workshop_idea, organization: organization, author: affiliated_person, title: "Dual Mosaic Idea", windows_type: windows_type, created_by: admin)
+
+ get organization_url(organization)
+
+ expect(response.body.scan(workshop.title).count).to eq(1)
+ expect(response.body.scan(idea.title).count).to eq(1)
+ end
+
it "hides the Monthly reports row when there are no monthly reports" do
organization = Organization.create!(valid_attributes)
get organization_url(organization)
diff --git a/spec/requests/workshop_ideas_spec.rb b/spec/requests/workshop_ideas_spec.rb
index e11ba382da..abddcbfb68 100644
--- a/spec/requests/workshop_ideas_spec.rb
+++ b/spec/requests/workshop_ideas_spec.rb
@@ -45,6 +45,19 @@
get workshop_ideas_path
expect(response).to have_http_status(:ok)
end
+
+ it "displays the organization name when present" do
+ org = create(:organization, name: "Sunset Arts")
+ create(:workshop_idea, valid_attributes.merge(organization: org))
+ get workshop_ideas_path
+ expect(response.body).to include("Sunset Arts")
+ end
+
+ it "does not error when organization is nil" do
+ create(:workshop_idea, valid_attributes.merge(organization: nil))
+ get workshop_ideas_path
+ expect(response).to have_http_status(:ok)
+ end
end
describe "GET /show" do
@@ -53,6 +66,19 @@
get workshop_idea_path(idea)
expect(response).to have_http_status(:ok)
end
+
+ it "displays the organization name when present" do
+ org = create(:organization, name: "Sunset Arts")
+ idea = create(:workshop_idea, valid_attributes.merge(organization: org))
+ get workshop_idea_path(idea)
+ expect(response.body).to include("Sunset Arts")
+ end
+
+ it "does not error when organization is nil" do
+ idea = create(:workshop_idea, valid_attributes.merge(organization: nil))
+ get workshop_idea_path(idea)
+ expect(response).to have_http_status(:ok)
+ end
end
describe "GET /new" do
@@ -60,6 +86,18 @@
get new_workshop_idea_path
expect(response).to have_http_status(:ok)
end
+
+ it "offers every organization in the dropdown, including ones the admin isn't affiliated with" do
+ first_org = create(:organization, name: "First Arts Org")
+ second_org = create(:organization, name: "Second Arts Org")
+
+ get new_workshop_idea_path
+
+ page = Capybara.string(response.body)
+ option_values = page.all("select[name='workshop_idea[organization_id]'] option").map(&:value)
+ expect(option_values).to include(first_org.id.to_s, second_org.id.to_s)
+ expect(option_values).to include("")
+ end
end
describe "GET /edit" do
@@ -79,10 +117,26 @@
expect(response).to redirect_to(workshop_idea_path(WorkshopIdea.last))
end
+ it "creates a WorkshopIdea with an organization" do
+ org = create(:organization)
+ expect {
+ post workshop_ideas_path, params: { workshop_idea: valid_attributes.merge(organization_id: org.id) }
+ }.to change(WorkshopIdea, :count).by(1)
+
+ expect(WorkshopIdea.last.organization).to eq(org)
+ end
+
+ it "creates a WorkshopIdea without an organization" do
+ expect {
+ post workshop_ideas_path, params: { workshop_idea: valid_attributes.merge(organization_id: "") }
+ }.to change(WorkshopIdea, :count).by(1)
+
+ expect(WorkshopIdea.last.organization).to be_nil
+ end
+
it "sends the author's confirmation and the admin FYI" do
submitter = create(:user, :with_person, super_user: true)
sign_in submitter
-
expect(NotificationServices::CreateNotification).to receive(:call).with(
hash_including(
kind: :idea_submitted,
@@ -125,6 +179,26 @@
expect(response).to redirect_to(workshop_idea_path(idea))
end
+ it "updates the organization_id" do
+ idea = create(:workshop_idea, valid_attributes)
+ org = create(:organization)
+
+ patch workshop_idea_path(idea),
+ params: { workshop_idea: { organization_id: org.id } }
+
+ expect(idea.reload.organization_id).to eq(org.id)
+ end
+
+ it "allows clearing the organization_id" do
+ org = create(:organization)
+ idea = create(:workshop_idea, valid_attributes.merge(organization: org))
+
+ patch workshop_idea_path(idea),
+ params: { workshop_idea: { organization_id: "" } }
+
+ expect(idea.reload.organization_id).to be_nil
+ end
+
it "handles RecordNotUnique gracefully" do
idea = create(:workshop_idea, valid_attributes)
@@ -180,6 +254,44 @@
end
end
+ describe "GET /new" do
+ before do
+ allow_any_instance_of(WorkshopIdeaPolicy).to receive(:new?).and_return(true)
+ end
+
+ it "fills in the one organization the user is currently active at" do
+ person = create(:person, user: regular_user)
+ current_org = create(:organization, name: "Current Arts Org")
+ past_org = create(:organization, name: "Former Arts Org")
+ create(:affiliation, person: person, organization: current_org, start_date: 1.year.ago)
+ create(:affiliation, person: person, organization: current_org, start_date: 5.years.ago, end_date: 3.years.ago)
+ create(:affiliation, person: person, organization: past_org, start_date: 3.years.ago, end_date: 2.years.ago)
+
+ get new_workshop_idea_path
+
+ page = Capybara.string(response.body)
+ expect(page).to have_field("workshop_idea[organization_id]", type: :hidden, with: current_org.id.to_s)
+ expect(page).not_to have_select("workshop_idea[organization_id]")
+ expect(page).not_to have_text(past_org.name)
+ end
+
+ it "lets the user choose among several current organizations" do
+ person = create(:person, user: regular_user)
+ first_org = create(:organization, name: "First Arts Org")
+ second_org = create(:organization, name: "Second Arts Org")
+ past_org = create(:organization, name: "Former Arts Org")
+ create(:affiliation, person: person, organization: first_org)
+ create(:affiliation, person: person, organization: second_org)
+ create(:affiliation, person: person, organization: past_org, inactive: true)
+
+ get new_workshop_idea_path
+
+ page = Capybara.string(response.body)
+ option_values = page.all("select[name='workshop_idea[organization_id]'] option").map(&:value)
+ expect(option_values).to eq([ "", first_org.id.to_s, second_org.id.to_s ])
+ end
+ end
+
describe "POST /create" do
context "with valid params" do
it "creates a WorkshopIdea" do
@@ -188,6 +300,25 @@
params: { workshop_idea: valid_attributes }
}.to change(WorkshopIdea, :count).by(1)
end
+
+ it "creates a WorkshopIdea with an organization" do
+ org = create(:organization)
+ expect {
+ post workshop_ideas_path,
+ params: { workshop_idea: valid_attributes.merge(organization_id: org.id) }
+ }.to change(WorkshopIdea, :count).by(1)
+
+ expect(WorkshopIdea.last.organization).to eq(org)
+ end
+
+ it "creates a WorkshopIdea without an organization" do
+ expect {
+ post workshop_ideas_path,
+ params: { workshop_idea: valid_attributes.merge(organization_id: "") }
+ }.to change(WorkshopIdea, :count).by(1)
+
+ expect(WorkshopIdea.last.organization).to be_nil
+ end
end
context "with invalid params" do
diff --git a/spec/requests/workshops_spec.rb b/spec/requests/workshops_spec.rb
index 5c84aaae7b..d51e4772ae 100644
--- a/spec/requests/workshops_spec.rb
+++ b/spec/requests/workshops_spec.rb
@@ -345,4 +345,145 @@
end
end
end
+
+ describe "organization handling" do
+ let(:admin) { create(:user, :admin) }
+ let(:windows_type) { create(:windows_type) }
+ let(:organization) { create(:organization, name: "Hope Harbor") }
+
+ before { sign_in admin }
+
+ describe "GET /new" do
+ it "offers all organizations in the dropdown with a blank prompt when multiple exist" do
+ first_org = create(:organization, name: "First Community")
+ second_org = create(:organization, name: "Second Community")
+
+ get new_workshop_url
+
+ page = Capybara.string(response.body)
+ option_values = page.all("select[name='workshop[organization_id]'] option").map(&:value)
+ expect(option_values).to include(first_org.id.to_s, second_org.id.to_s)
+ expect(option_values).to include("")
+ end
+
+ it "prefills organization_id in select dropdown when multiple organizations exist" do
+ create(:organization, name: "Another Org")
+ idea = create(:workshop_idea, organization: organization, windows_type: windows_type)
+
+ get new_workshop_url(workshop_idea_id: idea.id)
+
+ expect(response).to have_http_status(:ok)
+ page = Capybara.string(response.body)
+ expect(page).to have_select("workshop[organization_id]", selected: organization.type_name)
+ end
+
+ it "uses a hidden field for organization_id when only one organization exists" do
+ idea = create(:workshop_idea, organization: organization, windows_type: windows_type)
+
+ get new_workshop_url(workshop_idea_id: idea.id)
+
+ expect(response).to have_http_status(:ok)
+ page = Capybara.string(response.body)
+ expect(page).to have_field("workshop[organization_id]", type: :hidden, with: organization.id.to_s)
+ end
+ end
+
+ describe "POST /create" do
+ it "creates a workshop with an organization" do
+ expect {
+ post workshops_url, params: {
+ workshop: {
+ title: "Org Workshop",
+ windows_type_id: windows_type.id,
+ organization_id: organization.id,
+ category_ids: [ "" ]
+ }
+ }
+ }.to change(Workshop, :count).by(1)
+
+ expect(Workshop.last.organization).to eq(organization)
+ end
+
+ it "creates a workshop without an organization" do
+ expect {
+ post workshops_url, params: {
+ workshop: {
+ title: "No Org Workshop",
+ windows_type_id: windows_type.id,
+ organization_id: "",
+ category_ids: [ "" ]
+ }
+ }
+ }.to change(Workshop, :count).by(1)
+
+ expect(Workshop.last.organization).to be_nil
+ end
+ end
+
+ describe "PATCH /update" do
+ it "updates the workshop's organization" do
+ workshop = create(:workshop, organization: nil)
+
+ patch workshop_url(workshop), params: {
+ workshop: {
+ title: workshop.title,
+ organization_id: organization.id,
+ category_ids: [ "" ]
+ }
+ }
+
+ expect(workshop.reload.organization).to eq(organization)
+ end
+
+ it "allows clearing the organization" do
+ workshop = create(:workshop, organization: organization)
+
+ patch workshop_url(workshop), params: {
+ workshop: {
+ title: workshop.title,
+ organization_id: "",
+ category_ids: [ "" ]
+ }
+ }
+
+ expect(workshop.reload.organization).to be_nil
+ end
+ end
+
+ describe "GET /show" do
+ it "displays the organization name when present" do
+ workshop = create(:workshop, organization: organization, published: true)
+
+ get workshop_url(workshop)
+
+ expect(response.body).to include(organization.name)
+ end
+
+ it "does not error when organization is nil" do
+ workshop = create(:workshop, organization: nil, published: true)
+
+ get workshop_url(workshop)
+
+ expect(response).to have_http_status(:ok)
+ end
+ end
+
+ describe "GET /index" do
+ it "displays the organization name in results row when present" do
+ workshop = create(:workshop, organization: organization, published: true)
+
+ get workshops_url, headers: { "Turbo-Frame" => "workshops_results" }
+
+ expect(response.body).to include(organization.name)
+ end
+
+ it "does not error when organization is nil" do
+ create(:workshop, organization: nil, published: true)
+
+ get workshops_url, headers: { "Turbo-Frame" => "workshops_results" }
+
+ expect(response).to have_http_status(:ok)
+ end
+ end
+ end
end
diff --git a/spec/services/workshop_from_idea_service_spec.rb b/spec/services/workshop_from_idea_service_spec.rb
new file mode 100644
index 0000000000..f698482877
--- /dev/null
+++ b/spec/services/workshop_from_idea_service_spec.rb
@@ -0,0 +1,27 @@
+require "rails_helper"
+
+RSpec.describe WorkshopFromIdeaService do
+ let(:admin) { create(:user, :admin) }
+ let(:organization) { create(:organization) }
+ let(:windows_type) { create(:windows_type) }
+
+ describe "#call" do
+ it "copies organization_id from the workshop_idea to the initialized workshop" do
+ idea = create(:workshop_idea, organization: organization, windows_type: windows_type)
+
+ workshop = described_class.new(idea, user: admin).call
+
+ expect(workshop.organization_id).to eq(organization.id)
+ expect(workshop.organization).to eq(organization)
+ expect(workshop.workshop_idea_id).to eq(idea.id)
+ end
+
+ it "sets organization_id to nil when workshop_idea has no organization" do
+ idea = create(:workshop_idea, organization: nil, windows_type: windows_type)
+
+ workshop = described_class.new(idea, user: admin).call
+
+ expect(workshop.organization_id).to be_nil
+ end
+ end
+end
diff --git a/spec/system/organization_workshops_and_ideas_spec.rb b/spec/system/organization_workshops_and_ideas_spec.rb
new file mode 100644
index 0000000000..c6885e2720
--- /dev/null
+++ b/spec/system/organization_workshops_and_ideas_spec.rb
@@ -0,0 +1,69 @@
+require "rails_helper"
+
+RSpec.describe "Organization workshops and ideas", type: :system do
+ let(:admin) { create(:user, :admin) }
+ let(:windows_type) { create(:windows_type, :adult) }
+ let(:organization) { create(:organization, name: "Community Arts Center") }
+ let(:affiliated_person) { create(:person, first_name: "Jane", last_name: "Artist") }
+
+ before do
+ driven_by(:selenium_chrome_headless)
+ create(:affiliation, organization: organization, person: affiliated_person)
+ end
+
+ scenario "Organization profile displays directly associated workshops and ideas" do
+ sign_in(admin)
+
+ direct_workshop = create(:workshop, :published,
+ title: "Direct Pottery Workshop",
+ organization: organization,
+ windows_type: windows_type)
+ direct_idea = create(:workshop_idea,
+ title: "Direct Clay Idea",
+ organization: organization,
+ windows_type: windows_type,
+ created_by: admin)
+
+ visit organization_path(organization)
+
+ expect(page).to have_content("Workshops authored")
+ expect(page).to have_content(direct_workshop.title)
+ expect(page).to have_content("Workshop ideas")
+ expect(page).to have_content(direct_idea.title)
+ end
+
+ scenario "Organization profile displays workshops and ideas authored by affiliated people" do
+ sign_in(admin)
+
+ affiliated_workshop = create(:workshop, :published,
+ title: "Affiliated Painting Workshop",
+ author: affiliated_person,
+ organization: nil,
+ windows_type: windows_type)
+ affiliated_idea = create(:workshop_idea,
+ title: "Affiliated Canvas Idea",
+ author: affiliated_person,
+ organization: nil,
+ windows_type: windows_type,
+ created_by: admin)
+
+ visit organization_path(organization)
+
+ expect(page).to have_content(affiliated_workshop.title)
+ expect(page).to have_content(affiliated_idea.title)
+ end
+
+ scenario "Avoids duplicates when a workshop is both directly and indirectly associated" do
+ sign_in(admin)
+
+ create(:workshop, :published,
+ title: "Dual Linked Workshop",
+ author: affiliated_person,
+ organization: organization,
+ windows_type: windows_type)
+
+ visit organization_path(organization)
+
+ expect(page.all("a", text: "Dual Linked Workshop").count).to eq(1)
+ end
+end
diff --git a/spec/views/workshop_ideas/edit.html.erb_spec.rb b/spec/views/workshop_ideas/edit.html.erb_spec.rb
index 57db42f111..c14b35ba77 100644
--- a/spec/views/workshop_ideas/edit.html.erb_spec.rb
+++ b/spec/views/workshop_ideas/edit.html.erb_spec.rb
@@ -15,9 +15,9 @@
assign(:categories_grouped, [])
assign(:potential_series_workshops, [])
assign(:users, User.none)
+ assign(:organizations, [])
end
-
it "renders the edit workshop_idea form" do
render
@@ -26,6 +26,8 @@
assert_select "select[name=?]", "workshop_idea[windows_type_id]"
+ assert_select "select[name=?]", "workshop_idea[organization_id]"
+
assert_select "input[type=hidden][name=?]", "workshop_idea[rhino_tips]"
assert_select "input[type=hidden][name=?]", "workshop_idea[rhino_objective]"
diff --git a/spec/views/workshop_ideas/new.html.erb_spec.rb b/spec/views/workshop_ideas/new.html.erb_spec.rb
index 2ed237b264..f2ad8a6cd8 100644
--- a/spec/views/workshop_ideas/new.html.erb_spec.rb
+++ b/spec/views/workshop_ideas/new.html.erb_spec.rb
@@ -13,6 +13,7 @@
assign(:categories_grouped, [])
assign(:potential_series_workshops, [])
assign(:users, User.none)
+ assign(:organizations, [])
end
it "renders new workshop_idea form" do
@@ -23,6 +24,8 @@
assert_select "select[name=?]", "workshop_idea[windows_type_id]"
+ assert_select "select[name=?]", "workshop_idea[organization_id]"
+
assert_select "input[type=hidden][name=?]", "workshop_idea[rhino_tips]"
assert_select "input[type=hidden][name=?]", "workshop_idea[rhino_objective]"
diff --git a/spec/views/workshops/edit.html.erb_spec.rb b/spec/views/workshops/edit.html.erb_spec.rb
index 538e1821c2..76ca5da990 100644
--- a/spec/views/workshops/edit.html.erb_spec.rb
+++ b/spec/views/workshops/edit.html.erb_spec.rb
@@ -14,6 +14,7 @@
assign(:sectors, [])
assign(:categories_grouped, [])
assign(:age_range_comments, [])
+ assign(:organizations, [])
end
context "when user is an admin" do
|