Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions app/models/facet_search_builder.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,89 @@
# frozen_string_literal: true

##
# Application-level FacetSearchBuilder that extends Blacklight's base FacetSearchBuilder
# with Solr-specific functionality for facet queries.
#
# ## Purpose
#
# This class serves as your application's facet search builder for generating
# queries specifically for facet operations like "more facets" pages and facet
# pagination. It includes `Blacklight::Solr::FacetSearchBuilderBehavior` which
# provides a complete processor chain optimized for facet-only queries.
#
# ## When to Modify This Class
#
# Add custom facet logic here when you need to:
# - Filter facet values based on user permissions
# - Apply institution-specific facet constraints
# - Customize facet sorting or display logic
# - Add security filters to facet queries
# - Implement custom facet suggestion behavior
#
# ## Adding Custom Processors
#
# Extend the processor chain to add your own facet query building methods:
#
# class FacetSearchBuilder < Blacklight::FacetSearchBuilder
# include Blacklight::Solr::FacetSearchBuilderBehavior
#
# # Add your custom processor to the chain
# self.default_processor_chain += [:add_facet_access_control]
#
# # Define the processor method
# def add_facet_access_control(solr_parameters)
# return unless facet == 'restricted_facet'
# return unless current_user&.admin?
# # Only show restricted facet values to admins
# solr_parameters[:fq] ||= []
# solr_parameters[:fq] << "access_level:admin"
# end
# end
#
# ## Key Differences from SearchBuilder
#
# FacetSearchBuilder queries differ from regular search queries:
# - Returns no document results (`rows: 0`)
# - Focuses on facet value generation and pagination
# - Supports facet prefix filtering for autocomplete
# - Optimized for facet-specific operations
#
# ## Important: Shared Query Logic
#
# **If your customizations modify Solr's `q` or `fq` parameters, you likely need
# to add the same logic to BOTH this FacetSearchBuilder AND SearchBuilder.**
#
# This ensures facet queries reflect the same constraints as search results:
#
# class SearchBuilder < Blacklight::SearchBuilder
# include Blacklight::Solr::SearchBuilderBehavior
# self.default_processor_chain += [:add_institution_filter]
#
# def add_institution_filter(solr_parameters)
# return unless current_user&.institution
# solr_parameters[:fq] ||= []
# solr_parameters[:fq] << "institution_id:#{current_user.institution.id}"
# end
# end
#
# class FacetSearchBuilder < Blacklight::FacetSearchBuilder
# include Blacklight::Solr::FacetSearchBuilderBehavior
# self.default_processor_chain += [:add_institution_filter]
#
# # Same method needed here too!
# def add_institution_filter(solr_parameters)
# return unless current_user&.institution
# solr_parameters[:fq] ||= []
# solr_parameters[:fq] << "institution_id:#{current_user.institution.id}"
# end
# end
#
# ## Alternative Approaches
#
# - For main search results, use `SearchBuilder` instead
# - For completely different query patterns, extend `AbstractSearchBuilder`
# - For one-off modifications, use `append()` method on builder instances
#
class FacetSearchBuilder < Blacklight::FacetSearchBuilder
include Blacklight::Solr::FacetSearchBuilderBehavior
end
72 changes: 72 additions & 0 deletions app/models/search_builder.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,77 @@
# frozen_string_literal: true

##
# Application-level SearchBuilder that extends Blacklight's base SearchBuilder
# with Solr-specific functionality.
#
# ## Purpose
#
# This class serves as your application's main search builder for generating
# search result queries. It includes `Blacklight::Solr::SearchBuilderBehavior`
# which provides a complete processor chain for Solr queries.
#
# ## When to Modify This Class
#
# Add custom search logic here when you need to:
# - Apply institution-specific filtering
# - Add custom faceting behavior
# - Implement access controls or permission filtering
# - Modify query parsing or field weighting
# - Add analytics or logging to search queries
#
# ## Adding Custom Processors
#
# Extend the processor chain to add your own query building methods:
#
# class SearchBuilder < Blacklight::SearchBuilder
# include Blacklight::Solr::SearchBuilderBehavior
#
# # Add your custom processor to the chain
# self.default_processor_chain += [:add_my_custom_filter]
#
# # Define the processor method
# def add_my_custom_filter(solr_parameters)
# solr_parameters[:fq] ||= []
# solr_parameters[:fq] << "status:published"
# end
# end
#
# ## Important: Shared Query Logic
#
# **If your customizations modify Solr's `q` or `fq` parameters, you likely need
# to add the same logic to BOTH this SearchBuilder AND FacetSearchBuilder.**
#
# This ensures facet queries reflect the same constraints as search results:
#
# class SearchBuilder < Blacklight::SearchBuilder
# include Blacklight::Solr::SearchBuilderBehavior
# self.default_processor_chain += [:add_institution_filter]
#
# def add_institution_filter(solr_parameters)
# return unless current_user&.institution
# solr_parameters[:fq] ||= []
# solr_parameters[:fq] << "institution_id:#{current_user.institution.id}"
# end
# end
#
# class FacetSearchBuilder < Blacklight::FacetSearchBuilder
# include Blacklight::Solr::FacetSearchBuilderBehavior
# self.default_processor_chain += [:add_institution_filter]
#
# # Same method needed here too!
# def add_institution_filter(solr_parameters)
# return unless current_user&.institution
# solr_parameters[:fq] ||= []
# solr_parameters[:fq] << "institution_id:#{current_user.institution.id}"
# end
# end
#
# ## Alternative Approaches
#
# - For facet-specific queries, extend `FacetSearchBuilder` instead
# - For completely different search patterns, extend `AbstractSearchBuilder`
# - For one-off modifications, use `append()` method on search builder instances
#
class SearchBuilder < Blacklight::SearchBuilder
include Blacklight::Solr::SearchBuilderBehavior
end
50 changes: 47 additions & 3 deletions lib/blacklight/abstract_search_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,53 @@

