Module: Blacklight::SolrHelper

Included in:
AssetsController, CatalogController, CatalogHelper, FileAssetsController, FolderController, GenericContentObjectsController
Defined in:
vendor/plugins/blacklight/lib/blacklight/solr_helper.rb

Overview

SolrHelper is a controller layer mixin. It is in the controller scope: request params, session etc.

NOTE: Be careful when creating variables here as they may be overriding something that already exists. The ActionController docs: api.rubyonrails.org/classes/ActionController/Base.html

Override these methods in your own controller for customizations:

class CatalogController < ActionController::Base

  
  include Blacklight::SolrHelper
  
  def solr_search_params
    super.merge :per_page=>10
  end
  

end

Defined Under Namespace

Classes: InvalidSolrID

Constant Summary

MaxPerPage =
100

Class Method Summary (collapse)

Instance Method Summary (collapse)

Class Method Details

+ (Object) included(mod)



24
25
26
27
28
29
# File 'vendor/plugins/blacklight/lib/blacklight/solr_helper.rb', line 24

def self.included(mod)
  if mod.respond_to?(:helper_method)
    mod.helper_method(:facet_limit_hash)
    mod.helper_method(:facet_limit_for)
  end
end

Instance Method Details

- (Object) facet_limit_for(facet_field)

Look up facet limit for given facet_field. Will look at config, and if config is ‘true’ will look up from Solr @response if available. If no limit is avaialble, returns nil. Used from #solr_search_params to supply f.fieldname.facet.limit values in solr request (no @response available), and used in display (with @response available) to create a facet paginator with the right limit.



352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
# File 'vendor/plugins/blacklight/lib/blacklight/solr_helper.rb', line 352

def facet_limit_for(facet_field)
  limits_hash = facet_limit_hash
  return nil unless limits_hash
      
  limit = limits_hash[facet_field]

  if ( limit == true && @response && 
       @response["responseHeader"] && 
       @response["responseHeader"]["params"])
   limit =
     @response["responseHeader"]["params"]["f.#{facet_field}.facet.limit"] || 
     @response["responseHeader"]["params"]["facet.limit"]
     limit = (limit.to_i() -1) if limit
     limit = nil if limit == -2 # -1-1==-2, unlimited. 
  elsif limit == true
    limit = nil
  end

  return limit
end

- (Object) facet_limit_hash

Returns complete hash of key=facet_field, value=limit. Used by SolrHelper#solr_search_params to add limits to solr request for all configured facet limits.



376
377
378
# File 'vendor/plugins/blacklight/lib/blacklight/solr_helper.rb', line 376

def facet_limit_hash
  Blacklight.config[:facet][:limits]           
end

- (Object) get_facet_pagination(facet_field, extra_controller_params = {})

a solr query method used to paginate through a single facet field’s values /catalog/facet/language_facet



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'vendor/plugins/blacklight/lib/blacklight/solr_helper.rb', line 281

def get_facet_pagination(facet_field, extra_controller_params={})
  
  solr_params = solr_facet_params(facet_field, extra_controller_params)
  
  # Make the solr call
  response = Blacklight.solr.find(solr_params)

  limit =       
    if respond_to?(:facet_list_limit)
      facet_list_limit.to_s.to_i
    elsif solr_params[:"f.#{facet_field}.facet.limit"]
      solr_params[:"f.#{facet_field}.facet.limit"] - 1
    else
      nil
    end

  
  # Actually create the paginator!
  # NOTE: The sniffing of the proper sort from the solr response is not
  # currently tested for, tricky to figure out how to test, since the
  # default setup we test against doesn't use this feature. 
  return     Blacklight::Solr::FacetPaginator.new(response.facets.first.items, 
    :offset => solr_params['facet.offset'], 
    :limit => limit,
    :sort => response["responseHeader"]["params"]["f.#{facet_field}.facet.sort"] || response["responseHeader"]["params"]["facet.sort"]
  )
