From 63667cf68593fc97ab18562b41d41d8f62b02e9e Mon Sep 17 00:00:00 2001 From: Justin Coyne Date: Tue, 4 Nov 2025 10:14:05 -0600 Subject: [PATCH] Create a search builder for the range limit route This helps follow the pattern of having a class for each search builder rather than creating them dynamically. It may also allow Blacklight to remove the SearchBuilder#append and #except methods --- app/models/range_limit_search_builder.rb | 5 ++ lib/blacklight_range_limit.rb | 1 + .../controller_override.rb | 27 ++++--- .../range_limit_builder.rb | 79 ++++++------------- .../range_limit_search_builder_behavior.rb | 53 +++++++++++++ 5 files changed, 97 insertions(+), 68 deletions(-) create mode 100644 app/models/range_limit_search_builder.rb create mode 100644 lib/blacklight_range_limit/range_limit_search_builder_behavior.rb diff --git a/app/models/range_limit_search_builder.rb b/app/models/range_limit_search_builder.rb new file mode 100644 index 0000000..c43ab84 --- /dev/null +++ b/app/models/range_limit_search_builder.rb @@ -0,0 +1,5 @@ +# Used for building the search for the range_limit route +# You may completely override this class in the host application if you want to customize the search behavior. +class RangeLimitSearchBuilder < SearchBuilder + include BlacklightRangeLimit::RangeLimitSearchBuilderBehavior +end diff --git a/lib/blacklight_range_limit.rb b/lib/blacklight_range_limit.rb index 0189a4a..cf8265c 100644 --- a/lib/blacklight_range_limit.rb +++ b/lib/blacklight_range_limit.rb @@ -3,6 +3,7 @@ module BlacklightRangeLimit require 'blacklight_range_limit/facet_field_config_override' require 'blacklight_range_limit/range_limit_builder' require 'blacklight_range_limit/controller_override' + require 'blacklight_range_limit/range_limit_search_builder_behavior' require 'blacklight_range_limit/version' require 'blacklight_range_limit/engine' diff --git a/lib/blacklight_range_limit/controller_override.rb b/lib/blacklight_range_limit/controller_override.rb index 001a346..43c9ef4 100644 --- a/lib/blacklight_range_limit/controller_override.rb +++ b/lib/blacklight_range_limit/controller_override.rb @@ -6,7 +6,7 @@ module BlacklightRangeLimit module ControllerOverride extend ActiveSupport::Concern - RANGE_LIMIT_FIELDS = [:range_end, :range_field, :range_start].freeze + RANGE_LIMIT_FIELDS = %i[range_end range_field range_start].freeze included do # have to do this in before_action so it's done before any application level @@ -22,19 +22,14 @@ def range_limit @facet = blacklight_config.facet_fields[params[:range_field]] raise ActionController::RoutingError, 'Not Found' unless @facet&.range - # We need to swap out the add_range_limit_params search param filter, - # and instead add in our fetch_specific_range_limit filter, - # to fetch only the range limit segments for only specific - # field (with start/end params) mentioned in query params - # range_field, range_start, and range_end + @response, = range_limit_search_service.search_results - @response, _ = search_service.search_results do |search_builder| - search_builder.except(:add_range_limit_params).append(:fetch_specific_range_limit) - end - - display_facet = @response.aggregations[@facet.field] || Blacklight::Solr::Response::Facets::FacetField.new(@facet.key, [], response: @response) + display_facet = @response.aggregations[@facet.field] || Blacklight::Solr::Response::Facets::FacetField.new( + @facet.key, [], response: @response + ) - @presenter = (@facet.presenter || BlacklightRangeLimit::FacetFieldPresenter).new(@facet, display_facet, view_context) + @presenter = (@facet.presenter || BlacklightRangeLimit::FacetFieldPresenter).new(@facet, display_facet, + view_context) render BlacklightRangeLimit::RangeSegmentsComponent.new(facet_field: @presenter), layout: !request.xhr? end @@ -47,6 +42,14 @@ def _range_limit_add_search_state_fields blacklight_config.search_state_fields.concat(missing_keys) end + # @return [Blacklight::SearchService] + def range_limit_search_service + search_service_class.new(config: blacklight_config, + search_state: search_state, + search_builder_class: RangeLimitSearchBuilder, + **search_service_context) + end + class_methods do def default_range_config BlacklightRangeLimit.default_range_config diff --git a/lib/blacklight_range_limit/range_limit_builder.rb b/lib/blacklight_range_limit/range_limit_builder.rb index e13066b..0e613d8 100644 --- a/lib/blacklight_range_limit/range_limit_builder.rb +++ b/lib/blacklight_range_limit/range_limit_builder.rb @@ -15,11 +15,11 @@ def add_range_limit_params(solr_params) ranged_facet_configs = blacklight_config.facet_fields.select { |_key, config| config.range } return solr_params unless ranged_facet_configs.any? - solr_params["stats"] = "true" - solr_params["stats.field"] ||= [] + solr_params['stats'] = 'true' + solr_params['stats.field'] ||= [] ranged_facet_configs.each do |field_key, config| - solr_params["stats.field"] << config.field + solr_params['stats.field'] << config.field range_config = config.range_config next unless range_config[:chart_js] || range_config[:textual_facets] @@ -29,55 +29,20 @@ def add_range_limit_params(solr_params) range = bl_create_selected_range_value(selected_value, config) # If we have both ends of a range - add_range_segments_to_solr!(solr_params, field_key, range.begin, range.end) if range && range.count != Float::INFINITY + if range && range.count != Float::INFINITY + add_range_segments_to_solr!(solr_params, field_key, range.begin, range.end) + end end solr_params end - - # Another processing method, this one is NOT included in default processing chain, - # it is specifically swapped in *instead of* add_range_limit_params for - # certain ajax requests that only want to fetch range limit segments for - # ONE field. - # - # It turns off facetting and sets rows to 0 as well, only results for - # single specified field are needed. - # - # Specified field and parameters are specified in incoming parameters - # range_field, range_start, range_end - def fetch_specific_range_limit(solr_params) - field_key = blacklight_params[:range_field] # what field to fetch for - - unless blacklight_params[:range_start].present? && blacklight_params[:range_start].kind_of?(String) && - blacklight_params[:range_end].present? && blacklight_params[:range_end].kind_of?(String) - raise BlacklightRangeLimit::InvalidRange - end - - start = blacklight_params[:range_start].to_i - finish = blacklight_params[:range_end].to_i - - add_range_segments_to_solr!(solr_params, field_key, start, finish ) - - # Remove all field faceting for efficiency, we won't be using it. - solr_params.delete("facet.field") - solr_params.delete("facet.field".to_sym) - - # We don't need any actual rows either - solr_params[:rows] = 0 - - return solr_params - rescue BlacklightRangeLimit::InvalidRange - # This will make Rails return a 400 - raise ActionController::BadRequest, "invalid range_start (#{blacklight_params[:range_start]}) or range_end (#{blacklight_params[:range_end]})" - end - # hacky polyfill for new Blacklight behavior we need, if we don't have it yet # # https://github.com/projectblacklight/blacklight/pull/3213 # https://github.com/projectblacklight/blacklight/pull/3443 - bl_version = Gem.loaded_specs["blacklight"]&.version - if bl_version && (bl_version <= Gem::Version.new("8.6.1")) + bl_version = Gem.loaded_specs['blacklight']&.version + if bl_version && (bl_version <= Gem::Version.new('8.6.1')) def facet_value_to_fq_string(facet_field, value, use_local_params: true) facet_config = blacklight_config.facet_fields[facet_field] @@ -94,7 +59,7 @@ def facet_value_to_fq_string(facet_field, value, use_local_params: true) prefix = "{!#{local_params.join(' ')}}" unless local_params.empty? - "#{prefix}#{solr_field}:[#{value.begin || "*"} TO #{value.end || "*"}]" + "#{prefix}#{solr_field}:[#{value.begin || '*'} TO #{value.end || '*'}]" else super end @@ -108,23 +73,25 @@ def bl_create_selected_range_value(selected_value, field_config) range_config = field_config.range_config range = if selected_value.is_a? Range - selected_value - elsif range_config[:assumed_boundaries].is_a?(Range) - range_config[:assumed_boundaries] - elsif range_config[:assumed_boundaries] # Array of two things please - Range.new(*range_config[:assumed_boundaries]) - else - nil - end + selected_value + elsif range_config[:assumed_boundaries].is_a?(Range) + range_config[:assumed_boundaries] + elsif range_config[:assumed_boundaries] # Array of two things please + Range.new(*range_config[:assumed_boundaries]) + else + nil + end # clamp between config'd min and max min = range_config[:min_value] max = range_config[:max_value] - range = Range.new( - (range.begin.clamp(min, max) if range.begin), - (range.end.clamp(min, max) if range.end), - ) if range + if range + range = Range.new( + (range.begin.clamp(min, max) if range.begin), + (range.end.clamp(min, max) if range.end) + ) + end range end diff --git a/lib/blacklight_range_limit/range_limit_search_builder_behavior.rb b/lib/blacklight_range_limit/range_limit_search_builder_behavior.rb new file mode 100644 index 0000000..958ee68 --- /dev/null +++ b/lib/blacklight_range_limit/range_limit_search_builder_behavior.rb @@ -0,0 +1,53 @@ +# Used for building the search for the range_limit route +module BlacklightRangeLimit + module RangeLimitSearchBuilderBehavior + extend ActiveSupport::Concern + + included do + # We need to swap out the add_range_limit_params search param filter, + # and instead add in our fetch_specific_range_limit filter, + # to fetch only the range limit segments for only specific + # field (with start/end params) mentioned in query params + # range_field, range_start, and range_end + self.default_processor_chain += %i[fetch_specific_range_limit] + self.default_processor_chain -= %i[add_range_limit_params] + end + + # Another processing method, this one is NOT included in default processing chain, + # it is specifically swapped in *instead of* add_range_limit_params for + # certain ajax requests that only want to fetch range limit segments for + # ONE field. + # + # It turns off facetting and sets rows to 0 as well, only results for + # single specified field are needed. + # + # Specified field and parameters are specified in incoming parameters + # range_field, range_start, range_end + def fetch_specific_range_limit(solr_params) + field_key = blacklight_params[:range_field] # what field to fetch for + + unless blacklight_params[:range_start].present? && blacklight_params[:range_start].is_a?(String) && + blacklight_params[:range_end].present? && blacklight_params[:range_end].is_a?(String) + raise BlacklightRangeLimit::InvalidRange + end + + start = blacklight_params[:range_start].to_i + finish = blacklight_params[:range_end].to_i + + add_range_segments_to_solr!(solr_params, field_key, start, finish) + + # Remove all field faceting for efficiency, we won't be using it. + solr_params.delete('facet.field') + solr_params.delete('facet.field'.to_sym) + + # We don't need any actual rows either + solr_params[:rows] = 0 + + solr_params + rescue BlacklightRangeLimit::InvalidRange + # This will make Rails return a 400 + raise ActionController::BadRequest, + "invalid range_start (#{blacklight_params[:range_start]}) or range_end (#{blacklight_params[:range_end]})" + end + end +end