module Blacklight
##
# Blacklight's SearchBuilder converts blacklight request parameters into
# query parameters appropriate for search index. It does so by evaluating a
# chain of processing methods to populate a result hash (see {#to_hash}).
# Base class for all Blacklight search builders that converts Blacklight request
# parameters into query parameters appropriate for search index. It does so by
# evaluating a chain of processing methods to populate a result hash (see {#to_hash}).
#
# ## When to Use AbstractSearchBuilder
#
# You typically **should not use AbstractSearchBuilder directly**. Instead, use one of its subclasses:
# - {Blacklight::SearchBuilder} for main search results
# - {Blacklight::FacetSearchBuilder} for facet queries (e.g., "more facets" functionality)
#
# ## Creating Custom Search Builders
#
# You may extend AbstractSearchBuilder when creating specialized search builders that don't
# fit the standard search results or facet patterns. Common use cases include:
# - Building queries for specialized indexes or collections
# - Creating search builders for administrative interfaces
# - Implementing custom search workflows that require different processor chains
#
# ## Processor Chain Pattern
#
# All search builders use a "processor chain" pattern where methods are called in sequence
# to build up the final query parameters. Each method in the chain receives a hash of
# Solr parameters and can modify it:
#
# class CustomSearchBuilder < AbstractSearchBuilder
# self.default_processor_chain = [:add_defaults, :add_custom_logic]
#
# def add_defaults(solr_parameters)
# solr_parameters[:rows] = 20
# end
#
# def add_custom_logic(solr_parameters)
# solr_parameters[:fq] ||= []
# solr_parameters[:fq] << 'status:active'
# end
# end
#
# Methods can be added to the processor chain by modifying `default_processor_chain`
# or by using `append()` and `except()` methods on search builder instances.
#
# ## Important: Shared Query Logic Pattern
#
# When customizing search builders that modify core query parameters (`q` or `fq`),
# you often need to apply the same logic to both SearchBuilder and FacetSearchBuilder
# to ensure consistent behavior between search results and facet values.
#
#
class AbstractSearchBuilder
class_attribute :default_processor_chain
self.default_processor_chain = []
Expand Down
76 changes: 76 additions & 0 deletions lib/blacklight/facet_search_builder.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,82 @@
# frozen_string_literal: true