end

- (Object) get_opensearch_response(field = nil, extra_controller_params = {})

a solr query method does a standard search but returns a simplified object. an array is returned, the first item is the query string, the second item is an other array. This second array contains all of the field values for each of the documents… where the field is the “field” argument passed in.



337
338
339
340
341
342
# File 'vendor/plugins/blacklight/lib/blacklight/solr_helper.rb', line 337

def get_opensearch_response(field=nil, extra_controller_params={})
  solr_params = solr_opensearch_params(extra_controller_params)
  response = Blacklight.solr.find(solr_params)
  a = [solr_params[:q]]
  a << response.docs.map {|doc| doc[solr_params[:fl]].to_s }
end

- (Object) get_search_results(extra_controller_params = {})

a solr query method given a user query, return a solr response containing both result docs and facets

  • mixes in the Blacklight::Solr::SpellingSuggestions module

    • the response will have a spelling_suggestions method

Returns a two-element array (aka duple) with first the solr response object, and second an array of SolrDocuments representing the response.docs



193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'vendor/plugins/blacklight/lib/blacklight/solr_helper.rb', line 193

def get_search_results(extra_controller_params={})

  # In later versions of Rails, the #benchmark method can do timing
  # better for us. 
  bench_start = Time.now
  
    solr_response = Blacklight.solr.find(  self.solr_search_params(extra_controller_params) )

    document_list = solr_response.docs.collect {|doc| SolrDocument.new(doc)}  

    Rails.logger.debug("Solr fetch: #{self.class}#get_search_results (#{'%.1f' % ((Time.now.to_f - bench_start.to_f)*1000)}ms)")
  
  return [solr_response, document_list]
end

- (Object) get_single_doc_via_search(extra_controller_params = {})

a solr query method this is used when selecting a search result: we have a query and a position in the search results and possibly some facets



312
313
314
315
316
317
318
# File 'vendor/plugins/blacklight/lib/blacklight/solr_helper.rb', line 312

def get_single_doc_via_search(extra_controller_params={})
  solr_params = solr_search_params(extra_controller_params)
  solr_params[:per_page] = 1
  solr_params[:rows] = 1
  solr_params[:fl] = '*'
  Blacklight.solr.find(solr_params).docs.first
end

- (Object) get_solr_response_for_doc_id(id = nil, extra_controller_params = {})

a solr query method retrieve a solr document, given the doc id TODO: shouldn’t hardcode id field; should be setable to unique_key field in schema.xml

Raises:



223
224
225
226
227
228
# File 'vendor/plugins/blacklight/lib/blacklight/solr_helper.rb', line 223

def get_solr_response_for_doc_id(id=nil, extra_controller_params={})
  solr_response = Blacklight.solr.find solr_doc_params(id, extra_controller_params)
  raise InvalidSolrID.new if solr_response.docs.empty?
  document = SolrDocument.new(solr_response.docs.first)
  [solr_response, document]
end

- (Object) get_solr_response_for_field_values(field, values, extra_controller_params = {})

given a field name and array of values, get the matching SOLR documents



231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'vendor/plugins/blacklight/lib/blacklight/solr_helper.rb', line 231

def get_solr_response_for_field_values(field, values, extra_controller_params={})
  value_str = "(\"" + values.to_a.join("\" OR \"") + "\")"
  solr_params = {
    :qt => "standard",   # need boolean for OR
    :q => "#{field}:#{value_str}",
    'fl' => "*",
    'facet' => 'false',
    'spellcheck' => 'false'
  }
  solr_response = Blacklight.solr.find( self.solr_search_params(solr_params.merge(extra_controller_params)) )
  document_list = solr_response.docs.collect{|doc| SolrDocument.new(doc) }
  [solr_response,document_list]
end

- (Object) max_per_page



380
381
382
# File 'vendor/plugins/blacklight/lib/blacklight/solr_helper.rb', line 380

