Skip to content

Extending or Modifying Blacklight Search Behavior

cbeer edited this page Apr 12, 2011 · 30 revisions

Extending or Modifying Blacklight Search Behavior

Solr parameters used by for a given request can come from several different places.

  • Solr request handler: solrconfig.xml in your solr config
  • Application logic: logic in the BL rails app itself

Solr request handler

The Solr Request Handlers may be configured in the solrconfig.xml and are baked into the request handler itself. Depending on how you have blacklight configured, your app may be using the same Solr request handler for all searches, or may be using different request handlers for different "search fields".

The request handler is often set up with default parameters:

  <requestHandler name="standard" class="solr.SearchHandler" >
     <lst name="defaults">
       <str name="echoParams">explicit</str>
       <str name="rows">10</str> 
       <str name="fl">*</str>   
       <str name="facet">true</str>
       <str name="facet.mincount">1</str>
       <str name="facet.limit">30</str> 
       <str name="facet.field">access_facet</str>
       <str name="facet.field">author_person_facet</str>
       <str name="facet.field">author_other_facet</str>
       <str name="facet.field">building_facet</str>
       <str name="facet.field">callnum_1_facet</str>
       <str name="facet.field">era_facet</str>
       <str name="facet.field">format</str>
       <str name="facet.field">geographic_facet</str>
       <str name="facet.field">language</str>
       <str name="facet.field">pub_date_group_facet</str>
       <str name="facet.field">topic_facet</str>
     </lst>
  </requestHandler>

Configuration

