Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
6 changes: 6 additions & 0 deletions sunspot/lib/sunspot/dsl/field_query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ def order_by_geodist(field_name, lat, lon, direction = nil)
)
end

def order_by_child_document(field_name, direction = nil, block_join:)
@query.add_sort(
Sunspot::Query::Sort::ChildDocumentSort.new(field_name, block_join, direction)
)
end

#
# DEPRECATED Use <code>order_by(:random)</code>
#
Expand Down
29 changes: 18 additions & 11 deletions sunspot/lib/sunspot/field_factory.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Sunspot
#
#
# The FieldFactory module contains classes for generating fields. FieldFactory
# implementation classes should implement a #build method, although the arity
# of the method depends on the type of factory. They also must implement a
Expand Down Expand Up @@ -41,7 +41,7 @@ def extract_value(model, options = {})
end
end

#
#
# A StaticFieldFactory generates normal static fields. Each factory instance
# contains an eager-initialized field instance, which is returned by the
# #build method.
Expand All @@ -60,14 +60,14 @@ def initialize(name, type, options = {}, &block)
end
end

#
#
# Return the field instance built by this factory
#
def build
@field
end

#
#
# Extract the encapsulated field's data from the given model and add it
# into the Solr document for indexing.
#
Expand All @@ -90,7 +90,7 @@ def populate_document(document, model, options = {}) #:nodoc:
end
end

#
#
# A unique signature identifying this field by name and type.
#
def signature
Expand All @@ -107,7 +107,7 @@ def initialize(name, type, options = {}, &block)
@field = JoinField.new(self.name, type, options)
end

#
#
# Return the field instance built by this factory
#
def build
Expand All @@ -122,7 +122,7 @@ def signature
end
end

#
#
# DynamicFieldFactories create dynamic field instances based on dynamic
# configuration.
#
Expand All @@ -141,13 +141,13 @@ def initialize(name, type, options = {}, &block)
def build(dynamic_name)
AttributeField.new([@name, dynamic_name].join(separator), @type, @options.dup)
end
#
#
# This alias allows a DynamicFieldFactory to be used in place of a Setup
# or CompositeSetup instance by query components.
#
alias_method :field, :build

#
#
# Generate dynamic fields based on hash returned by data accessor and
# add the field data to the document.
#
Expand All @@ -167,7 +167,7 @@ def populate_document(document, model, options = {})
end
end

#
#
# Unique signature identifying this dynamic field based on name and type
#
def signature
Expand All @@ -193,7 +193,7 @@ def extract_value(model, options = {})
# TODO(ar3s3ru): how to handle incorrect field values?
values = @extractor.value_for(model)
adapter = options[:adapter]
unless values.is_a? Array
unless values.is_a?(Array) || rails_association?(values)
raise 'Child documents field must be an Array of indexable documents'
end
if adapter.nil? || !adapter.respond_to?(:call)
Expand All @@ -206,6 +206,13 @@ def extract_value(model, options = {})
def signature
[field, ::RSolr::Document::CHILD_DOCUMENT_KEY]
end

private

def rails_association?(values)
return false unless defined?(ActiveRecord::Associations::CollectionProxy)
values.is_a?(ActiveRecord::Associations::CollectionProxy)
end
end
end
end
4 changes: 2 additions & 2 deletions sunspot/lib/sunspot/indexer.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
require 'sunspot/batcher'

module Sunspot
#
#
# This class presents a service for adding, updating, and removing data
# from the Solr index. An Indexer instance is associated with a particular
# setup, and thus is capable of indexing instances of a certain class (and its
Expand All @@ -12,7 +12,7 @@ def initialize(connection)
@connection = connection
end

#
#
# Construct a representation of the model for indexing and send it to the
# connection for indexing
#
Expand Down
34 changes: 29 additions & 5 deletions sunspot/lib/sunspot/query/block_join.rb
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,11 @@ def all_parents_filter
# to select which parents are used in the query.
fq = filter_query.to_params[:fq]
raise 'allParents filter must be non-empty!' if fq.nil?
fq[0] # Type filter used by Sunspot
Util.escape(fq[0]) # Type filter used by Sunspot
end

def facet_type_filter
filter_query.to_params[:fq][0]
end

def secondary_filter
Expand All @@ -176,9 +180,18 @@ def to_params
class ParentWhich < Abstract
alias some_children_filter secondary_filter

def all_parents_filter
def all_parents_parts
# Use top-level scope (on parent type) as allParents filter.
scope.to_params[:fq].flatten.join(' AND ')
parts = scope.to_params[:fq].flatten
parts.map { |v| Util.escape(v) }
end

def all_parents_filter(*args)
all_parents_parts(*args).join(' AND ')
end

def facet_type_filter
scope.to_params[:fq].flatten[0]
end

def secondary_filter
Expand All @@ -190,10 +203,21 @@ def secondary_filter
q
end

def field_list_string
parts = []
parts << '[child'
parts << 'parentFilter="' + all_parents_parts[0] + '"'
parts << 'childFilter="' + secondary_filter.map { |f| Util.escape(f) }.join(' AND ') + '"]'
parts.join(' ')
end