module Blacklight
##
# FacetSearchBuilder creates Solr queries specifically for facet-related operations,
# particularly for "more facets" functionality where users want to see additional
# facet values beyond what's displayed on the main search results page.
#
# ## When to Use FacetSearchBuilder
#
# Use FacetSearchBuilder for:
# - **"More facets" pages** - When users click "more" to see additional facet values
# - **Facet pagination** - Browsing through large lists of facet values
# - **Facet suggestion/autocomplete** - Type-ahead search within facet values
# - **Facet-only queries** - When you need facet data without search results
#
# ## Key Differences from SearchBuilder
#
# FacetSearchBuilder differs from SearchBuilder in several important ways:
# - Sets `rows: 0` to avoid returning document results (only facets matter)
# - Includes facet pagination parameters (`facet.offset`, `facet.limit`)
# - Supports facet prefix filtering for suggestion queries
# - Optimized processor chain focused on facet generation
#
# ## Typical Usage
#
# FacetSearchBuilder is typically used internally by Blacklight's facet controllers
# and helpers, but you can extend it for custom facet behavior:
#
# class FacetSearchBuilder < Blacklight::FacetSearchBuilder
# include Blacklight::Solr::FacetSearchBuilderBehavior
#
# # Add custom facet filtering
# self.default_processor_chain += [:add_facet_security_filter]
#
# def add_facet_security_filter(solr_parameters)
# return unless facet == 'institution_facet'
# solr_parameters[:fq] ||= []
# solr_parameters[:fq] << 'public:true'
# end
# end
#
# ## Processor Chain Methods
#
# When including `Blacklight::Solr::FacetSearchBuilderBehavior`, you get:
# - All SearchBuilderBehavior processors (for base query building)
# - `add_facet_paging_to_solr` - Handles facet pagination parameters
# - `add_facet_suggestion_parameters` - Supports facet prefix/suggestion queries
#
# ## Important: Query Parameter Customizations
#
# **If your customizations modify Solr's `q` or `fq` parameters, you likely need
# to add the same logic to both SearchBuilder AND FacetSearchBuilder.**
#
# This is critical because facet queries should reflect the same constraints as
# the main search results. Users expect facet values to be filtered by the same
# criteria that filter their search results:
#
# # Add to BOTH SearchBuilder and FacetSearchBuilder
# def add_institution_filter(solr_parameters)
# return unless current_user&.institution
# solr_parameters[:fq] ||= []
# solr_parameters[:fq] << "institution_id:#{current_user.institution.id}"
# end
#
# Common parameters that typically need both builders:
# - Access control filters (`fq`)
# - Institution or tenant scoping (`fq`)
# - Date range constraints (`fq`)
# - Status filtering (published, active, etc.) (`fq`)
# - Query scope modifications (`q`)
#
# ## When NOT to Use FacetSearchBuilder
#
# Don't use FacetSearchBuilder for:
# - **Main search results** - Use {Blacklight::SearchBuilder} instead
# - **Document display** - Use repository methods directly
# - **General queries** - Use {SearchBuilder} or extend {AbstractSearchBuilder}
#
class FacetSearchBuilder < AbstractSearchBuilder
def facet_suggestion_query=(value)
params_will_change!
Expand Down
78 changes: 75 additions & 3 deletions lib/blacklight/search_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,81 @@

module Blacklight
##
# Blacklight's SearchBuilder converts blacklight request parameters into
# query parameters appropriate for search index. It does so by evaluating a
# chain of processing methods to populate a result hash (see {#to_hash}).
# SearchBuilder is the primary search builder for generating main search result queries.
# It converts Blacklight request parameters into query parameters appropriate for the
# search index by evaluating a chain of processing methods.
#
# ## When to Use SearchBuilder
#
# Use SearchBuilder (or extend it) for:
# - **Main search results pages** - The primary search interface users see
# - **Advanced search** - Complex multi-field search forms
#
# SearchBuilder handles common search functionality like:
# - Query parsing and field searching
# - Facet filtering and constraints
# - Pagination (rows, start, page)
# - Sorting and result ordering
# - Field selection for display
#
# ## Typical Usage Patterns
#
# Most applications will extend SearchBuilder to add custom query logic:
#
# class SearchBuilder < Blacklight::SearchBuilder
# include Blacklight::Solr::SearchBuilderBehavior
#
# # Add custom processor to the chain
# self.default_processor_chain += [:add_institution_filter]
#
# def add_institution_filter(solr_parameters)
# return unless current_user&.institution
# solr_parameters[:fq] ||= []
# solr_parameters[:fq] << "institution_id:#{current_user.institution.id}"
# end
# end
#
# ## When NOT to Use SearchBuilder
#
# Don't use SearchBuilder for:
# - **Facet queries** - Use {Blacklight::FacetSearchBuilder} instead
# - **Single document retrieval** - Use repository methods directly
# - **Specialized admin queries** - Consider extending {AbstractSearchBuilder}
#
# ## Important: Query Parameter Customizations
#
# **If your customizations modify Solr's `q` or `fq` parameters, you likely need
# to add the same logic to both SearchBuilder AND FacetSearchBuilder.**
#
# This is because facet queries inherit the same base query context as search results.
# For example, if you add an institution filter to search results, users would expect
# facet values to also be filtered by that same institution:
#
# # Add to BOTH SearchBuilder and FacetSearchBuilder
# def add_institution_filter(solr_parameters)
# return unless current_user&.institution
# solr_parameters[:fq] ||= []
# solr_parameters[:fq] << "institution_id:#{current_user.institution.id}"
# end
#
# Common parameters that affect both builders:
# - Access control filters (`fq`)
# - Institution or tenant scoping (`fq`)
# - Date range constraints (`fq`)
# - Status filtering (published, active, etc.) (`fq`)
# - Query modifications that change search scope (`q`)
#
# ## Processor Chain Methods
#
# When including `Blacklight::Solr::SearchBuilderBehavior`, you get these processors:
# - `default_solr_parameters` - Base configuration defaults
# - `add_query_to_solr` - Main user query (q parameter)
# - `add_facet_fq_to_solr` - Applied facet filters
# - `add_facetting_to_solr` - Facet field configuration
# - `add_paging_to_solr` - Pagination (rows, start)
# - `add_sorting_to_solr` - Sort parameters
# - `add_solr_fields_to_query` - Field lists (fl parameter)
#
class SearchBuilder < AbstractSearchBuilder
##
# Update the :q (query) parameter
Expand Down