Skip to content

Commit

Permalink
Add Blacklight::SolrResponse#aggregations to abstract solr's various …
Browse files Browse the repository at this point in the history
…facet

flavors
  • Loading branch information
cbeer committed Mar 21, 2015
1 parent 8ef2355 commit b5da6f2
Show file tree
Hide file tree
Showing 15 changed files with 271 additions and 209 deletions.
6 changes: 2 additions & 4 deletions app/helpers/blacklight/catalog_helper_behavior.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@ def page_entries_info(collection, options = {})
entry_name = if options[:entry_name]
options[:entry_name]
elsif collection.respond_to? :model # DataMapper
collection.model.model_name.human.downcase
collection.model.model_name.human.downcase
elsif collection.respond_to? :model_name and !collection.model_name.nil? # AR, Blacklight::PaginationMethods
collection.model_name.human.downcase
elsif collection.is_a?(::Kaminari::PaginatableArray)
'entry'
collection.model_name.human.downcase
else
t('blacklight.entry_name.default')
end
Expand Down
2 changes: 1 addition & 1 deletion app/helpers/blacklight/url_helper_behavior.rb
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ def add_facet_params(field, item, source_params = params)
p[:f][url_field].push(value)

if item and item.respond_to?(:fq) and item.fq
item.fq.each do |f,v|
Array(item.fq).each do |f,v|
p = add_facet_params(f, v, p)
end
end
Expand Down
2 changes: 1 addition & 1 deletion blacklight.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Gem::Specification.new do |s|

s.add_dependency "rails", ">= 3.2.6", "< 5"
s.add_dependency "nokogiri", "~>1.6" # XML Parser
s.add_dependency "kaminari", "~> 0.13" # the pagination (page 1,2,3, etc..) of our search results
s.add_dependency "kaminari", "~> 0.15" # the pagination (page 1,2,3, etc..) of our search results
s.add_dependency "rsolr", "~> 1.0.11" # Library for interacting with rSolr.
s.add_dependency "bootstrap-sass", "~> 3.2"
s.add_dependency "deprecation"
Expand Down
1 change: 1 addition & 0 deletions config/locales/blacklight.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ en:
more_html: 'more <span class="sr-only">%{field_name}</span> »'
selected:
remove: '[remove]'
missing: "[Missing]"
group:
more: 'more »'
filters:
Expand Down
60 changes: 6 additions & 54 deletions lib/blacklight/facet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,61 +32,13 @@ def facet_configuration_for_field(field)
# Get a FacetField object from the @response
def facet_by_field_name field_or_field_name
case field_or_field_name
when String, Symbol
facet_field = facet_configuration_for_field(field_or_field_name)
extract_facet_by_field(facet_field)
when Blacklight::Configuration::FacetField
extract_facet_by_field(field_or_field_name)
else
field_or_field_name
end
end

private

# Get the solr response for the field :field
def extract_facet_by_field facet_field
case
when (facet_field.respond_to?(:query) and facet_field.query)
create_facet_field_response_for_query_facet_field facet_field.key, facet_field
when (facet_field.respond_to?(:pivot) and facet_field.pivot)
create_facet_field_response_for_pivot_facet_field facet_field.key, facet_field
else
@response.facet_by_field_name(facet_field.field)
end
end

def create_facet_field_response_for_query_facet_field facet_name, facet_field
salient_facet_queries = facet_field.query.map { |k, x| x[:fq] }
items = []
@response.facet_queries.select { |k,v| salient_facet_queries.include?(k) }.reject { |value, hits| hits == 0 }.map do |value,hits|
salient_fields = facet_field.query.select { |key, val| val[:fq] == value }
key = ((salient_fields.keys if salient_fields.respond_to? :keys) || salient_fields.first).first
items << Blacklight::SolrResponse::Facets::FacetItem.new(:value => key, :hits => hits, :label => facet_field.query[key][:label])
when String, Symbol, Blacklight::Configuration::FacetField
facet_field = facet_configuration_for_field(field_or_field_name)
@response.aggregations[facet_field.key]
else
# is this really a useful case?
field_or_field_name
end

Blacklight::SolrResponse::Facets::FacetField.new facet_name, items
end


def create_facet_field_response_for_pivot_facet_field facet_name, facet_field
items = []
(@response.facet_pivot[facet_field.pivot.join(",")] || []).map do |lst|
items << construct_pivot_field(lst)
end

Blacklight::SolrResponse::Facets::FacetField.new facet_name, items
end

def construct_pivot_field lst, parent_fq = {}
items = []

lst[:pivot].each do |i|
items << construct_pivot_field(i, parent_fq.merge({ lst[:field] => lst[:value] }))
end if lst[:pivot]