The default application logic (explained below) looks in configuration for things like the name of a the solr request handler to use, and default request parameters to send on every solr search request (or with every request from a certain blacklight search type/field). An example getting started configuration is generally installed into your app when you install Blacklight, at config/initializers/blacklight_config.rb. Or see the current example in blacklight source: [https://github.com/projectblacklight/blacklight/blob/master/config/initializers/blacklight_config.rb]

Nothing special about the filename 'blacklight_config.rb', anything in a Rails app at config/initializers/*.rb gets run on application startup, what matters it the code.

Application logic

The logic Blacklight uses to determine Solr paramters for a given application request is in the #solr_search_params method in the SolrHelper module. [[CatalogController|https://github.com/projectblacklight/blacklight/blob/master/app/controllers/catalog_controller.rb]] extends [[SolrHelper|https://github.com/projectblacklight/blacklight/blob/master/lib/blacklight/solr_helper.rb]], so the SolrHelper method becomes available in the CatalogController (and sometimes other controllers, if they extend SolrHelper too).

The [[#solr_search_params|https://github.com/projectblacklight/blacklight/blob/master/lib/blacklight/solr_helper.rb#L59]] method produces a Hash of key/value pairs, that will be sent to an RSolr object, which will convert the hash into query parameters for a Solr request. One confusing thing is that RSolr and RSolr-ext provide their own mappings from certain custom terms to Solr-recognized request parameters. For instance, a :per_page key in that hash will get mapped to the Solr &rows parameter -- but a :rows key will also. We Blacklight developers have found that using these special RSolr "aliases" leads to confusion, as well as confusing bugs (if both :per_page and :rows are set in the hash, what happens can be hard to predict). So we try to avoid using the special RSolr aliases, and just use ordinary Solr parameters in our hashes. But you may encounter older code that uses the RSolr aliases.

There can be similar confusing behavior or bugs if one piece of code adds a key to a hash using a :symbol, but another piece of code looks for and/or adds that same key to hash using "string" instead. RSolr accepts either one, but if both are present RSolr behavior can be unexpected. And even before it gets to RSolr, you may have code that thinks it replaced a key but did not becuase it was using the wrong form. Blacklight developers have agreed to try and always use :symbol keys in hashes meant for Solr parameters, to try and avoid these problems. Longer term, we could probably make some code changes to make this kind of error less likely.

The #solr_search_params logic is meant to handle very common cases and patterns based on simple configuration options, usually set in the config/blacklight_config.rb file.

  • [[Blacklight.config[:default_solr_params]|https://github.com/projectblacklight/blacklight/blob/master/config/initializers/blacklight_config.rb#L44]]
    • Default params sent to solr with every search, including the default :qt param, which determines which Solr request handler will be targetted. Some people like to use solrconfig.xml request handler defaults exclusively, and include only a :qt here; others find it more convenient to specify some defaults at the application level here.
  • [[Blacklight.config[:search_fields]|https://github.com/projectblacklight/blacklight/blob/master/config/initializers/blacklight_config.rb#L194]]
    • An array of hashes, each of which defines a 'search field' which will be presented in a select menu in the BL user interface search box. These 'search fields' don't neccesarily correspond 1-to-1 with Solr fields. What they instead correspond to is Solr parameter over-rides that will be used for this BL UI search field. Those over-rides are present here.
    • You could simply chose a different :qt Solr request handler for each search field, which has it's own default parameters in the sorlconfig.xml. This is what we started out doing with Solr, but found it led to much duplication of information in solrconfig.xml.
    • You can continue using the same Solr request handler, but simply specify different parameters which will be included in the http query string sent to Solr here, with the :solr_parameters key. This works fine, but some people don't like how it makes your Solr requests much longer/more complex in the Solr logs; and/or they prefer to control this Solr side instead of Application side.
    • For the best of both worlds, although it's a bit confusing at first, you can use the :solr_local_parameters key to have parameters supplied to Solr using the Solr LocalParams syntax, which means you can use "parameter dereferencing" with dollar-sign-prefixed references to variables defined in solrconfig.xml request handler. This is what the current example BL setup does.

So the default implementation of #solr_search_params takes these configured parameters, combines them with certain query parameters from the current users HTTP request to the BL app, and prepares a Hash of parameters that will be sent to solr. For common use patterns, this is all you need.

But sometimes you want to add some custom logic to #solr_search_params to come up with the solr params Hash in different ways. Typically to support a new UI feature of some kind, either in your local app or in a Blacklight add-on plugin you are developing.

Extending Blacklight::SolrHelper#solr_search_params

To add new search behaviors (e.g. authorization controls, pre-parsing query strings, etc), the easiest route is to provide an alternative #solr_search_params function to handle the parameter mapping.

The first step is to extend the Blacklight CatalogController within the application by adding ./app/controllers/catalog_controller.rb, which may look something like this:

require_dependency 'vendor/plugins/blacklight/app/controllers/catalog_controller.rb'
class CatalogController < ApplicationController 
end

The basic approach

Within the controller itself, you can provide an alternative #solr_search_params method:

require_dependency 'vendor/plugins/blacklight/app/controllers/catalog_controller.rb'
class CatalogController < ApplicationController 
  def solr_search_params(extra_controller_params={})
    solr_parameters = super(extra_controller_params)
    # add custom logic here
    # solr_parameters[:something] = "something" if something
    # solr_parameters.delete(:some_key) if something_else
    return solr_parameters
  end
end

Note that it's important to call 'super' both to start with the default logic, which you probably don't want to disable -- but also to avoid accidentally disabling any logic added by other features or add-ons that may be installed.

The better approach

In a larger application, it may be a good idea to move the custom search logic into a module that can be shared among multiple controllers. Here is a simple authorization filter:

./app/controllers/catalog_controller.rb

require_dependency 'vendor/plugins/blacklight/app/controllers/catalog_controller.rb'
class CatalogController < ApplicationController 
  include MyApplication::SolrHelper::Authz
end

./lib/my_application/solr_helper/authz.rb

module MyApplication::SolrHelper::Authz
  def solr_search_params(extra_controller_params)
    solr_params = super(extra_controller_params)
    solr_params.merge(authorization_params)
  end

  def authorization_params  
    if current_user
      return {:phrase_filters => {:user_id_i => current_user.id } } 
    else
      return {:phrase_filters => {:public_b => 1 } }
    end
  end
end

Yet another approach

The behavior above counts on "Rails Engines" loading the original CatalogController from the BL plugin, as well as loading your local CatalogController, and merging them together. It also counts on them being loaded in the right order. This sometimes works kind of weirdly, especially with Rails "rapid development " mode where classes are reloaded on any request. It's also not entirely clear how this will work in Rails 3. Here's another way to add on a module to a class defined somewhere else.

Any .rb file in config/initializers will get executed on application start up. So in any file in there, add this:

CatalogController.class_eval do 
   include  MyApplication::SolrHelper::Authz # for instance
end

Except, oops, that too will have problems with Rails development-mode class reloading. So somewhat more complicated attempt at a general purpose solution that will work making as few assumptions as possible about the current class reloading behavior:

ActionController::Dispatcher.to_prepare do 
   unless CatalogController.kind_of?(  MyApplication::SolrHelper::Authz (
     CatalogController.class_eval do 
        include  MyApplication::SolrHelper::Authz # for instance
     end
   end
end

Common Pitfalls

When extending the Blacklight CatalogController or ApplicationHelper, as described in the above example, it's important to pull in the existing code using 'require_dependency' rather than simply 'require' which will only allow for adding new methods; attempts to override existing methods will fail.

Other examples

In addition to providing this behavior locally, some Blacklight plugins also extend the #solr_search_params:

Clone this wiki locally