Skip to content

Commit 53e608a

Browse files
committed
Extract FilterQueryBuilder to its own class
1 parent 3e3e63c commit 53e608a

6 files changed

+182
-150
lines changed

lib/blacklight/search_state/pivot_filter_field.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,16 +89,16 @@ def initialize(value: nil, fq: {}, **_args) # rubocop:disable Naming/MethodParam
8989
end
9090
end
9191

92-
class QueryBuilder
92+
class QueryBuilder < Solr::AbstractFilterQueryBuilder
9393
# @return [Array] filter_query, subqueries
94-
def self.call(search_builder, filter, solr_parameters)
94+
def call(filter, solr_parameters)
9595
existing = solr_parameters['fq']&.dup || []
9696
queries = []
9797
filter.values.compact_blank.each do |value|
98-
queries << search_builder.send(:facet_value_to_fq_string, filter.pivot.first, value.value)
98+
queries << facet_value_to_fq_string(filter.pivot.first, value.value)
9999
value.fq.each do |entry|
100100
k, v = entry
101-
queries << search_builder.send(:facet_value_to_fq_string, k, v) if v
101+
queries << facet_value_to_fq_string(k, v) if v
102102
end
103103
queries.uniq!
104104
end
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# frozen_string_literal: true
2+
3+
module Blacklight::Solr
4+
class AbstractFilterQueryBuilder
5+
def initialize(blacklight_config:)
6+
@blacklight_config = blacklight_config
7+
end
8+
9+
attr_reader :blacklight_config
10+
11+
private
12+
13+
def facet_inclusive_value_to_fq_string(facet_field, values)
14+
return if values.blank?
15+
16+
return facet_value_to_fq_string(facet_field, values.first) if values.length == 1
17+
18+
facet_config = blacklight_config.facet_fields[facet_field]
19+
20+
local_params = []
21+
local_params << "tag=#{facet_config.tag}" if facet_config&.tag
22+
23+
solr_filters = values.each_with_object({}).with_index do |(v, h), index|
24+
h["f_inclusive.#{facet_field}.#{index}"] = facet_value_to_fq_string(facet_field, v, use_local_params: false)
25+
end
26+
27+
filter_query = solr_filters.keys.map do |k|
28+
"{!query v=$#{k}}"
29+
end.join(' OR ')
30+
31+
["{!lucene#{" #{local_params.join(' ')}" unless local_params.empty?}}#{filter_query}", solr_filters]
32+
end
33+
34+
##
35+
# Convert a facet/value pair into a solr fq parameter
36+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
37+
def facet_value_to_fq_string(facet_field, value, use_local_params: true)
38+
facet_config = blacklight_config.facet_fields[facet_field]
39+
40+
solr_field = facet_config.field if facet_config && !facet_config.query
41+
solr_field ||= facet_field
42+
43+
local_params = []
44+
local_params << "tag=#{facet_config.tag}" if use_local_params && facet_config&.tag
45+
46+
if facet_config&.query
47+
if facet_config.query[value]
48+
facet_config.query[value][:fq]
49+
else
50+
# exclude all documents if the custom facet key specified was not found
51+
'-*:*'
52+
end
53+
elsif value.is_a?(Range)
54+
prefix = "{!#{local_params.join(' ')}}" unless local_params.empty?
55+
start = value.begin || '*'
56+
finish = value.end || '*'
57+
"#{prefix}#{solr_field}:[#{start} TO #{finish}]"
58+
elsif value == Blacklight::SearchState::FilterField::MISSING
59+
"-#{solr_field}:[* TO *]"
60+
else
61+
"{!term f=#{solr_field}#{" #{local_params.join(' ')}" unless local_params.empty?}}#{convert_to_term_value(value)}"
62+
end
63+
end
64+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
65+
66+
def convert_to_term_value(value)
67+
case value
68+
when DateTime, Time
69+
value.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
70+
when Date
71+
value.to_time(:local).strftime("%Y-%m-%dT%H:%M:%SZ")
72+
else
73+
value.to_s
74+
end
75+
end
76+
end
77+
end
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# frozen_string_literal: true
2+
3+
module Blacklight::Solr
4+
class DefaultFilterQueryBuilder < AbstractFilterQueryBuilder
5+
def call(filter, _solr_parameters)
6+
filter_queries = []
7+
all_subqueries = {}
8+
filter.values.compact_blank.each do |value|
9+
filter_query, subqueries = if value.is_a?(Array)
10+
facet_inclusive_value_to_fq_string(filter.key, value.compact_blank)
11+
else
12+
facet_value_to_fq_string(filter.config.key, value)
13+
end
14+
filter_queries << filter_query
15+
all_subqueries.merge!(subqueries) if subqueries
16+
end
17+
[filter_queries, all_subqueries]
18+
end
19+
end
20+
end

lib/blacklight/solr/search_builder_behavior.rb

Lines changed: 11 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -128,25 +128,19 @@ def add_facet_fq_to_solr(solr_parameters)
128128
end
129129

130130
search_state.filters.each do |filter|
131-
if filter.config.filter_query_builder
132-
filter_query, subqueries = filter.config.filter_query_builder.call(self, filter, solr_parameters)
133-
134-
Array(filter_query).each do |fq|
135-
solr_parameters.append_filter_query(fq)
136-
end
137-
solr_parameters.merge!(subqueries) if subqueries
131+
filter_query_builder_class_or_proc = filter.config.filter_query_builder || DefaultFilterQueryBuilder
132+
if filter_query_builder_class_or_proc.is_a?(Class)
133+
filter_query_builder = filter_query_builder_class_or_proc.new(blacklight_config: blacklight_config)
134+
filter_query, subqueries = filter_query_builder.call(filter, solr_parameters)
138135
else
139-
filter.values.compact_blank.each do |value|
140-
filter_query, subqueries = if value.is_a?(Array)
141-
facet_inclusive_value_to_fq_string(filter.key, value.compact_blank)
142-
else
143-
facet_value_to_fq_string(filter.config.key, value)
144-
end
145-
146-
solr_parameters.append_filter_query filter_query
147-
solr_parameters.merge!(subqueries) if subqueries
148-
end
136+
# TODO: Maybe deprecate proc?
137+
filter_query, subqueries = filter_query_builder_class_or_proc.call(self, filter, solr_parameters)
138+
end
139+
140+
Array(filter_query).each do |fq|
141+
solr_parameters.append_filter_query(fq)
149142
end
143+
solr_parameters.merge!(subqueries) if subqueries
150144
end
151145
end
152146

@@ -338,70 +332,6 @@ def solr_param_quote(val, options = {})
338332

339333
private
340334

341-
##
342-
# Convert a facet/value pair into a solr fq parameter
343-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
344-
def facet_value_to_fq_string(facet_field, value, use_local_params: true)
345-
facet_config = blacklight_config.facet_fields[facet_field]
346-
347-
solr_field = facet_config.field if facet_config && !facet_config.query
348-
solr_field ||= facet_field
349-
350-
local_params = []
351-
local_params << "tag=#{facet_config.tag}" if use_local_params && facet_config&.tag
352-
353-
if facet_config&.query
354-
if facet_config.query[value]
355-
facet_config.query[value][:fq]
356-
else
357-
# exclude all documents if the custom facet key specified was not found
358-
'-*:*'
359-
end
360-
elsif value.is_a?(Range)
361-
prefix = "{!#{local_params.join(' ')}}" unless local_params.empty?
362-
start = value.begin || '*'
363-
finish = value.end || '*'
364-
"#{prefix}#{solr_field}:[#{start} TO #{finish}]"
365-
elsif value == Blacklight::SearchState::FilterField::MISSING
366-
"-#{solr_field}:[* TO *]"
367-
else
368-
"{!term f=#{solr_field}#{" #{local_params.join(' ')}" unless local_params.empty?}}#{convert_to_term_value(value)}"
369-
end
370-
end
371-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
372-
373-
def facet_inclusive_value_to_fq_string(facet_field, values)
374-
return if values.blank?
375-
376-
return facet_value_to_fq_string(facet_field, values.first) if values.length == 1
377-
378-
facet_config = blacklight_config.facet_fields[facet_field]
379-
380-
local_params = []
381-
local_params << "tag=#{facet_config.tag}" if facet_config&.tag
382-
383-
solr_filters = values.each_with_object({}).with_index do |(v, h), index|
384-
h["f_inclusive.#{facet_field}.#{index}"] = facet_value_to_fq_string(facet_field, v, use_local_params: false)
385-
end
386-
387-
filter_query = solr_filters.keys.map do |k|
388-
"{!query v=$#{k}}"
389-
end.join(' OR ')
390-
391-
["{!lucene#{" #{local_params.join(' ')}" unless local_params.empty?}}#{filter_query}", solr_filters]
392-
end
393-
394-
def convert_to_term_value(value)
395-
case value
396-
when DateTime, Time
397-
value.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
398-
when Date
399-
value.to_time(:local).strftime("%Y-%m-%dT%H:%M:%SZ")
400-
else
401-
value.to_s
402-
end
403-
end
404-
405335
##
406336
# The key to use to retrieve the grouped field to display
407337
def grouped_key_for_results
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
RSpec.describe Blacklight::Solr::DefaultFilterQueryBuilder do
2+
subject { described_class.new(blacklight_config: blacklight_config) }
3+
4+
let(:blacklight_config) { CatalogController.blacklight_config.deep_copy }
5+
6+
describe "#facet_value_to_fq_string" do
7+
it "uses the configured field name" do
8+
blacklight_config.add_facet_field :facet_key, field: "facet_name"
9+
expect(subject.send(:facet_value_to_fq_string, "facet_key", "my value")).to eq "{!term f=facet_name}my value"
10+
end
11+
12+
it "uses the raw handler for strings" do
13+
expect(subject.send(:facet_value_to_fq_string, "facet_name", "my value")).to eq "{!term f=facet_name}my value"
14+
end
15+
16+
it "passes booleans through" do
17+
expect(subject.send(:facet_value_to_fq_string, "facet_name", true)).to eq '{!term f=facet_name}true'
18+
end
19+
20+
it "passes boolean-like strings through" do
21+
expect(subject.send(:facet_value_to_fq_string, "facet_name", "true")).to eq '{!term f=facet_name}true'
22+
end
23+
24+
it "passes integers through" do
25+
expect(subject.send(:facet_value_to_fq_string, "facet_name", 1)).to eq '{!term f=facet_name}1'
26+
end
27+
28+
it "passes integer-like strings through" do
29+
expect(subject.send(:facet_value_to_fq_string, "facet_name", "1")).to eq '{!term f=facet_name}1'
30+
expect(subject.send(:facet_value_to_fq_string, "facet_name", -1)).to eq '{!term f=facet_name}-1'
31+
end
32+
33+
it "passes floats through" do
34+
expect(subject.send(:facet_value_to_fq_string, "facet_name", 1.11)).to eq '{!term f=facet_name}1.11'
35+
end
36+
37+
it "passes floats in strings through" do
38+
expect(subject.send(:facet_value_to_fq_string, "facet_name", "1.11")).to eq '{!term f=facet_name}1.11'
39+
end
40+
41+
context 'date handling' do
42+
before { allow(blacklight_config.facet_fields).to receive(:[]).with('facet_name').and_return(double(date: true, query: nil, tag: nil, field: 'facet_name')) }
43+
44+
it "passes date-type fields through" do
45+
expect(subject.send(:facet_value_to_fq_string, "facet_name", "2012-01-01")).to eq '{!term f=facet_name}2012-01-01'
46+
expect(subject.send(:facet_value_to_fq_string, "facet_name", "2003-04-09T00:00:00Z")).to eq '{!term f=facet_name}2003-04-09T00:00:00Z'
47+
end
48+
49+
it "formats Date objects correctly" do
50+
allow(blacklight_config.facet_fields).to receive(:[]).with('facet_name').and_return(double(date: nil, query: nil, tag: nil, field: 'facet_name'))
51+
d = DateTime.parse("2003-04-09T00:00:00")
52+
expect(subject.send(:facet_value_to_fq_string, "facet_name", d)).to eq '{!term f=facet_name}2003-04-09T00:00:00Z'
53+
end
54+
end
55+
56+
it "handles range requests" do
57+
expect(subject.send(:facet_value_to_fq_string, "facet_name", 1..5)).to eq "facet_name:[1 TO 5]"
58+
expect(subject.send(:facet_value_to_fq_string, "facet_name", 1..nil)).to eq "facet_name:[1 TO *]"
59+
expect(subject.send(:facet_value_to_fq_string, "facet_name", nil..5)).to eq "facet_name:[* TO 5]"
60+
expect(subject.send(:facet_value_to_fq_string, "facet_name", nil..nil)).to eq "facet_name:[* TO *]"
61+
end
62+
63+
it "adds tag local parameters" do
64+
allow(blacklight_config.facet_fields).to receive(:[]).with('facet_name').and_return(double(query: nil, tag: 'asdf', date: nil, field: 'facet_name'))
65+
66+
expect(subject.send(:facet_value_to_fq_string, "facet_name", true)).to eq "{!term f=facet_name tag=asdf}true"
67+
expect(subject.send(:facet_value_to_fq_string, "facet_name", "my value")).to eq "{!term f=facet_name tag=asdf}my value"
68+
end
69+
end
70+
end

spec/models/blacklight/solr/search_builder_behavior_spec.rb

Lines changed: 0 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -523,71 +523,6 @@
523523
end
524524
end
525525

526-
describe "#facet_value_to_fq_string" do
527-
it "uses the configured field name" do
528-
blacklight_config.add_facet_field :facet_key, field: "facet_name"
529-
expect(subject.send(:facet_value_to_fq_string, "facet_key", "my value")).to eq "{!term f=facet_name}my value"
530-
end
531-
532-
it "uses the raw handler for strings" do
533-
expect(subject.send(:facet_value_to_fq_string, "facet_name", "my value")).to eq "{!term f=facet_name}my value"
534-
end
535-
536-
it "passes booleans through" do
537-
expect(subject.send(:facet_value_to_fq_string, "facet_name", true)).to eq '{!term f=facet_name}true'
538-
end
539-
540-
it "passes boolean-like strings through" do
541-
expect(subject.send(:facet_value_to_fq_string, "facet_name", "true")).to eq '{!term f=facet_name}true'
542-
end
543-
544-
it "passes integers through" do
545-
expect(subject.send(:facet_value_to_fq_string, "facet_name", 1)).to eq '{!term f=facet_name}1'
546-
end
547-
548-
it "passes integer-like strings through" do
549-
expect(subject.send(:facet_value_to_fq_string, "facet_name", "1")).to eq '{!term f=facet_name}1'
550-
expect(subject.send(:facet_value_to_fq_string, "facet_name", -1)).to eq '{!term f=facet_name}-1'
551-
end
552-
553-
it "passes floats through" do
554-
expect(subject.send(:facet_value_to_fq_string, "facet_name", 1.11)).to eq '{!term f=facet_name}1.11'
555-
end
556-
557-
it "passes floats in strings through" do
558-
expect(subject.send(:facet_value_to_fq_string, "facet_name", "1.11")).to eq '{!term f=facet_name}1.11'
559-
end
560-
561-
context 'date handling' do
562-
before { allow(blacklight_config.facet_fields).to receive(:[]).with('facet_name').and_return(double(date: true, query: nil, tag: nil, field: 'facet_name')) }
563-
564-
it "passes date-type fields through" do
565-
expect(subject.send(:facet_value_to_fq_string, "facet_name", "2012-01-01")).to eq '{!term f=facet_name}2012-01-01'
566-
expect(subject.send(:facet_value_to_fq_string, "facet_name", "2003-04-09T00:00:00Z")).to eq '{!term f=facet_name}2003-04-09T00:00:00Z'
567-
end
568-
569-
it "formats Date objects correctly" do
570-
allow(blacklight_config.facet_fields).to receive(:[]).with('facet_name').and_return(double(date: nil, query: nil, tag: nil, field: 'facet_name'))
571-
d = DateTime.parse("2003-04-09T00:00:00")
572-
expect(subject.send(:facet_value_to_fq_string, "facet_name", d)).to eq '{!term f=facet_name}2003-04-09T00:00:00Z'
573-
end
574-
end
575-
576-
it "handles range requests" do
577-
expect(subject.send(:facet_value_to_fq_string, "facet_name", 1..5)).to eq "facet_name:[1 TO 5]"
578-
expect(subject.send(:facet_value_to_fq_string, "facet_name", 1..nil)).to eq "facet_name:[1 TO *]"
579-
expect(subject.send(:facet_value_to_fq_string, "facet_name", nil..5)).to eq "facet_name:[* TO 5]"
580-
expect(subject.send(:facet_value_to_fq_string, "facet_name", nil..nil)).to eq "facet_name:[* TO *]"
581-
end
582-
583-
it "adds tag local parameters" do
584-
allow(blacklight_config.facet_fields).to receive(:[]).with('facet_name').and_return(double(query: nil, tag: 'asdf', date: nil, field: 'facet_name'))
585-
586-
expect(subject.send(:facet_value_to_fq_string, "facet_name", true)).to eq "{!term f=facet_name tag=asdf}true"
587-
expect(subject.send(:facet_value_to_fq_string, "facet_name", "my value")).to eq "{!term f=facet_name tag=asdf}my value"
588-
end
589-
end
590-
591526
describe "#add_facet_fq_to_solr" do
592527
it "converts a String fq into an Array" do
593528
solr_parameters = { fq: 'a string' }

0 commit comments

Comments
 (0)