Blacklight::SolrResponse::Facets::FacetItem.new(:value => lst[:value], :hits => lst[:count], :field => lst[:field], :items => items, :fq => parent_fq)

end
end
end
2 changes: 1 addition & 1 deletion lib/blacklight/solr_repository.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def send_and_receive(path, solr_params = {})
key = blacklight_config.http_method == :post ? :data : :params
res = connection.send_and_receive(path, {key=>solr_params.to_hash, method:blacklight_config.http_method})

solr_response = blacklight_config.response_model.new(res, solr_params, document_model: blacklight_config.document_model)
solr_response = blacklight_config.response_model.new(res, solr_params, document_model: blacklight_config.document_model, blacklight_config: blacklight_config)

Blacklight.logger.debug("Solr query: #{solr_params.inspect}")
Blacklight.logger.debug("Solr response: #{solr_response.inspect}") if defined?(::BLACKLIGHT_VERBOSE_LOGGING) and ::BLACKLIGHT_VERBOSE_LOGGING
Expand Down
24 changes: 8 additions & 16 deletions lib/blacklight/solr_response.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
class Blacklight::SolrResponse < HashWithIndifferentAccess
extend Deprecation

require 'blacklight/solr_response/pagination_methods'

Expand All @@ -16,44 +17,35 @@ class Blacklight::SolrResponse < HashWithIndifferentAccess
include MoreLikeThis

attr_reader :request_params
attr_accessor :document_model
attr_accessor :document_model, :blacklight_config

def initialize(data, request_params, options = {})
super(force_to_utf8(data))
@request_params = request_params
self.document_model = options[:solr_document_model] || options[:document_model] || SolrDocument
self.blacklight_config = options[:blacklight_config]
end

def header
self['responseHeader']
self['responseHeader'] || {}
end

def update(other_hash)
other_hash.each_pair { |key, value| self[key] = value }
self
end

def params
(header and header['params']) ? header['params'] : request_params
header['params'] || request_params
end

def rows
params[:rows].to_i
params[:rows].to_i
end

def sort
params[:sort]
end

def docs
@docs ||= begin
response['docs'] || []
end
end

def documents
docs.collect{|doc| document_model.new(doc, self) }
@documents ||= (response['docs'] || []).collect{|doc| document_model.new(doc, self) }
end
alias_method :docs, :documents

def grouped
@groups ||= self["grouped"].map do |field, group|
Expand Down
158 changes: 133 additions & 25 deletions lib/blacklight/solr_response/facets.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'ostruct'

module Blacklight::SolrResponse::Facets
extend Deprecation

# represents a facet value; which is a field value and its hit count
class FacetItem < OpenStruct
Expand Down Expand Up @@ -67,48 +68,46 @@ def solr_default_offset
end

end


##
# Get all the Solr facet data (fields, queries, pivots) as a hash keyed by
# both the Solr field name and/or by the blacklight field name
def aggregations
@aggregations ||= {}.merge(facet_field_aggregations).merge(facet_query_aggregations).merge(facet_pivot_aggregations)
end

# @response.facets.each do |facet|
# facet.name
# facet.items
# end
# "caches" the result in the @facets instance var
def facets
@facets ||= begin
facet_fields.map do |(facet_field_name,values_and_hits)|
items = []
options = {}
values_and_hits.each_slice(2) do |k,v|
items << FacetItem.new(:value => k, :hits => v)
end
options[:sort] = (params[:"f.#{facet_field_name}.facet.sort"] || params[:'facet.sort'])
if params[:"f.#{facet_field_name}.facet.limit"] || params[:"facet.limit"]
options[:limit] = (params[:"f.#{facet_field_name}.facet.limit"] || params[:"facet.limit"]).to_i
end

if params[:"f.#{facet_field_name}.facet.offset"] || params[:'facet.offset']
options[:offset] = (params[:"f.#{facet_field_name}.facet.offset"] || params[:'facet.offset']).to_i
end
FacetField.new(facet_field_name, items, options)
end
end
aggregations.values
end

deprecation_deprecate facets: :aggregations

# pass in a facet field name and get back a Facet instance
def facet_by_field_name(name)
@facets_by_field_name ||= {}
@facets_by_field_name[name] ||= (
facets.detect{|facet|facet.name.to_s == name.to_s}
)
aggregations[name]
end
deprecation_deprecate facet_by_field_name: :aggregations

def facet_counts
@facet_counts ||= self['facet_counts'] || {}
end

# Returns the hash of all the facet_fields (ie: {'instock_b' => ['true', 123, 'false', 20]}
def facet_fields
@facet_fields ||= facet_counts['facet_fields'] || {}
@facet_fields ||= begin
val = facet_counts['facet_fields'] || {}

# this is some old solr (1.4? earlier?) serialization of facet fields
if val.is_a? Array
Hash[val]
else
val
end
end
end

