Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Extract methods related to request building into its own module
- Loading branch information
Showing
3 changed files
with
291 additions
and
278 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,288 @@ | ||
module Blacklight | ||
## | ||
# This module contains methods that are specified by SolrHelper.solr_search_params_logic | ||
# They transform user parameters into parameters that are sent as a request to Solr when | ||
# RequestBuilders#solr_search_params is called. | ||
# | ||
module RequestBuilders | ||
extend ActiveSupport::Concern | ||
|
||
included do | ||
# We want to install a class-level place to keep | ||
# solr_search_params_logic method names. Compare to before_filter, | ||
# similar design. Since we're a module, we have to add it in here. | ||
# There are too many different semantic choices in ruby 'class variables', | ||
# we choose this one for now, supplied by Rails. | ||
class_attribute :solr_search_params_logic | ||
|
||
# Set defaults. Each symbol identifies a _method_ that must be in | ||
# this class, taking two parameters (solr_parameters, user_parameters) | ||
# Can be changed in local apps or by plugins, eg: | ||
# CatalogController.include ModuleDefiningNewMethod | ||
# CatalogController.solr_search_params_logic += [:new_method] | ||
# CatalogController.solr_search_params_logic.delete(:we_dont_want) | ||
self.solr_search_params_logic = [:default_solr_parameters , :add_query_to_solr, :add_facet_fq_to_solr, :add_facetting_to_solr, :add_solr_fields_to_query, :add_paging_to_solr, :add_sorting_to_solr, :add_group_config_to_solr ] | ||
end | ||
|
||
# @returns a params hash for searching solr. | ||
# The CatalogController #index action uses this. | ||
# Solr parameters can come from a number of places. From lowest | ||
# precedence to highest: | ||
# 1. General defaults in blacklight config (are trumped by) | ||
# 2. defaults for the particular search field identified by params[:search_field] (are trumped by) | ||
# 3. certain parameters directly on input HTTP query params | ||
# * not just any parameter is grabbed willy nilly, only certain ones are allowed by HTTP input) | ||
# * for legacy reasons, qt in http query does not over-ride qt in search field definition default. | ||
# 4. extra parameters passed in as argument. | ||
# | ||
# spellcheck.q will be supplied with the [:q] value unless specifically | ||
# specified otherwise. | ||
# | ||
# Incoming parameter :f is mapped to :fq solr parameter. | ||
def solr_search_params(user_params = params || {}) | ||
Blacklight::Solr::Request.new.tap do |solr_parameters| | ||
solr_search_params_logic.each do |method_name| | ||
send(method_name, solr_parameters, user_params) | ||
end | ||
end | ||
end | ||
|
||
#### | ||
# Start with general defaults from BL config. Need to use custom | ||
# merge to dup values, to avoid later mutating the original by mistake. | ||
def default_solr_parameters(solr_parameters, user_params) | ||
blacklight_config.default_solr_params.each do |key, value| | ||
solr_parameters[key] = value.dup rescue value | ||
end | ||
end | ||
|
||
## | ||
# Take the user-entered query, and put it in the solr params, | ||
# including config's "search field" params for current search field. | ||
# also include setting spellcheck.q. | ||
def add_query_to_solr(solr_parameters, user_parameters) | ||
### | ||
# Merge in search field configured values, if present, over-writing general | ||
# defaults | ||
### | ||
# legacy behavior of user param :qt is passed through, but over-ridden | ||
# by actual search field config if present. We might want to remove | ||
# this legacy behavior at some point. It does not seem to be currently | ||
# rspec'd. | ||
solr_parameters[:qt] = user_parameters[:qt] if user_parameters[:qt] | ||
|
||
search_field_def = search_field_def_for_key(user_parameters[:search_field]) | ||
if (search_field_def) | ||
solr_parameters[:qt] = search_field_def.qt | ||
solr_parameters.merge!( search_field_def.solr_parameters) if search_field_def.solr_parameters | ||
end | ||
|
||
## | ||
# Create Solr 'q' including the user-entered q, prefixed by any | ||
# solr LocalParams in config, using solr LocalParams syntax. | ||
# http://wiki.apache.org/solr/LocalParams | ||
## | ||
if (search_field_def && hash = search_field_def.solr_local_parameters) | ||
local_params = hash.collect do |key, val| | ||
key.to_s + "=" + solr_param_quote(val, :quote => "'") | ||
end.join(" ") | ||
solr_parameters[:q] = "{!#{local_params}}#{user_parameters[:q]}" | ||
else | ||
solr_parameters[:q] = user_parameters[:q] if user_parameters[:q] | ||
end | ||
|
||
|
||
## | ||
# Set Solr spellcheck.q to be original user-entered query, without | ||
# our local params, otherwise it'll try and spellcheck the local | ||
# params! Unless spellcheck.q has already been set by someone, | ||
# respect that. | ||
# | ||
# TODO: Change calling code to expect this as a symbol instead of | ||
# a string, for consistency? :'spellcheck.q' is a symbol. Right now | ||
# rspec tests for a string, and can't tell if other code may | ||
# insist on a string. | ||
solr_parameters["spellcheck.q"] = user_parameters[:q] unless solr_parameters["spellcheck.q"] | ||
end | ||
|
||
## | ||
# Add any existing facet limits, stored in app-level HTTP query | ||
# as :f, to solr as appropriate :fq query. | ||
def add_facet_fq_to_solr(solr_parameters, user_params) | ||
|
||
# convert a String value into an Array | ||
if solr_parameters[:fq].is_a? String | ||
solr_parameters[:fq] = [solr_parameters[:fq]] | ||
end | ||
|
||
# :fq, map from :f. | ||
if ( user_params[:f]) | ||
f_request_params = user_params[:f] | ||
|
||
f_request_params.each_pair do |facet_field, value_list| | ||
Array(value_list).each do |value| | ||
solr_parameters.append_filter_query facet_value_to_fq_string(facet_field, value) | ||
end | ||
end | ||
end | ||
end | ||
|
||
## | ||
# Add appropriate Solr facetting directives in, including | ||
# taking account of our facet paging/'more'. This is not | ||
# about solr 'fq', this is about solr facet.* params. | ||
def add_facetting_to_solr(solr_parameters, user_params) | ||
# While not used by BL core behavior, legacy behavior seemed to be | ||
# to accept incoming params as "facet.field" or "facets", and add them | ||
# on to any existing facet.field sent to Solr. Legacy behavior seemed | ||
# to be accepting these incoming params as arrays (in Rails URL with [] | ||
# on end), or single values. At least one of these is used by | ||
# Stanford for "faux hieararchial facets". | ||
if user_params.has_key?("facet.field") || user_params.has_key?("facets") | ||
solr_parameters[:"facet.field"].concat( [user_params["facet.field"], user_params["facets"]].flatten.compact ).uniq! | ||
end | ||
|
||
blacklight_config.facet_fields.select { |field_name,facet| | ||
facet.include_in_request || (facet.include_in_request.nil? && blacklight_config.add_facet_fields_to_solr_request) | ||
}.each do |field_name, facet| | ||
solr_parameters[:facet] ||= true | ||
|
||
case | ||
when facet.pivot | ||
solr_parameters.append_facet_pivot with_ex_local_param(facet.ex, facet.pivot.join(",")) | ||
when facet.query | ||
solr_parameters.append_facet_query facet.query.map { |k, x| with_ex_local_param(facet.ex, x[:fq]) } | ||
else | ||
solr_parameters.append_facet_fields with_ex_local_param(facet.ex, facet.field) | ||
end | ||
|
||
if facet.sort | ||
solr_parameters[:"f.#{facet.field}.facet.sort"] = facet.sort | ||
end | ||
|
||
if facet.solr_params | ||
facet.solr_params.each do |k, v| | ||
solr_parameters[:"f.#{facet.field}.#{k}"] = v | ||
end | ||
end | ||
|
||
# Support facet paging and 'more' | ||
# links, by sending a facet.limit one more than what we | ||
# want to page at, according to configured facet limits. | ||
solr_parameters[:"f.#{facet.field}.facet.limit"] = (facet_limit_for(field_name) + 1) if facet_limit_for(field_name) | ||
end | ||
end | ||
|
||
def add_solr_fields_to_query solr_parameters, user_parameters | ||
blacklight_config.show_fields.select(&method(:should_add_to_solr)).each do |field_name, field| | ||
if field.solr_params | ||
field.solr_params.each do |k, v| | ||
solr_parameters[:"f.#{field.field}.#{k}"] = v | ||
end | ||
end | ||
end | ||
|
||
blacklight_config.index_fields.select(&method(:should_add_to_solr)).each do |field_name, field| | ||
if field.highlight | ||
solr_parameters[:hl] = true | ||
solr_parameters.append_highlight_field field.field | ||
end | ||
|
||
if field.solr_params | ||
field.solr_params.each do |k, v| | ||
solr_parameters[:"f.#{field.field}.#{k}"] = v | ||
end | ||
end | ||
end | ||
end | ||
|
||
### | ||
# copy paging params from BL app over to solr, changing | ||
# app level per_page and page to Solr rows and start. | ||
def add_paging_to_solr(solr_params, user_params) | ||
|
||
# Now any over-rides from current URL? | ||
solr_params[:rows] = user_params[:rows].to_i unless user_params[:rows].blank? | ||
solr_params[:rows] = user_params[:per_page].to_i unless user_params[:per_page].blank? | ||
|
||
# Do we need to translate :page to Solr :start? | ||
unless user_params[:page].blank? | ||
# already set solr_params["rows"] might not be the one we just set, | ||
# could have been from app defaults too. But we need one. | ||
# raising is consistent with prior RSolr magic keys behavior. | ||
# We could change this to default to 10, or to raise on startup | ||
# from config instead of at runtime. | ||
if solr_params[:rows].blank? | ||
raise Exception.new("To use pagination when no :per_page is supplied in the URL, :rows must be configured in blacklight_config default_solr_params") | ||
end | ||
solr_params[:start] = solr_params[:rows].to_i * (user_params[:page].to_i - 1) | ||
solr_params[:start] = 0 if solr_params[:start].to_i < 0 | ||
end | ||
|
||
solr_params[:rows] ||= blacklight_config.per_page.first unless blacklight_config.per_page.blank? | ||
|
||
solr_params[:rows] = blacklight_config.max_per_page if solr_params[:rows].to_i > blacklight_config.max_per_page | ||
end | ||
|
||
### | ||
# copy sorting params from BL app over to solr | ||
def add_sorting_to_solr(solr_parameters, user_params) | ||
if user_params[:sort].blank? and sort_field = blacklight_config.default_sort_field | ||
# no sort param provided, use default | ||
solr_parameters[:sort] = sort_field.sort unless sort_field.sort.blank? | ||
elsif sort_field = blacklight_config.sort_fields[user_params[:sort]] | ||
# check for sort field key | ||
solr_parameters[:sort] = sort_field.sort unless sort_field.sort.blank? | ||
else | ||
# just pass the key through | ||
solr_parameters[:sort] = user_params[:sort] | ||
end | ||
end | ||
|
||
# Remove the group parameter if we've faceted on the group field (e.g. for the full results for a group) | ||
def add_group_config_to_solr solr_parameters, user_parameters | ||
if user_parameters[:f] and user_parameters[:f][grouped_key_for_results] | ||
solr_parameters[:group] = false | ||
end | ||
end | ||
|
||
def with_ex_local_param(ex, value) | ||
if ex | ||
"{!ex=#{ex}}#{value}" | ||
else | ||
value | ||
end | ||
end | ||
|
||
private | ||
|
||
## | ||
# Convert a facet/value pair into a solr fq parameter | ||
def facet_value_to_fq_string(facet_field, value) | ||
facet_config = blacklight_config.facet_fields[facet_field] | ||
|
||
local_params = [] | ||
local_params << "tag=#{facet_config.tag}" if facet_config and facet_config.tag | ||
|
||
prefix = "" | ||
prefix = "{!#{local_params.join(" ")}}" unless local_params.empty? | ||
|
||
fq = case | ||
when (facet_config and facet_config.query) | ||
facet_config.query[value][:fq] | ||
when (facet_config and facet_config.date), | ||
(value.is_a?(TrueClass) or value.is_a?(FalseClass) or value == 'true' or value == 'false'), | ||
(value.is_a?(Integer) or (value.to_i.to_s == value if value.respond_to? :to_i)), | ||
(value.is_a?(Float) or (value.to_f.to_s == value if value.respond_to? :to_f)) | ||
(value.is_a?(DateTime) or value.is_a?(Date) or value.is_a?(Time)) | ||
"#{prefix}#{facet_field}:#{value}" | ||
when value.is_a?(Range) | ||
"#{prefix}#{facet_field}:[#{value.first} TO #{value.last}]" | ||
else | ||
"{!raw f=#{facet_field}#{(" " + local_params.join(" ")) unless local_params.empty?}}#{value}" | ||
end | ||
|
||
|
||
end | ||
end | ||
end |
Oops, something went wrong.