def to_params
{ q: render_query_string('parent', 'which') }
{
q: render_query_string('parent', 'which'),
fl: [:id] + [field_list_string]
}
end
end
end
end
end
end
4 changes: 2 additions & 2 deletions sunspot/lib/sunspot/query/block_join_json_facet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def field_name_with_local_params
type: 'terms',
field: @field.indexed_name,
domain: {
@operator => @query.all_parents_filter,
@operator => @query.facet_type_filter,
FILTER_OP => generate_filter
}
}.merge!(init_params)
Expand All @@ -78,4 +78,4 @@ def generate_filter
end
end
end
end
end
35 changes: 25 additions & 10 deletions sunspot/lib/sunspot/query/sort.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
module Sunspot
module Query
#
#
# The classes in this module implement query components that build sort
# parameters for Solr. As well as regular sort on fields, there are several
# "special" sorts that allow ordering for metrics calculated during the
# search.
#
#
module Sort #:nodoc: all
DIRECTIONS = {
:asc => 'asc',
Expand All @@ -15,7 +15,7 @@ module Sort #:nodoc: all
}

class <<self
#
#
# Certain field names are "special", referring to specific non-field
# sorts, which are generally by other metrics associated with hits.
#
Expand All @@ -30,7 +30,7 @@ def special(name)
end
end

#
#
# Base class for sorts. All subclasses should implement the #to_param
# method, which is a string that is then concatenated with other sort
# strings by the SortComposite to form the sort parameter.
Expand All @@ -42,19 +42,19 @@ def initialize(direction)

private

#
#
# Translate fairly forgiving direction argument into solr direction
#
def direction_for_solr
DIRECTIONS[@direction] ||
DIRECTIONS[@direction] ||
raise(
ArgumentError,
"Unknown sort direction #{@direction}. Acceptable input is: #{DIRECTIONS.keys.map { |input| input.inspect } * ', '}"
)
end
end

#
#
# A FieldSort is the usual kind of sort, by the value of a particular
# field, ascending or descending
#
Expand All @@ -71,15 +71,15 @@ def to_param
end
end

#
#
# A RandomSort uses Solr's random field functionality to sort results
# (usually) randomly.
#
class RandomSort < Abstract
def initialize(options_or_direction=nil)
if options_or_direction.is_a?(Hash)
@seed, @direction = options_or_direction[:seed], options_or_direction[:direction]
else
else
@direction = options_or_direction
end

Expand All @@ -91,7 +91,7 @@ def to_param
end
end

#
#
# A ScoreSort sorts by keyword relevance score. This is only useful when
# performing fulltext search.
#
Expand Down Expand Up @@ -155,6 +155,21 @@ def to_s
"#{function}(#{fields.map(&:to_s).join(",")})"
end
end

#
# A ChildDocumentSort sorts on child documents.
#
class ChildDocumentSort < Abstract
def initialize(field_name, block_join, direction)
@field_name = field_name
@block_join = block_join
@direction = (direction || :asc).to_sym
end
def to_param
field = Setup.for(@block_join[:type]).field(@field_name)
"childfield(#{field.indexed_name}) #{@direction}"
end
end
end
end
end
3 changes: 2 additions & 1 deletion sunspot/lib/sunspot/search/field_json_facet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ class FieldJsonFacet
attr_reader :name

def initialize(field, search, options)
@name, @search, @options = name, search, options
@search, @options = search, options
@name = (options[:name] || field.name)
@field = field
end

Expand Down
20 changes: 13 additions & 7 deletions sunspot/lib/sunspot/search/hit.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module Sunspot
module Search
#
#
# Hit objects represent the raw information returned by Solr for a single
# document. As well as the primary key and class name, hit objects give
# access to stored field values, keyword relevance score, and keyword
Expand All @@ -9,15 +9,15 @@ module Search
class Hit
SPECIAL_KEYS = Set.new(%w(id type score)) #:nodoc:

#
#
# Primary key of object associated with this hit, as string.
#
attr_reader :primary_key
#
#
# Class name of object associated with this hit, as string.
#
attr_reader :class_name
#
#
# Keyword relevance score associated with this result. Nil if this hit
# is not from a keyword search.
#
Expand All @@ -33,8 +33,9 @@ def initialize(raw_hit, highlights, search) #:nodoc:
@stored_values = raw_hit
@stored_cache = {}
@highlights = highlights
@child_documents = (raw_hit['_childDocuments_'] || []).map { |h| Hit.new(h, highlights, search) }
end

#
# Returns all highlights for this hit when called without parameters.
# When a field_name is provided, returns only the highlight for this field.
Expand All @@ -55,7 +56,7 @@ def highlight(field_name)
highlights(field_name).first
end

#
#
# Retrieve stored field value. For any attribute field configured with
# :stored => true, the Hit object will contain the stored value for
# that field. The value of this field will be typecast according to the
Expand All @@ -80,7 +81,7 @@ def stored(field_name, dynamic_field_name = nil)
@stored_cache[field_key] = stored_value(field_name, dynamic_field_name)
end

#
#
# Retrieve the instance associated with this hit. This is lazy-loaded, but
# the first time it is called on any hit, all the hits for the search will
# load their instances using the adapter's #load_all method.
Expand Down Expand Up @@ -110,6 +111,11 @@ def to_param
self.primary_key
end

def children(type = nil)
return @child_documents if type.nil?
@child_documents.select { |d| d.class_name == type.name }
end

private

def setup
Expand Down
Loading