# Returns all of the facet queries
Expand All @@ -120,5 +119,114 @@ def facet_queries
def facet_pivot
@facet_pivot ||= facet_counts['facet_pivot'] || {}
end

private
##
# Convert Solr responses of various json.nl flavors to
def list_as_hash solr_list
# map
@facet_fields_as_hash ||= if solr_list.values.first.is_a? Hash
solr_list
else
solr_list.each_with_object({}) do |(key, values), hash|
hash[key] = if values.first.is_a? Array
# arrarr
Hash[values]
else
# flat
Hash[values.each_slice(2).to_a]
end
end
end
end

##
# Convert Solr's facet_field response into
# a hash of Blacklight::SolrResponse::Facet::FacetField objects
def facet_field_aggregations
list_as_hash(facet_fields).each_with_object({}) do |(facet_field_name, values), hash|
items = []
options = {}
values.each do |value, hits|
i = FacetItem.new(value: value, hits: hits)

# solr facet.missing serialization
if value.nil?
i.label = I18n.t(:"blacklight.search.fields.facet.missing.#{facet_field_name}", default: [:"blacklight.search.facets.missing"])
i.fq = "-#{facet_field_name}:[* TO *]"
end

items << i
end
options[:sort] = (params[:"f.#{facet_field_name}.facet.sort"] || params[:'facet.sort'])
if params[:"f.#{facet_field_name}.facet.limit"] || params[:"facet.limit"]
options[:limit] = (params[:"f.#{facet_field_name}.facet.limit"] || params[:"facet.limit"]).to_i
end

if params[:"f.#{facet_field_name}.facet.offset"] || params[:'facet.offset']
options[:offset] = (params[:"f.#{facet_field_name}.facet.offset"] || params[:'facet.offset']).to_i
end

hash[facet_field_name] = FacetField.new(facet_field_name, items, options)

if blacklight_config and !blacklight_config.facet_fields[facet_field_name]
# alias all the possible blacklight config names..
blacklight_config.facet_fields.select { |k,v| v.field == facet_field_name }.each do |key,_|
hash[key] = hash[facet_field_name]
end
end
end
end

##
# Aggregate Solr's facet_query response into the virtual facet fields defined
# in the blacklight configuration
def facet_query_aggregations
return {} unless blacklight_config

blacklight_config.facet_fields.select { |k,v| v.query }.each_with_object({}) do |(field_name, facet_field), hash|
salient_facet_queries = facet_field.query.map { |k, x| x[:fq] }
items = []
facet_queries.select { |k,v| salient_facet_queries.include?(k) }.reject { |value, hits| hits == 0 }.map do |value,hits|
salient_fields = facet_field.query.select { |key, val| val[:fq] == value }
key = ((salient_fields.keys if salient_fields.respond_to? :keys) || salient_fields.first).first
items << Blacklight::SolrResponse::Facets::FacetItem.new(value: key, hits: hits, label: facet_field.query[key][:label])
end

hash[field_name] = Blacklight::SolrResponse::Facets::FacetField.new field_name, items
end
end

##
# Convert Solr's facet_pivot response into
# a hash of Blacklight::SolrResponse::Facet::FacetField objects
def facet_pivot_aggregations
facet_pivot.each_with_object({}) do |(field_name, values), hash|
items = []
values.map do |lst|
items << construct_pivot_field(lst)
end

if blacklight_config and !blacklight_config.facet_fields[field_name]
# alias all the possible blacklight config names..
blacklight_config.facet_fields.select { |k,v| v.pivot and v.pivot.join(",") == field_name }.each do |key, _|
hash[key] = Blacklight::SolrResponse::Facets::FacetField.new key, items
end
end
end
end

##
# Recursively parse the pivot facet response to build up the full pivot tree
def construct_pivot_field lst, parent_fq = {}
items = []

lst[:pivot].each do |i|
items << construct_pivot_field(i, parent_fq.merge({ lst[:field] => lst[:value] }))
end if lst[:pivot]

Blacklight::SolrResponse::Facets::FacetItem.new(value: lst[:value], hits: lst[:count], field: lst[:field], items: items, fq: parent_fq)
end


end # end Facets
17 changes: 0 additions & 17 deletions lib/blacklight/solr_response/pagination_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,4 @@ def offset_value #:nodoc:
def total_count #:nodoc:
total
end

def model_name
if !docs.empty? and docs.first.respond_to? :model_name
docs.first.model_name
end
end

## Methods in kaminari master that we'd like to use today.
# Next page number in the collection
def next_page
current_page + 1 unless last_page?
end

# Previous page number in the collection
def prev_page
current_page - 1 unless first_page?
end
end

0 comments on commit b5da6f2

Please sign in to comment.