diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index a5c669b8b..f181c56f6 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -25,7 +25,10 @@ def index records = records.limit(25) - render json: records.map(&:remote_search_label) + labels = records.map(&:remote_search_label) + labels = model_class.resolve_duplicate_labels(labels) if model_class.respond_to?(:resolve_duplicate_labels) + + render json: labels end private diff --git a/app/models/workshop.rb b/app/models/workshop.rb index 86a2f645d..c683b1ece 100644 --- a/app/models/workshop.rb +++ b/app/models/workshop.rb @@ -326,6 +326,21 @@ def attach_assets_from_idea! remote_searchable_by :title + def self.remote_search(query) + super.includes(:windows_type) + end + + def remote_search_label + label = windows_type ? "#{title} (#{windows_type.short_name})" : title + { id: id, label: label } + end + + def self.resolve_duplicate_labels(labels) + dupes = labels.group_by { |l| l[:label] }.select { |_, v| v.size > 1 }.keys.to_set + labels.each { |l| l[:label] = "#{l[:label]} ##{l[:id]}" if dupes.include?(l[:label]) } + labels + end + private def assign_pending_associations diff --git a/app/views/stories/_form.html.erb b/app/views/stories/_form.html.erb index 22de452bd..e061662fc 100644 --- a/app/views/stories/_form.html.erb +++ b/app/views/stories/_form.html.erb @@ -50,7 +50,7 @@
<%= f.input :workshop_id, - collection: f.object.workshop.present? ? [[ f.object.workshop.title, f.object.workshop.id ]] : [], + collection: f.object.workshop.present? ? [[ f.object.workshop.remote_search_label[:label], f.object.workshop.id ]] : [], include_blank: true, input_html: { class: "w-full px-3 py-2 border border-gray-300 rounded-lg", diff --git a/app/views/story_ideas/_form.html.erb b/app/views/story_ideas/_form.html.erb index 5647878b1..fb4e8ee00 100644 --- a/app/views/story_ideas/_form.html.erb +++ b/app/views/story_ideas/_form.html.erb @@ -58,7 +58,7 @@
<%= f.input :workshop_id, - collection: f.object.workshop.present? ? [[ f.object.workshop.title, f.object.workshop.id]] : [], + collection: f.object.workshop.present? ? [[ f.object.workshop.remote_search_label[:label], f.object.workshop.id]] : [], include_blank: true, required: true, input_html: { diff --git a/app/views/workshop_logs/_form.html.erb b/app/views/workshop_logs/_form.html.erb index ebdca4f07..320a3465f 100644 --- a/app/views/workshop_logs/_form.html.erb +++ b/app/views/workshop_logs/_form.html.erb @@ -12,7 +12,7 @@
<%= f.input :workshop_id, - collection: f.object.workshop.present? ? [[ f.object.workshop.title, f.object.workshop.id]] : [], + collection: f.object.workshop.present? ? [[ f.object.workshop.remote_search_label[:label], f.object.workshop.id]] : [], include_blank: true, required: true, input_html: { diff --git a/app/views/workshop_variation_ideas/_form.html.erb b/app/views/workshop_variation_ideas/_form.html.erb index 5379ad976..c9ca35437 100644 --- a/app/views/workshop_variation_ideas/_form.html.erb +++ b/app/views/workshop_variation_ideas/_form.html.erb @@ -35,7 +35,7 @@
<%= f.input :workshop_id, - collection: f.object.workshop.present? ? [[ f.object.workshop.title, f.object.workshop.id]] : [], + collection: f.object.workshop.present? ? [[ f.object.workshop.remote_search_label[:label], f.object.workshop.id]] : [], include_blank: true, required: true, input_html: { diff --git a/app/views/workshop_variations/_form.html.erb b/app/views/workshop_variations/_form.html.erb index 9709f0a5e..601ab16bc 100644 --- a/app/views/workshop_variations/_form.html.erb +++ b/app/views/workshop_variations/_form.html.erb @@ -14,7 +14,7 @@
<% else %> <%= f.input :workshop_id, - collection: f.object.workshop.present? ? [[ f.object.workshop.title, f.object.workshop.id]] : [], + collection: f.object.workshop.present? ? [[ f.object.workshop.remote_search_label[:label], f.object.workshop.id]] : [], include_blank: true, required: true, input_html: { diff --git a/app/views/workshops/edit.html.erb b/app/views/workshops/edit.html.erb index 1794ed784..de32c8f51 100644 --- a/app/views/workshops/edit.html.erb +++ b/app/views/workshops/edit.html.erb @@ -5,7 +5,7 @@ <%= link_to "Workshops", workshops_path, class: "text-sm text-gray-500 hover:text-gray-700 px-2 py-1" %> <%= link_to "View", workshop_path(@workshop), class: "text-sm text-gray-500 hover:text-gray-700 px-2 py-1" %>
-

Edit workshop

+

Edit workshop: <%= @workshop.remote_search_label[:label] %>

diff --git a/db/seeds/dummy_dev_seeds.rb b/db/seeds/dummy_dev_seeds.rb index 9cf0f20d0..7a0e4388b 100644 --- a/db/seeds/dummy_dev_seeds.rb +++ b/db/seeds/dummy_dev_seeds.rb @@ -322,6 +322,34 @@ Workshop.where(title: workshop_data[:title]).first_or_create!(workshop_data) end +# Duplicate-title workshops to exercise ID disambiguation in search +[ + { + title: "Healing Through Color", + windows_type: adult_wt, + full_name: "Maria Torres", + month: 3, + year: 2020, + description: "Uses color mixing and painting to help participants explore emotions and find calm. Participants create a personal color wheel that maps feelings to colors.", + published: true, + searchable: true, + created_by: admin_user + }, + { + title: "Healing Through Color", + windows_type: adult_wt, + full_name: "James Whitfield", + month: 9, + year: 2022, + description: "A revised version exploring color as a pathway to emotional awareness. Participants blend watercolors while discussing how color connects to memory and healing.", + published: true, + searchable: true, + created_by: admin_user + } +].each do |workshop_data| + Workshop.create!(workshop_data) +end + puts "Assigning workshop categories and sectors…" workshops = Workshop.all categories = Category.all.to_a diff --git a/spec/models/workshop_spec.rb b/spec/models/workshop_spec.rb index 6634cbb60..f5f326352 100644 --- a/spec/models/workshop_spec.rb +++ b/spec/models/workshop_spec.rb @@ -119,5 +119,68 @@ end end + describe "#remote_search_label" do + it "returns title with windows type short_name" do + record = create(:workshop, title: "Art Therapy", windows_type: create(:windows_type, :children)) + + expect(record.remote_search_label).to eq({ id: record.id, label: "Art Therapy (CHILDREN)" }) + end + + it "returns just the title when no windows type" do + record = create(:workshop, title: "Art Therapy", windows_type: nil) + + expect(record.remote_search_label).to eq({ id: record.id, label: "Art Therapy" }) + end + end + + describe ".resolve_duplicate_labels" do + it "omits id when labels are unique" do + wt = create(:windows_type, :adult) + w1 = create(:workshop, title: "Art Therapy", windows_type: wt) + w2 = create(:workshop, title: "Music Therapy", windows_type: wt) + + labels = Workshop.resolve_duplicate_labels([ w1, w2 ].map(&:remote_search_label)) + + expect(labels).to contain_exactly( + { id: w1.id, label: "Art Therapy (ADULT)" }, + { id: w2.id, label: "Music Therapy (ADULT)" } + ) + end + + it "appends id only to duplicate labels" do + wt = create(:windows_type, :adult) + w1 = create(:workshop, title: "Art Therapy", windows_type: wt) + w2 = create(:workshop, title: "Art Therapy", windows_type: wt) + w3 = create(:workshop, title: "Music Therapy", windows_type: wt) + + labels = Workshop.resolve_duplicate_labels([ w1, w2, w3 ].map(&:remote_search_label)) + + expect(labels).to contain_exactly( + { id: w1.id, label: "Art Therapy (ADULT) ##{w1.id}" }, + { id: w2.id, label: "Art Therapy (ADULT) ##{w2.id}" }, + { id: w3.id, label: "Music Therapy (ADULT)" } + ) + end + end + + describe ".remote_search" do + it "finds workshops matching the query" do + matching = create(:workshop, title: "Healing Through Art") + create(:workshop, title: "Unrelated Workshop") + + results = Workshop.remote_search("Healing") + + expect(results).to contain_exactly(matching) + end + + it "eager loads windows_type to avoid N+1" do + create(:workshop, title: "Healing Through Art") + + results = Workshop.remote_search("Healing") + + expect(results.includes_values).to include(:windows_type) + end + end + # Add tests for scopes, methods like #rating, #log_count, SearchCop etc. end