Skip to content

Commit 48ef981

Browse files
authored
[#4552] More reliable boolean operators on the advanced search form (#4553)
* [#4552] More reliable boolean operators on the advanced search form This is not quite working, see failing test spec/features/advanced_searching_spec.rb:156 Specifically, if the user enters a boolean NOT search in the advanced search, the constraints on the search results page is currently not including the word NOT, so the user doesn't get the feedback that it is a negated term. I think we could address this in our ClausePresenter, although it might make that class quite a bit more complex. * Mutate the params in the controller instead This allows the ClausePresenter to access both our params (e.g. boolean_operator1=OR) and the ones that Solr's BoolQParser likes (e.g. clause[0][op]=should).
1 parent c27bf59 commit 48ef981

File tree

6 files changed

+205
-41
lines changed

6 files changed

+205
-41
lines changed

.reek.yml

Lines changed: 7 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ detectors:
5050
- Requests::ApplicationHelper#hidden_service_options
5151
- Requests::ApplicationHelper#single_pickup
5252
- Requests::RequestHelper#reshare_type
53+
- AdvancedBooleanOperators#initialize
5354
- Blacklight::Document::JsonLd#check_role
5455
- Blacklight::Document::JsonLd#date
5556
- Requests::Aeon#holding_location_to_site
@@ -62,16 +63,12 @@ detectors:
6263
- PhysicalHoldingsMarkupBuilder#self.thesis?
6364
DataClump:
6465
exclude:
65-
- ApplicationHelper
6666
- Requests::ApplicationHelper
6767
- Requests::SelectedItemsValidator
6868
- PhysicalHoldingsMarkupBuilder
6969
DuplicateMethodCall:
7070
exclude:
7171
- Holdings::OnlineHoldingsComponent#marc_links
72-
- LocationCodeFacetComponent#add_scsb_loc
73-
- LocationCodeFacetComponent#fetch_libraries_and_locations
74-
- LocationCodeFacetComponent#library_facet_values
7572
- NumismaticsSearchFormComponent#initialize_constraints
7673
- Orangelight::AdvancedSearchFormComponent#fields_for_etc
7774
- Orangelight::AdvancedSearchFormComponent#initialize_search_filter_controls
@@ -86,7 +83,6 @@ detectors:
8683
- ApplicationController#default_url_options
8784
- ApplicationController#origin
8885
- BookmarksController#csv_values
89-
- BookmarksController#index
9086
- CatalogController#render_empty_search
9187
- Orangelight::Catalog#linked_records
9288
- Orangelight::Catalog#redirect_browse
@@ -151,7 +147,6 @@ detectors:
151147
- Requests::RequestHelper#pul_patron_name
152148
- Requests::RequestMailer#on_shelf_email
153149
- Requests::RequestMailer#paging_pick_ups
154-
- Blacklight::Document::Email#add_holding_fields
155150
- Blacklight::Document::Email#add_online_text
156151
- Blacklight::Document::JsonLd#data
157152
- Blacklight::Document::JsonLd#date
@@ -171,7 +166,7 @@ detectors:
171166
- Requests::Scsb#parse_scsb_response
172167
- Requests::Scsb#scsb_param_mapping
173168
- Requests::Scsb#scsb_request
174-
- RecordMailer#email_record
169+
- Orangelight::RecordMailer#email_record
175170
- Requests::AeonUrl#ctx_with_item_info
176171
- Requests::ClancyItem#get_clancy
177172
- Requests::ClancyItem#initialize
@@ -252,7 +247,6 @@ detectors:
252247
- StackmapService::Url#url
253248
FeatureEnvy:
254249
exclude:
255-
- LocationCodeFacetComponent#add_scsb_loc
256250
- Orangelight::ConstraintsComponent#guided_search_value
257251
- Orangelight::FacetFieldCheckboxesComponent#values
258252
- Orangelight::SearchBarComponent#search_fields
@@ -262,7 +256,6 @@ detectors:
262256
- AdvancedHelper#advanced_search_fields
263257
- AdvancedHelper#param_for_field
264258
- ApplicationHelper#action_notes_display
265-
- ApplicationHelper#search_location_display
266259
- ApplicationHelper#series_results
267260
- ApplicationHelper#title_hierarchy
268261
- BlacklightHelper#isbn_resolve
@@ -292,7 +285,7 @@ detectors:
292285
- Requests::Scsb#parse_scsb_response
293286
- Requests::Scsb#scsb_param_mapping
294287
- Requests::Scsb#scsb_request
295-
- RecordMailer#email_record
288+
- Orangelight::RecordMailer#email_record
296289
- Requests::AlmaPatron#active_barcode
297290
- Requests::ClancyItem#get_clancy
298291
- Requests::ClancyItem#post_clancy
@@ -384,7 +377,6 @@ detectors:
384377
- IndexMetadataComponent
385378
- IndexMetadataFieldLayoutComponent
386379
- IndexTitleComponent
387-
- LocationCodeFacetComponent
388380
- MultiselectComboboxComponent
389381
- NumismaticsSearchFormComponent
390382
- Orangelight::AdvancedSearchFormComponent
@@ -395,6 +387,7 @@ detectors:
395387
- Orangelight::FacetFieldCheckboxesComponent
396388
- Orangelight::SearchBarComponent
397389
- Orangelight::SearchContext::ServerItemPaginationComponent
390+
- Orangelight::System::FlashMessageComponent
398391
- AccountController
399392
- ApplicationController
400393
- BookmarksController
@@ -442,6 +435,7 @@ detectors:
442435
- Orangelight::CallNumber
443436
- Orangelight::Name
444437
- Orangelight::NameTitle
438+
- Orangelight::RecordMailer
445439
- Orangelight::Subject
446440
- Orangelight
447441
- Requests::ClancyItem
@@ -511,8 +505,6 @@ detectors:
511505
LongParameterList:
512506
exclude:
513507
- Orangelight::ConstraintsComponent#initialize
514-
- ApplicationHelper#locate_link_with_glyph
515-
- ApplicationHelper#locate_url
516508
- HoldingsHelper#holding_status_li
517509
- Requests::ApplicationHelper#single_pickup
518510
- Requests::RequestMailer#confirmation_email
@@ -546,7 +538,6 @@ detectors:
546538
- ApplicationController
547539
NestedIterators:
548540
exclude:
549-
- LocationCodeFacetComponent#fetch_libraries_and_locations
550541
- BookmarksController#csv_output
551542
- AdvancedHelper#generate_solr_fq
552543
- ApplicationHelper#name_title_hierarchy
@@ -568,10 +559,6 @@ detectors:
568559
- SolrDocument#identifiers
569560
NilCheck:
570561
exclude:
571-
- LocationCodeFacetComponent#add_library
572-
- LocationCodeFacetComponent#add_scsb_loc
573-
- LocationCodeFacetComponent#fetch_libraries_and_locations
574-
- LocationCodeFacetComponent#location_codes_by_lib
575562
- Orangelight::AdvancedSearchFormComponent#initialize_search_filter_controls
576563
- AccountController#cancel_ill_requests
577564
- ApplicationController#after_sign_in_path_for
@@ -589,7 +576,6 @@ detectors:
589576
- ApplicationHelper#alma_location_display
590577
- ApplicationHelper#fast_subjects_value?
591578
- ApplicationHelper#holding_location_label
592-
- ApplicationHelper#locate_link_with_glyph
593579
- ApplicationHelper#render_location_code
594580
- BlacklightHelper#html_facets
595581
- BlacklightHelper#isbn_resolve
@@ -647,7 +633,7 @@ detectors:
647633
TooManyInstanceVariables:
648634
exclude:
649635
- MultiselectComboboxComponent
650-
- BookmarksController
636+
- CatalogController
651637
- FeedbackController
652638
- Orangelight::BrowsablesController
653639
- Requests::FormController
@@ -689,13 +675,10 @@ detectors:
689675
- AeonStatus#check!
690676
- BibdataStatus#check!
691677
- Holdings::OnlineHoldingsComponent#marc_links
692-
- LocationCodeFacetComponent#fetch_libraries_and_locations
693-
- LocationCodeFacetComponent#location_codes_by_lib
694678
- Orangelight::ConstraintsComponent#guided_search_value
695679
- Orangelight::SearchBarComponent#before_render
696680
- AccountController#cancel_ill_requests
697681
- ApplicationController#after_sign_in_path_for
698-
- BookmarksController#index
699682
- CatalogController#numismatics
700683
- CatalogController#render_empty_search
701684
- Orangelight::Stackmap#stackmap
@@ -741,7 +724,6 @@ detectors:
741724
- Blacklight::Document::CiteProc#properties
742725
- Blacklight::Document::DublinCore#export_as_oai_dc_xml
743726
- Blacklight::Document::DublinCore#export_as_rdf_dc
744-
- Blacklight::Document::Email#add_holdings_text
745727
- Blacklight::Document::Email#add_online_text
746728
- Blacklight::Document::JsonLd#contributors
747729
- Blacklight::Document::JsonLd#data
@@ -760,7 +742,7 @@ detectors:
760742
- Requests::Scsb#items_by_id
761743
- Requests::Scsb#parse_scsb_response
762744
- Requests::Scsb#scsb_request
763-
- RecordMailer#email_record
745+
- Orangelight::RecordMailer#email_record
764746
- Requests::AeonUrl#ctx_with_item_info
765747
- Requests::ClancyItem#post_clancy
766748
- Requests::ClancyItem#request
@@ -824,8 +806,6 @@ detectors:
824806
exclude:
825807
- Orangelight::AdvancedSearchFormComponent#initialize_search_filter_controls
826808
- Orangelight::ConstraintsComponent#remove_guided_query_path
827-
- BookmarkButtonComponent
828-
- BookmarksController#bookmark_ids
829809
- BookmarksController#csv_bom
830810
- BookmarksController#two_values
831811
- Orangelight::Catalog#online_holding_note?
@@ -871,13 +851,10 @@ detectors:
871851
- Requests::RequestMailer#recap_marquand_in_library_email
872852
UtilityFunction:
873853
exclude:
874-
- LocationCodeFacetComponent#add_library
875-
- LocationCodeFacetComponent#library_facet_values
876854
- Orangelight::DropdownHelpTextComponent#option_text_and_value
877855
- AccountController#cancel_ill_success
878856
- AccountController#current_patron
879857
- ApplicationController#default_url_options
880-
- BookmarksController#convert_to_alma_id
881858
- BookmarksController#csv_bom
882859
- BookmarksController#two_values
883860
- Orangelight::Catalog#online_holding_note?
@@ -896,7 +873,6 @@ detectors:
896873
- ApplicationHelper#holding_location
897874
- ApplicationHelper#holding_request_block
898875
- ApplicationHelper#html_safe
899-
- ApplicationHelper#locate_url
900876
- ApplicationHelper#location_full_display
901877
- ApplicationHelper#rails_env?
902878
- ApplicationHelper#remote_storage?
@@ -932,7 +908,6 @@ detectors:
932908
- Requests::RequestMailer#annex_items
933909
- Bookmark#alma_id?
934910
- Bookmark#special_system_id?
935-
- Blacklight::Document::Email#add_holding_fields
936911
- Blacklight::Document::Email#add_online_text
937912
- Blacklight::Document::Email#add_single_valued_field
938913
- Blacklight::Document::JsonLd#check_role

app/controllers/catalog_controller.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,7 @@ class CatalogController < ApplicationController
693693

694694
config.filter_search_state_fields = true
695695
config.search_state_fields = config.search_state_fields + [
696-
:advanced_type, :clause
696+
:advanced_type, :clause, :boolean_operator1, :boolean_operator2
697697
]
698698

699699
config.index.constraints_component = Orangelight::ConstraintsComponent
@@ -709,6 +709,7 @@ def render_search_results_as_json
709709
end
710710

711711
def index
712+
solrize_boolean_params
712713
if no_search_yet?
713714
render_empty_search
714715
else
@@ -859,4 +860,8 @@ def search_algorithm_param
859860
def search_service_compatibility_wrapper
860861
@search_service_compatibility_wrapper ||= SearchServiceCompatibilityWrapper.new(search_service)
861862
end
863+
864+
def solrize_boolean_params
865+
@search_state = search_state.reset(AdvancedBooleanOperators.new(search_state.params).for_boolqparser)
866+
end
862867
end
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# frozen_string_literal: true
2+
# This class is responsible for reading some boolean operators
3+
# (AND, OR, or NOT) from some URL query params, and translating
4+
# them into the operators that Solr's BoolQParser likes (must,
5+
# must_not, or should)
6+
class AdvancedBooleanOperators
7+
def initialize(parameters)
8+
@parameters = parameters || ActionController::Parameters.new
9+
end
10+
11+
def for_boolqparser
12+
parameters.without(:boolean_operator1, :boolean_operator2).deep_merge(clauses)
13+
end
14+
15+
private
16+
17+
attr_reader :parameters
18+
19+
def first_operator
20+
parameters['boolean_operator1']&.upcase
21+
end
22+
23+
def second_operator
24+
parameters['boolean_operator2']&.upcase
25+
end
26+
27+
def clauses
28+
if first_operator.present? || second_operator.present?
29+
{ clause: { '0': { op: first_boolqparser_param }, '1': { op: middle_boolqparser_param }, '2': { op: last_boolqparser_param } } }
30+
else
31+
{}
32+
end
33+
end
34+
35+
def first_boolqparser_param
36+
return 'must' if first_operator == 'AND'
37+
return 'must' if first_operator == 'NOT' && second_operator != 'OR'
38+
'should'
39+
end
40+
41+
def middle_boolqparser_param
42+
return 'should' if second_operator == 'OR' && first_operator != 'NOT'
43+
mapping = {
44+
'OR' => 'should',
45+
'NOT' => 'must_not',
46+
'AND' => 'must'
47+
}
48+
mapping[first_operator] || 'should'
49+
end
50+
51+
def last_boolqparser_param
52+
mapping = {
53+
'OR' => 'should',
54+
'NOT' => 'must_not',
55+
'AND' => 'must'
56+
}
57+
mapping[second_operator] || 'should'
58+
end
59+
end

app/views/advanced/_guided_search_fields.html.erb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
</div>
2222
</div>
2323
<div class="search-operators" role="radiogroup" aria-label="Search operators">
24-
<label><%= radio_button_tag("clause[1][op]", "must", guided_radio(:clause_1_op, "AND"), class: 'first-and') %> AND</label>
25-
<label><%= radio_button_tag("clause[1][op]", "should", guided_radio(:clause_1_op, "OR"), class: 'first-or') %> OR</label>
26-
<label><%= radio_button_tag("clause[1][op]", "must_not", guided_radio(:clause_1_op, "NOT"), class: 'first-not') %> NOT</label>
24+
<label><%= radio_button_tag("boolean_operator1", "AND", guided_radio(:boolean_operator1, "AND"), class: 'first-and') %> AND</label>
25+
<label><%= radio_button_tag("boolean_operator1", "OR", guided_radio(:boolean_operator1, "OR"), class: 'first-or') %> OR</label>
26+
<label><%= radio_button_tag("boolean_operator1", "NOT", guided_radio(:boolean_operator1, "NOT"), class: 'first-not') %> NOT</label>
2727
</div>
2828
<div class="search-field">
2929
<label for="clause_1_field" class="sr-only">Options for advanced search - second parameter</label>
@@ -47,9 +47,9 @@
4747
</div>
4848
</div>
4949
<div class="search-operators" role="radiogroup" aria-label="Search operators">
50-
<label><%= radio_button_tag("clause[2][op]", "must", guided_radio(:clause_2_op, "AND")) %> AND</label>
51-
<label><%= radio_button_tag("clause[2][op]", "should", guided_radio(:clause_2_op, "OR")) %> OR</label>
52-
<label><%= radio_button_tag("clause[2][op]", "must_not", guided_radio(:clause_2_op, "NOT")) %> NOT</label>
50+
<label><%= radio_button_tag("boolean_operator2", "AND", guided_radio(:boolean_operator2, "AND")) %> AND</label>
51+
<label><%= radio_button_tag("boolean_operator2", "OR", guided_radio(:boolean_operator2, "OR")) %> OR</label>
52+
<label><%= radio_button_tag("boolean_operator2", "NOT", guided_radio(:boolean_operator2, "NOT")) %> NOT</label>
5353
</div>
5454
<div class="search-field">
5555
<label for="clause_2_field" class="sr-only">Options for advanced search - third parameter</label>

spec/features/advanced_searching_spec.rb

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@
123123
it 'can exclude terms from the search', js: false do
124124
# defaults to keyword
125125
fill_in(id: 'clause_0_query', with: 'gay')
126-
choose(id: 'clause_2_op_must_not')
126+
choose(id: 'boolean_operator2_NOT')
127127
# defaults to title
128128
fill_in(id: 'clause_2_query', with: 'RenoOut')
129129
click_button('advanced-search-submit')
@@ -132,10 +132,24 @@
132132
expect(page).not_to have_content('Reno Gay Press and Promotions')
133133
end
134134

135+
it 'can do a boolean OR search', js: false do
136+
# defaults to keyword
137+
fill_in(id: 'clause_0_query', with: 'gay')
138+
choose(id: 'boolean_operator1_OR')
139+
# defaults to title
140+
select('Title', from: 'clause_1_field')
141+
fill_in(id: 'clause_1_query', with: 'algebra')
142+
click_button('advanced-search-submit')
143+
expect(page.find(".page_entries").text).to eq('1 - 3 of 3')
144+
expect(page).to have_content('Seeking sanctuary')
145+
expect(page).to have_content('Reno Gay Press and Promotions')
146+
expect(page).to have_content('College algebra')
147+
end
148+
135149
it 'shows constraint-value on search results page' do
136150
# defaults to keyword
137151
fill_in(id: 'clause_0_query', with: 'gay')
138-
choose(id: 'clause_2_op_must_not')
152+
choose(id: 'boolean_operator2_NOT')
139153
# defaults to title
140154
fill_in(id: 'clause_2_query', with: 'RenoOut')
141155
click_button('advanced-search-submit')

0 commit comments

Comments
 (0)