diff --git a/app/models/workshop.rb b/app/models/workshop.rb index c8dc49fd6..1ba67d22b 100644 --- a/app/models/workshop.rb +++ b/app/models/workshop.rb @@ -122,7 +122,10 @@ class Workshop < ApplicationRecord scope :legacy, -> { where(legacy: true) } scope :published, ->(published = nil) { published.to_s.present? ? where(inactive: !published) : where(inactive: false) } - scope :title, ->(title) { where("workshops.title like ?", "%#{ title }%") } + scope :title, ->(title) { + sanitized_input = ActiveRecord::Base.sanitize_sql_like(title.to_s.gsub("-", "")) + where("REPLACE(workshops.title, '-', '') like ?", "%#{sanitized_input}%") + } scope :windows_type_ids, ->(windows_type_ids) { where(windows_type_id: windows_type_ids) } scope :order_by_date, ->(sort_order = "asc") { order(Arel.sql(<<~SQL.squish)) diff --git a/app/services/workshop_search_service.rb b/app/services/workshop_search_service.rb index 43b6ffb97..751ebeba8 100644 --- a/app/services/workshop_search_service.rb +++ b/app/services/workshop_search_service.rb @@ -121,16 +121,30 @@ def filter_by_title def filter_by_query return unless params[:query].present? - results = @workshops.search(params[:query]) # Use the SearchCop search scope directly on the relation - - # If SearchCop returned an Array (e.g., because of scoring), convert back to Relation - if results.is_a?(Array) - ordered_ids = results.map(&:id) - @workshops = Workshop.where(id: ordered_ids) - .order(Arel.sql("FIELD(id, #{ordered_ids.join(',')})")) - else - @workshops = results - end + # Strip hyphens from the query to match against hyphen-stripped content + sanitized_query = ActiveRecord::Base.sanitize_sql_like(params[:query].to_s.gsub("-", "").strip) + return if sanitized_query.blank? + + # Build a custom SQL query that strips hyphens from all searchable fields + searchable_fields = [ + :title, :full_name, + :objective, :materials, :setup, :introduction, + :demonstration, :opening_circle, :warm_up, + :creation, :closing, :notes, :tips, :misc1, :misc2, + :objective_spanish, :materials_spanish, :setup_spanish, :introduction_spanish, + :demonstration_spanish, :opening_circle_spanish, :warm_up_spanish, + :creation_spanish, :closing_spanish, :notes_spanish, :tips_spanish, :misc1_spanish, :misc2_spanish + ] + + # Create WHERE conditions that strip hyphens from both field and search term + conditions = searchable_fields.map do |field| + "REPLACE(workshops.#{field}, '-', '') LIKE ?" + end.join(" OR ") + + # Use the same sanitized query for all fields + query_params = Array.new(searchable_fields.length, "%#{sanitized_query}%") + + @workshops = @workshops.where(conditions, *query_params) end # --- Search methods --- diff --git a/spec/services/workshop_search_service_spec.rb b/spec/services/workshop_search_service_spec.rb index 9d3fd5f14..dfbdcc6b3 100644 --- a/spec/services/workshop_search_service_spec.rb +++ b/spec/services/workshop_search_service_spec.rb @@ -96,5 +96,66 @@ expect(service.sort).to eq('created') end end + + context "hyphen-ignoring search" do + let!(:workshop_with_hyphen) do + create(:workshop, title: "Hello - Goodbye", year: 2025, month: 3) + end + let!(:workshop_without_hyphen) do + create(:workshop, title: "Hello Goodbye", year: 2025, month: 4) + end + + context "title search" do + it "finds workshops with hyphens when searching without hyphens" do + service = WorkshopSearchService.new({ title: 'Hello Goodbye' }).call + workshops = service.workshops + + expect(workshops).to include(workshop_with_hyphen) + expect(workshops).to include(workshop_without_hyphen) + end + + it "finds workshops without hyphens when searching with hyphens" do + service = WorkshopSearchService.new({ title: 'Hello - Goodbye' }).call + workshops = service.workshops + + expect(workshops).to include(workshop_with_hyphen) + expect(workshops).to include(workshop_without_hyphen) + end + + it "finds workshops with multiple hyphens when searching with different hyphen patterns" do + workshop_multi_hyphen = create(:workshop, title: "Hello -- Goodbye", year: 2025, month: 5) + + service = WorkshopSearchService.new({ title: 'Hello Goodbye' }).call + workshops = service.workshops + + expect(workshops).to include(workshop_multi_hyphen) + end + end + + context "query search" do + let!(:workshop_with_hyphen_content) do + create(:workshop, title: "Test Workshop", objective: "Learn about self-care", year: 2025, month: 6) + end + let!(:workshop_without_hyphen_content) do + create(:workshop, title: "Another Workshop", objective: "Learn about selfcare", year: 2025, month: 7) + end + + it "finds workshops with hyphens in content when searching without hyphens" do + service = WorkshopSearchService.new({ query: 'selfcare' }).call + workshops = service.workshops + + expect(workshops).to include(workshop_with_hyphen_content) + expect(workshops).to include(workshop_without_hyphen_content) + end + + it "finds workshops without hyphens in content when searching with hyphens" do + service = WorkshopSearchService.new({ query: 'self-care' }).call + workshops = service.workshops + + expect(workshops).to include(workshop_with_hyphen_content) + expect(workshops).to include(workshop_without_hyphen_content) + end + end + end end end