def max_per_page
  MaxPerPage
end

- (Object) solr_doc_params(id = nil, extra_controller_params = {})

returns a params hash for finding a single solr document (CatalogController #show action) If the id arg is nil, then the value is fetched from params[:id] This method is primary called by the get_solr_response_for_doc_id method.



211
212
213
214
215
216
217
218
# File 'vendor/plugins/blacklight/lib/blacklight/solr_helper.rb', line 211

def solr_doc_params(id=nil, extra_controller_params={})
  id ||= params[:id]
  # just to be consistent with the other solr param methods:
  {
    :qt => :document,
    :id => id
  }.deep_merge(extra_controller_params.symbolize_keys)
end

- (Object) solr_facet_params(facet_field, extra_controller_params = {})

returns a params hash for a single facet field solr query. used primary by the get_facet_pagination method. Looks up Facet Paginator request params from current request params to figure out sort and offset. Default limit for facet list can be specified by defining a controller method facet_list_limit, otherwise 20.



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'vendor/plugins/blacklight/lib/blacklight/solr_helper.rb', line 251

def solr_facet_params(facet_field, extra_controller_params={})
  input = params.deep_merge(extra_controller_params)

  # First start with a standard solr search params calculations,
  # for any search context in our request params. 
  solr_params = solr_search_params(extra_controller_params)
  
  # Now override with our specific things for fetching facet values
  solr_params[:"facet.field"] = facet_field

  # Need to set as f.facet_field.facet.limit to make sure we
  # override any field-specific default in the solr request handler. 
  solr_params[:"f.#{facet_field}.facet.limit"] = 
    if solr_params["facet.limit"] 
      solr_params["facet.limit"] + 1
    elsif respond_to?(:facet_list_limit)
      facet_list_limit.to_s.to_i + 1
    else
      20 + 1
    end
  solr_params['facet.offset'] = input[  Blacklight::Solr::FacetPaginator.request_keys[:offset]  ].to_i # will default to 0 if nil
  solr_params['facet.sort'] = input[  Blacklight::Solr::FacetPaginator.request_keys[:sort] ]     
  solr_params[:rows] = 0

  return solr_params
end

- (Object) solr_opensearch_params(field, extra_controller_params = {})

returns a solr params hash if field is nil, the value is fetched from Blacklight.config[:index][:show_link] the :fl (solr param) is set to the “field” value. per_page is set to 10



324
325
326
327
328
329
# File 'vendor/plugins/blacklight/lib/blacklight/solr_helper.rb', line 324

def solr_opensearch_params(field, extra_controller_params={})
  solr_params = solr_search_params(extra_controller_params)
  solr_params[:per_page] = 10
  solr_params[:fl] = Blacklight.config[:index][:show_link]
  solr_params
end

- (Object) solr_param_quote(val, options = {})

A helper method used for generating solr LocalParams, put quotes around the term unless it’s a bare-word. Escape internal quotes if needed.



33
34
35
36
37
38
39
40
41
42
# File 'vendor/plugins/blacklight/lib/blacklight/solr_helper.rb', line 33

def solr_param_quote(val, options = {})
  options[:quote] ||= '"'
  unless val =~ /^[a-zA-Z$_\-\^]+$/
    val = options[:quote] +
      # Yes, we need crazy escaping here, to deal with regexp esc too!
      val.gsub("'", "\\\\\'").gsub('"', "\\\\\"") + 
      options[:quote]
  end
  return val
end

- (Object) solr_search_params(extra_controller_params = {})

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.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'vendor/plugins/blacklight/lib/blacklight/solr_helper.rb', line 59

def solr_search_params(extra_controller_params={})
  solr_parameters = {}
  

  # Order of precedence for all the places solr params can come from,
  # start lowest, and keep over-riding with higher. 
  ####
  # Start with general defaults from BL config. Need to use custom
  # merge to dup values, to avoid later mutating the original by mistake.
  if Blacklight.config[:default_solr_params]
    Blacklight.config[:default_solr_params].each_pair do |key, value|
      solr_parameters[key] = case value
                               when Hash then value.dup
                               when Array then value.dup
                               else value
                             end
    end
  end
  
  
  
  ###
  # Merge in search field configured values, if present, over-writing general
  # defaults
  ###
  search_field_def = Blacklight.search_field_def_for_key(params[:search_field] || extra_controller_params[:search_field])
  
  solr_parameters[:qt] = search_field_def[:qt] if search_field_def
  
  if ( search_field_def && search_field_def[:solr_parameters])
    solr_parameters.merge!( search_field_def[:solr_parameters])
  end

  
  ###
  # Merge in certain values from HTTP query itelf
  ###
  # Omit empty strings and nil values. 
  [:facets, :f, :page, :sort, :per_page].each do |key|
    solr_parameters[key] = params[key] unless params[key].blank?      
  end
  # :q is meaningful as an empty string, should be used unless nil!
  [:q].each do |key|
    solr_parameters[key] = params[key] if params[key]
  end
  # pass through any facet fields from request params["facet.field"] to
  # solr params. Used by Stanford for it's "faux hierarchical facets".
  if params.has_key?("facet.field")
    solr_parameters[:"facet.field"] ||= []
    solr_parameters[:"facet.field"].concat( [params["facet.field"]].flatten ).uniq!
  end
    
  
      
  # qt is handled different for legacy reasons; qt in HTTP param can not
  # over-ride qt from search_field_def defaults, it's only used if there
  # was no qt from search_field_def_defaults
  unless params[:qt].blank? || ( search_field_def && search_field_def[:qt])
    solr_parameters[:qt] = params[:qt]
  end
  
  ###
  # Merge in any values from extra_params argument. It doesn't seem like
  # we should have to take a slice of just certain keys, but legacy code
  # seems to put arguments in here that aren't really expected to turn
  # into solr params. 
  ###
  solr_parameters.deep_merge!(extra_controller_params.slice(:qt, :q, :facets,  :page, :per_page, :phrase_filters, :f, :fq, :fl, :sort, :qf, :df ).symbolize_keys   )




  
  ###
  # Defaults for otherwise blank values and normalization. 
  ###
  
  # TODO: Change calling code to expect this as a symbol instead of
  # a string, for consistency? :'spellcheck.q' is a symbol. Right now
  # callers assume a string. 
  solr_parameters["spellcheck.q"] = solr_parameters[:q] unless solr_parameters["spellcheck.q"]

  # And fix the 'facets' parameter to be the way the solr expects it.
  solr_parameters[:facets]= {:fields => solr_parameters[:facets]} if solr_parameters[:facets]
  
  # :fq, map from :f. 
  if ( solr_parameters[:f])
    f_request_params = solr_parameters.delete(:f)
    solr_parameters[:fq] ||= []
    f_request_params.each_pair do |facet_field, value_list|
      value_list.each do |value|
      solr_parameters[:fq] << "{!raw f=#{facet_field}}#{value}"
      end              
    end      
  end

  # Facet 'more' limits. Add +1 to any configured facets limits,
  facet_limit_hash.each_key do |field_name|
    next if field_name.nil? # skip the 'default' key
    next unless (limit = facet_limit_for(field_name))

    solr_parameters[:"f.#{field_name}.facet.limit"] = (limit + 1)
  end

  ##
  # Merge in search-field-specified LocalParams into q param in
  # solr LocalParams syntax
  ##
  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}} #{solr_parameters[:q]}"
  end
  
  
  ###
  # Sanity/requirements checks.
  ###
  
  # limit to MaxPerPage (100). Tests want this to be a string not an integer,
  # not sure why. 
  solr_parameters[:per_page] = solr_parameters[:per_page].to_i > self.max_per_page ? self.max_per_page.to_s : solr_parameters[:per_page]

  return solr_parameters
  
end