sunspot_rbg 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +12 -0
- data/Gemfile +4 -0
- data/History.txt +222 -0
- data/LICENSE +18 -0
- data/Rakefile +17 -0
- data/TODO +13 -0
- data/VERSION.yml +4 -0
- data/bin/sunspot-installer +19 -0
- data/bin/sunspot-solr +74 -0
- data/installer/config/schema.yml +95 -0
- data/lib/light_config.rb +40 -0
- data/lib/sunspot/adapters.rb +265 -0
- data/lib/sunspot/composite_setup.rb +202 -0
- data/lib/sunspot/configuration.rb +46 -0
- data/lib/sunspot/data_extractor.rb +50 -0
- data/lib/sunspot/dsl/adjustable.rb +47 -0
- data/lib/sunspot/dsl/field_query.rb +279 -0
- data/lib/sunspot/dsl/fields.rb +103 -0
- data/lib/sunspot/dsl/fulltext.rb +243 -0
- data/lib/sunspot/dsl/function.rb +14 -0
- data/lib/sunspot/dsl/functional.rb +44 -0
- data/lib/sunspot/dsl/more_like_this_query.rb +56 -0
- data/lib/sunspot/dsl/paginatable.rb +28 -0
- data/lib/sunspot/dsl/query_facet.rb +36 -0
- data/lib/sunspot/dsl/restriction.rb +25 -0
- data/lib/sunspot/dsl/restriction_with_near.rb +121 -0
- data/lib/sunspot/dsl/scope.rb +217 -0
- data/lib/sunspot/dsl/search.rb +30 -0
- data/lib/sunspot/dsl/standard_query.rb +121 -0
- data/lib/sunspot/dsl.rb +5 -0
- data/lib/sunspot/field.rb +193 -0
- data/lib/sunspot/field_factory.rb +129 -0
- data/lib/sunspot/indexer.rb +131 -0
- data/lib/sunspot/installer/library_installer.rb +45 -0
- data/lib/sunspot/installer/schema_builder.rb +219 -0
- data/lib/sunspot/installer/solrconfig_updater.rb +76 -0
- data/lib/sunspot/installer/task_helper.rb +18 -0
- data/lib/sunspot/installer.rb +31 -0
- data/lib/sunspot/query/abstract_field_facet.rb +52 -0
- data/lib/sunspot/query/boost_query.rb +24 -0
- data/lib/sunspot/query/common_query.rb +85 -0
- data/lib/sunspot/query/composite_fulltext.rb +36 -0
- data/lib/sunspot/query/connective.rb +206 -0
- data/lib/sunspot/query/date_field_facet.rb +14 -0
- data/lib/sunspot/query/dismax.rb +128 -0
- data/lib/sunspot/query/field_facet.rb +41 -0
- data/lib/sunspot/query/filter.rb +38 -0
- data/lib/sunspot/query/function_query.rb +52 -0
- data/lib/sunspot/query/geo.rb +53 -0
- data/lib/sunspot/query/highlighting.rb +55 -0
- data/lib/sunspot/query/more_like_this.rb +61 -0
- data/lib/sunspot/query/more_like_this_query.rb +12 -0
- data/lib/sunspot/query/pagination.rb +38 -0
- data/lib/sunspot/query/query_facet.rb +16 -0
- data/lib/sunspot/query/restriction.rb +262 -0
- data/lib/sunspot/query/scope.rb +9 -0
- data/lib/sunspot/query/sort.rb +95 -0
- data/lib/sunspot/query/sort_composite.rb +33 -0
- data/lib/sunspot/query/standard_query.rb +16 -0
- data/lib/sunspot/query/text_field_boost.rb +17 -0
- data/lib/sunspot/query.rb +11 -0
- data/lib/sunspot/schema.rb +151 -0
- data/lib/sunspot/search/abstract_search.rb +293 -0
- data/lib/sunspot/search/date_facet.rb +35 -0
- data/lib/sunspot/search/facet_row.rb +27 -0
- data/lib/sunspot/search/field_facet.rb +88 -0
- data/lib/sunspot/search/highlight.rb +38 -0
- data/lib/sunspot/search/hit.rb +136 -0
- data/lib/sunspot/search/more_like_this_search.rb +31 -0
- data/lib/sunspot/search/paginated_collection.rb +55 -0
- data/lib/sunspot/search/query_facet.rb +67 -0
- data/lib/sunspot/search/standard_search.rb +21 -0
- data/lib/sunspot/search.rb +9 -0
- data/lib/sunspot/server.rb +152 -0
- data/lib/sunspot/session.rb +260 -0
- data/lib/sunspot/session_proxy/abstract_session_proxy.rb +29 -0
- data/lib/sunspot/session_proxy/class_sharding_session_proxy.rb +66 -0
- data/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +89 -0
- data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +43 -0
- data/lib/sunspot/session_proxy/sharding_session_proxy.rb +222 -0
- data/lib/sunspot/session_proxy/silent_fail_session_proxy.rb +42 -0
- data/lib/sunspot/session_proxy/thread_local_session_proxy.rb +37 -0
- data/lib/sunspot/session_proxy.rb +87 -0
- data/lib/sunspot/setup.rb +350 -0
- data/lib/sunspot/text_field_setup.rb +29 -0
- data/lib/sunspot/type.rb +372 -0
- data/lib/sunspot/util.rb +243 -0
- data/lib/sunspot/version.rb +3 -0
- data/lib/sunspot.rb +569 -0
- data/lib/sunspot_rbg.rb +7 -0
- data/log/.gitignore +1 -0
- data/pkg/.gitignore +1 -0
- data/script/console +10 -0
- data/solr/README.txt +42 -0
- data/solr/etc/jetty.xml +218 -0
- data/solr/etc/webdefault.xml +379 -0
- data/solr/lib/jetty-6.1.3.jar +0 -0
- data/solr/lib/jetty-util-6.1.3.jar +0 -0
- data/solr/lib/jsp-2.1/ant-1.6.5.jar +0 -0
- data/solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
- data/solr/lib/jsp-2.1/jsp-2.1.jar +0 -0
- data/solr/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
- data/solr/lib/servlet-api-2.5-6.1.3.jar +0 -0
- data/solr/logs/.gitignore +1 -0
- data/solr/solr/.gitignore +1 -0
- data/solr/solr/README.txt +54 -0
- data/solr/solr/conf/admin-extra.html +31 -0
- data/solr/solr/conf/elevate.xml +36 -0
- data/solr/solr/conf/mapping-ISOLatin1Accent.txt +246 -0
- data/solr/solr/conf/protwords.txt +21 -0
- data/solr/solr/conf/schema.xml +238 -0
- data/solr/solr/conf/scripts.conf +24 -0
- data/solr/solr/conf/solrconfig.xml +934 -0
- data/solr/solr/conf/spellings.txt +2 -0
- data/solr/solr/conf/stopwords.txt +58 -0
- data/solr/solr/conf/synonyms.txt +31 -0
- data/solr/solr/conf/xslt/example.xsl +132 -0
- data/solr/solr/conf/xslt/example_atom.xsl +67 -0
- data/solr/solr/conf/xslt/example_rss.xsl +66 -0
- data/solr/solr/conf/xslt/luke.xsl +337 -0
- data/solr/start.jar +0 -0
- data/solr/webapps/solr.war +0 -0
- data/solr-1.3/etc/jetty.xml +212 -0
- data/solr-1.3/etc/webdefault.xml +379 -0
- data/solr-1.3/lib/jetty-6.1.3.jar +0 -0
- data/solr-1.3/lib/jetty-util-6.1.3.jar +0 -0
- data/solr-1.3/lib/jsp-2.1/ant-1.6.5.jar +0 -0
- data/solr-1.3/lib/jsp-2.1/core-3.1.1.jar +0 -0
- data/solr-1.3/lib/jsp-2.1/jsp-2.1.jar +0 -0
- data/solr-1.3/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
- data/solr-1.3/lib/servlet-api-2.5-6.1.3.jar +0 -0
- data/solr-1.3/solr/conf/elevate.xml +36 -0
- data/solr-1.3/solr/conf/protwords.txt +21 -0
- data/solr-1.3/solr/conf/schema.xml +64 -0
- data/solr-1.3/solr/conf/solrconfig.xml +725 -0
- data/solr-1.3/solr/conf/stopwords.txt +57 -0
- data/solr-1.3/solr/conf/synonyms.txt +31 -0
- data/solr-1.3/solr/lib/geoapi-nogenerics-2.1-M2.jar +0 -0
- data/solr-1.3/solr/lib/gt2-referencing-2.3.1.jar +0 -0
- data/solr-1.3/solr/lib/jsr108-0.01.jar +0 -0
- data/solr-1.3/solr/lib/locallucene.jar +0 -0
- data/solr-1.3/solr/lib/localsolr.jar +0 -0
- data/solr-1.3/start.jar +0 -0
- data/solr-1.3/webapps/solr.war +0 -0
- data/spec/api/adapters_spec.rb +33 -0
- data/spec/api/binding_spec.rb +50 -0
- data/spec/api/indexer/attributes_spec.rb +149 -0
- data/spec/api/indexer/batch_spec.rb +46 -0
- data/spec/api/indexer/dynamic_fields_spec.rb +42 -0
- data/spec/api/indexer/fixed_fields_spec.rb +57 -0
- data/spec/api/indexer/fulltext_spec.rb +43 -0
- data/spec/api/indexer/removal_spec.rb +53 -0
- data/spec/api/indexer/spec_helper.rb +1 -0
- data/spec/api/indexer_spec.rb +14 -0
- data/spec/api/query/advanced_manipulation_examples.rb +35 -0
- data/spec/api/query/connectives_examples.rb +189 -0
- data/spec/api/query/dsl_spec.rb +18 -0
- data/spec/api/query/dynamic_fields_examples.rb +165 -0
- data/spec/api/query/faceting_examples.rb +397 -0
- data/spec/api/query/fulltext_examples.rb +313 -0
- data/spec/api/query/function_spec.rb +70 -0
- data/spec/api/query/geo_examples.rb +68 -0
- data/spec/api/query/highlighting_examples.rb +223 -0
- data/spec/api/query/more_like_this_spec.rb +140 -0
- data/spec/api/query/ordering_pagination_examples.rb +95 -0
- data/spec/api/query/scope_examples.rb +275 -0
- data/spec/api/query/spec_helper.rb +1 -0
- data/spec/api/query/standard_spec.rb +28 -0
- data/spec/api/query/text_field_scoping_examples.rb +30 -0
- data/spec/api/query/types_spec.rb +20 -0
- data/spec/api/search/dynamic_fields_spec.rb +33 -0
- data/spec/api/search/faceting_spec.rb +360 -0
- data/spec/api/search/highlighting_spec.rb +69 -0
- data/spec/api/search/hits_spec.rb +120 -0
- data/spec/api/search/paginated_collection_spec.rb +26 -0
- data/spec/api/search/results_spec.rb +66 -0
- data/spec/api/search/search_spec.rb +23 -0
- data/spec/api/search/spec_helper.rb +1 -0
- data/spec/api/server_spec.rb +91 -0
- data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +85 -0
- data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +30 -0
- data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +41 -0
- data/spec/api/session_proxy/sharding_session_proxy_spec.rb +77 -0
- data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +24 -0
- data/spec/api/session_proxy/spec_helper.rb +9 -0
- data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +50 -0
- data/spec/api/session_spec.rb +220 -0
- data/spec/api/spec_helper.rb +3 -0
- data/spec/api/sunspot_spec.rb +18 -0
- data/spec/ext.rb +11 -0
- data/spec/helpers/indexer_helper.rb +29 -0
- data/spec/helpers/query_helper.rb +38 -0
- data/spec/helpers/search_helper.rb +80 -0
- data/spec/integration/dynamic_fields_spec.rb +55 -0
- data/spec/integration/faceting_spec.rb +238 -0
- data/spec/integration/highlighting_spec.rb +22 -0
- data/spec/integration/indexing_spec.rb +33 -0
- data/spec/integration/keyword_search_spec.rb +317 -0
- data/spec/integration/local_search_spec.rb +64 -0
- data/spec/integration/more_like_this_spec.rb +43 -0
- data/spec/integration/scoped_search_spec.rb +354 -0
- data/spec/integration/spec_helper.rb +7 -0
- data/spec/integration/stored_fields_spec.rb +10 -0
- data/spec/integration/test_pagination.rb +32 -0
- data/spec/mocks/adapters.rb +32 -0
- data/spec/mocks/blog.rb +3 -0
- data/spec/mocks/comment.rb +21 -0
- data/spec/mocks/connection.rb +126 -0
- data/spec/mocks/mock_adapter.rb +30 -0
- data/spec/mocks/mock_class_sharding_session_proxy.rb +24 -0
- data/spec/mocks/mock_record.rb +52 -0
- data/spec/mocks/mock_sharding_session_proxy.rb +15 -0
- data/spec/mocks/photo.rb +11 -0
- data/spec/mocks/post.rb +85 -0
- data/spec/mocks/super_class.rb +2 -0
- data/spec/mocks/user.rb +13 -0
- data/spec/spec_helper.rb +30 -0
- data/sunspot.gemspec +40 -0
- data/tasks/rdoc.rake +27 -0
- data/tasks/schema.rake +19 -0
- data/tasks/todo.rake +4 -0
- metadata +457 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
module DSL #:nodoc:
|
|
3
|
+
#
|
|
4
|
+
# This class provides the DSL for MoreLikeThis queries.
|
|
5
|
+
#
|
|
6
|
+
class MoreLikeThisQuery < FieldQuery
|
|
7
|
+
include Paginatable, Adjustable
|
|
8
|
+
|
|
9
|
+
def fields(*field_names)
|
|
10
|
+
boosted_fields = field_names.pop if field_names.last.is_a?(Hash)
|
|
11
|
+
field_names.each do |name|
|
|
12
|
+
mlt_fields = @setup.more_like_this_fields(name)
|
|
13
|
+
raise(ArgumentError, "Field #{name} is not setup for more_like_this") if mlt_fields.empty?
|
|
14
|
+
mlt_fields.each { |field| @query.more_like_this.add_field(field) }
|
|
15
|
+
end
|
|
16
|
+
if boosted_fields
|
|
17
|
+
boosted_fields.each_pair do |field_name, boost|
|
|
18
|
+
@setup.more_like_this_fields(field_name).each do |field|
|
|
19
|
+
@query.more_like_this.add_field(field, boost)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def minimum_term_frequency(value)
|
|
26
|
+
@query.more_like_this.minimum_term_frequency = value
|
|
27
|
+
end
|
|
28
|
+
alias_method :mintf, :minimum_term_frequency
|
|
29
|
+
|
|
30
|
+
def minimum_document_frequency(value)
|
|
31
|
+
@query.more_like_this.minimum_document_frequency = value
|
|
32
|
+
end
|
|
33
|
+
alias_method :mindf, :minimum_document_frequency
|
|
34
|
+
|
|
35
|
+
def minimum_word_length(value)
|
|
36
|
+
@query.more_like_this.minimum_word_length = value
|
|
37
|
+
end
|
|
38
|
+
alias_method :minwl, :minimum_word_length
|
|
39
|
+
|
|
40
|
+
def maximum_word_length(value)
|
|
41
|
+
@query.more_like_this.maximum_word_length = value
|
|
42
|
+
end
|
|
43
|
+
alias_method :maxwl, :maximum_word_length
|
|
44
|
+
|
|
45
|
+
def maximum_query_terms(value)
|
|
46
|
+
@query.more_like_this.maximum_query_terms = value
|
|
47
|
+
end
|
|
48
|
+
alias_method :maxqt, :maximum_query_terms
|
|
49
|
+
|
|
50
|
+
def boost_by_relevance(should_boost)
|
|
51
|
+
@query.more_like_this.boost_by_relevance = should_boost
|
|
52
|
+
end
|
|
53
|
+
alias_method :boost, :boost_by_relevance
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
module DSL #:nodoc
|
|
3
|
+
module Paginatable
|
|
4
|
+
# Paginate your search. This works the same way as WillPaginate's
|
|
5
|
+
# paginate().
|
|
6
|
+
#
|
|
7
|
+
# Note that Solr searches are _always_ paginated. Not calling #paginate is
|
|
8
|
+
# the equivalent of calling:
|
|
9
|
+
#
|
|
10
|
+
# paginate(:page => 1, :per_page => Sunspot.config.pagination.default_per_page)
|
|
11
|
+
#
|
|
12
|
+
# ==== Options (options)
|
|
13
|
+
#
|
|
14
|
+
# :page<Integer,String>:: The requested page. The default is 1.
|
|
15
|
+
#
|
|
16
|
+
# :per_page<Integer,String>::
|
|
17
|
+
# How many results to return per page. The default is the value in
|
|
18
|
+
# +Sunspot.config.pagination.default_per_page+
|
|
19
|
+
#
|
|
20
|
+
def paginate(options = {})
|
|
21
|
+
page = options.delete(:page)
|
|
22
|
+
per_page = options.delete(:per_page)
|
|
23
|
+
raise ArgumentError, "unknown argument #{options.keys.first.inspect} passed to paginate" unless options.empty?
|
|
24
|
+
@query.paginate(page, per_page)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
module DSL
|
|
3
|
+
#
|
|
4
|
+
# This tiny DSL class implements the DSL for the FieldQuery.facet
|
|
5
|
+
# method.
|
|
6
|
+
#
|
|
7
|
+
class QueryFacet
|
|
8
|
+
def initialize(query, setup, facet) #:nodoc:
|
|
9
|
+
@query, @setup, @facet = query, setup, facet
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
#
|
|
13
|
+
# Add a row to this query facet. The label argument can be anything; it's
|
|
14
|
+
# simply the value that's passed into the Sunspot::QueryFacetRow object
|
|
15
|
+
# corresponding to the row that's created. Use whatever seems most
|
|
16
|
+
# intuitive.
|
|
17
|
+
#
|
|
18
|
+
# The block is evaluated in the context of a Sunspot::DSL::Scope, meaning
|
|
19
|
+
# any restrictions can be placed on the documents matching this facet row.
|
|
20
|
+
#
|
|
21
|
+
# ==== Parameters
|
|
22
|
+
#
|
|
23
|
+
# label<Object>::
|
|
24
|
+
# An object used to identify this facet row in the results.
|
|
25
|
+
#
|
|
26
|
+
def row(label, &block)
|
|
27
|
+
query_facet = Sunspot::Query::QueryFacet.new
|
|
28
|
+
Sunspot::Util.instance_eval_or_call(
|
|
29
|
+
Scope.new(@query.add_query_facet(query_facet), @setup),
|
|
30
|
+
&block
|
|
31
|
+
)
|
|
32
|
+
@facet.add_row(label, query_facet.to_boolean_phrase)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
module DSL
|
|
3
|
+
#
|
|
4
|
+
# This class presents an API for building restrictions in the query DSL. The
|
|
5
|
+
# methods exposed are the snake-cased names of the classes defined in the
|
|
6
|
+
# Sunspot::Restriction module, with the exception of Base. All
|
|
7
|
+
# methods take a single argument, which is the value to be applied to the
|
|
8
|
+
# restriction.
|
|
9
|
+
#
|
|
10
|
+
class Restriction
|
|
11
|
+
def initialize(field, scope, negative) #:nodoc:
|
|
12
|
+
@field, @scope, @negative = field, scope, negative
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
Sunspot::Query::Restriction.names.each do |class_name|
|
|
16
|
+
method_name = Util.snake_case(class_name.to_s)
|
|
17
|
+
module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
|
18
|
+
def #{method_name}(*value)
|
|
19
|
+
@scope.add_restriction(@negative, @field, Sunspot::Query::Restriction::#{class_name}, *value)
|
|
20
|
+
end
|
|
21
|
+
RUBY
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
module DSL
|
|
3
|
+
class RestrictionWithNear < Restriction
|
|
4
|
+
def initialize(field, scope, query, negated)
|
|
5
|
+
super(field, scope, negated)
|
|
6
|
+
@query = query
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
#
|
|
10
|
+
# Perform a Geohash-based location restriction for the given `location`
|
|
11
|
+
# field. Though this uses the same API as other attribute-field
|
|
12
|
+
# restrictions, there are several differences between this and other
|
|
13
|
+
# scoping methods:
|
|
14
|
+
#
|
|
15
|
+
# * It can only be called from the top-level query; it cannot be nested
|
|
16
|
+
# in a `dynamic`, `any_of`, or `all_of` block. This is because geohash
|
|
17
|
+
# queries are not sent to Solr as filter queries like other scopes, but
|
|
18
|
+
# rather are part of the fulltext query sent to Solr.
|
|
19
|
+
# * Because it is included with the fulltext query (if any), location
|
|
20
|
+
# restrictions can be given boost. By default, an "exact"
|
|
21
|
+
# (maximum-precision) match will give the result a boost of 1.0; each
|
|
22
|
+
# lower level of precision gives a boost of 1/2 the next highest
|
|
23
|
+
# precision. See below for options to modify this behavior.
|
|
24
|
+
#
|
|
25
|
+
# ==== What is a Geohash?
|
|
26
|
+
#
|
|
27
|
+
# Geohash is a clever algorithm that creates a decodable digest of a
|
|
28
|
+
# geographical point. It does this by dividing the globe into
|
|
29
|
+
# quadrants, encoding the quadrant in which the point sits in the hash,
|
|
30
|
+
# dividing the quadrant into smaller quadrants, and repeating an arbitrary
|
|
31
|
+
# number of times (the "precision"). Because of the way Geohash are
|
|
32
|
+
# built, the shared Geohash prefix length of two locations will
|
|
33
|
+
# <em>usually</em> increase as the distance between the points decreases.
|
|
34
|
+
# Put another way, the geohashes of two nearby points will
|
|
35
|
+
# <em>usually</em> have a longer shared prefix than two points which are
|
|
36
|
+
# distant from one another.
|
|
37
|
+
#
|
|
38
|
+
# Read more about Geohashes on
|
|
39
|
+
# {Wikipedia}[http://en.wikipedia.org/wiki/Geohash] or play around with
|
|
40
|
+
# generating your own at {geohash.org}[http://geohash.org/].
|
|
41
|
+
#
|
|
42
|
+
# In Sunspot, GeoHashes can have a precision between 3 and 12; this is the
|
|
43
|
+
# number of characters in the hash. The precisions have the following
|
|
44
|
+
# maximum bounding box sizes, in miles:
|
|
45
|
+
#
|
|
46
|
+
# <dt>3</dt>
|
|
47
|
+
# <dd>389.07812</dd>
|
|
48
|
+
# <dt>4</dt>
|
|
49
|
+
# <dd>97.26953</dd>
|
|
50
|
+
# <dt>5</dt>
|
|
51
|
+
# <dd>24.31738</dd>
|
|
52
|
+
# <dt>6</dt>
|
|
53
|
+
# <dd>6.07935</dd>
|
|
54
|
+
# <dt>7</dt>
|
|
55
|
+
# <dd>1.51984
|
|
56
|
+
# <dt>8</dt>
|
|
57
|
+
# <dd>0.37996</dd>
|
|
58
|
+
# <dt>9</dt>
|
|
59
|
+
# <dd>0.09499</dd>
|
|
60
|
+
# <dt>10</dt>
|
|
61
|
+
# <dd>0.02375</dd>
|
|
62
|
+
# <dt>11</dt>
|
|
63
|
+
# <dd>0.00594</dd>
|
|
64
|
+
# <dt>12</dt>
|
|
65
|
+
# <dd>0.00148</dd>
|
|
66
|
+
#
|
|
67
|
+
# ==== Score, boost, and sorting with location search
|
|
68
|
+
#
|
|
69
|
+
# The concept of relevance scoring is a familiar one from fulltext search;
|
|
70
|
+
# Solr (or Lucene, actually) gives each result document a score based on
|
|
71
|
+
# how relevant the document's text is to the search phrase. Sunspot's
|
|
72
|
+
# location search also uses scoring to determine geographical relevance;
|
|
73
|
+
# using boosts, longer prefix matches (which are, in general,
|
|
74
|
+
# geographically closer to the search origin) are assigned higher
|
|
75
|
+
# relevance. This means that the results of a pure location search are
|
|
76
|
+
# <em>roughly</em> in order of geographical distance, as long as no other
|
|
77
|
+
# sort is specified explicitly.
|
|
78
|
+
#
|
|
79
|
+
# This geographical relevance plays on the same field as fulltext scoring;
|
|
80
|
+
# if you use both fulltext and geographical components in a single search,
|
|
81
|
+
# both types of relevance will be taken into account when scoring the
|
|
82
|
+
# matches. Thus, a very close fulltext match that's further away from the
|
|
83
|
+
# geographical origin will be scored similarly to a less precise fulltext
|
|
84
|
+
# match that is very close to the geographical origin. That's likely to be
|
|
85
|
+
# consistent with the way most users would expect a fulltext geographical
|
|
86
|
+
# search to work.
|
|
87
|
+
#
|
|
88
|
+
# ==== Options
|
|
89
|
+
#
|
|
90
|
+
# <dt><code>:precision</code></dt>
|
|
91
|
+
# <dd>The minimum precision at which locations should match. See the table
|
|
92
|
+
# of precisions and bounding-box sizes above; the proximity value will
|
|
93
|
+
# ensure that all matching documents share a bounding box of the
|
|
94
|
+
# corresponding maximum size with your origin point. The default value
|
|
95
|
+
# is 7, meaning all results will share a bounding box with edges of
|
|
96
|
+
# about one and a half miles with the origin.</dd>
|
|
97
|
+
# <dt><code>:boost</code></dt>
|
|
98
|
+
# <dd>The boost to apply to maximum-precision matches. Default is 1.0. You
|
|
99
|
+
# can use this option to adjust the weight given to geographic
|
|
100
|
+
# proximity versus fulltext matching, if you are doing both in a
|
|
101
|
+
# search.</dd>
|
|
102
|
+
# <dt><code>:precision_factor</code></dt>
|
|
103
|
+
# <dd>This option determines how much boost is applied to matches at lower
|
|
104
|
+
# precisions. The default value, 16.0, means that a match at precision
|
|
105
|
+
# N is 1/16 as relevant as a match at precision N+1 (this is consistent
|
|
106
|
+
# with the fact that each precision's bounding box is about sixteen
|
|
107
|
+
# times the size of the next highest precision.)</dd>
|
|
108
|
+
#
|
|
109
|
+
# ==== Example
|
|
110
|
+
#
|
|
111
|
+
# Sunspot.search(Post) do
|
|
112
|
+
# fulltext('pizza')
|
|
113
|
+
# with(:location).near(-40.0, -70.0, :boost => 2, :precision => 6)
|
|
114
|
+
# end
|
|
115
|
+
#
|
|
116
|
+
def near(lat, lng, options = {})
|
|
117
|
+
@query.fulltext.add_location(@field, lat, lng, options)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
module DSL #:nodoc:
|
|
3
|
+
#
|
|
4
|
+
# This DSL presents methods for constructing restrictions and other query
|
|
5
|
+
# elements that are specific to fields. As well as being a superclass of
|
|
6
|
+
# Sunspot::DSL::Query, which presents the main query block, this DSL class
|
|
7
|
+
# is also used directly inside the #dynamic() block, which only allows
|
|
8
|
+
# operations on specific fields.
|
|
9
|
+
#
|
|
10
|
+
class Scope
|
|
11
|
+
NONE = Object.new
|
|
12
|
+
|
|
13
|
+
def initialize(scope, setup) #:nodoc:
|
|
14
|
+
@scope, @setup = scope, setup
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
#
|
|
18
|
+
# Build a positive restriction. This method can take three forms: equality
|
|
19
|
+
# restriction, restriction by another restriction, or identity
|
|
20
|
+
# restriction.
|
|
21
|
+
# In the first two forms, the first argument is a field name. If only a
|
|
22
|
+
# field name is specified, this method returns another DSL object which
|
|
23
|
+
# presents methods for attaching various restriction types.
|
|
24
|
+
# With two arguments, this creates a shorthand restriction: if the second
|
|
25
|
+
# argument is a scalar, an equality restriction is created; if it is a
|
|
26
|
+
# Range, a between restriction will be created; and if it is an Array, an
|
|
27
|
+
# any_of restriction will be created.
|
|
28
|
+
# The third from restricts the search results to a specific instance.
|
|
29
|
+
#
|
|
30
|
+
# ==== Parameters (restriction by field value)
|
|
31
|
+
#
|
|
32
|
+
# field_name<Symbol>:: Name of the field on which to place the restriction
|
|
33
|
+
# value<Object,Range,Array>::
|
|
34
|
+
# If passed, creates an equality, range, or any-of restriction based on
|
|
35
|
+
# the type of value passed.
|
|
36
|
+
#
|
|
37
|
+
# ==== Parameters (restriction by identity)
|
|
38
|
+
#
|
|
39
|
+
# args<Object>...::
|
|
40
|
+
# One or more instances that should be included in the results
|
|
41
|
+
#
|
|
42
|
+
# ==== Returns
|
|
43
|
+
#
|
|
44
|
+
# Sunspot::DSL::Query::Restriction::
|
|
45
|
+
# Restriction DSL object (if only one argument is passed which is a
|
|
46
|
+
# field name)
|
|
47
|
+
#
|
|
48
|
+
# ==== Examples
|
|
49
|
+
#
|
|
50
|
+
# An equality restriction:
|
|
51
|
+
#
|
|
52
|
+
# Sunspot.search do
|
|
53
|
+
# with(:blog_id, 1)
|
|
54
|
+
# end
|
|
55
|
+
#
|
|
56
|
+
# Restrict by range:
|
|
57
|
+
#
|
|
58
|
+
# Sunspot.search do
|
|
59
|
+
# with(:average_rating, 3.0..5.0)
|
|
60
|
+
# end
|
|
61
|
+
#
|
|
62
|
+
# Restrict by a set of allowed values:
|
|
63
|
+
#
|
|
64
|
+
# Sunspot.search do
|
|
65
|
+
# with(:category_ids, [1, 5, 9])
|
|
66
|
+
# end
|
|
67
|
+
#
|
|
68
|
+
# Other restriction types:
|
|
69
|
+
#
|
|
70
|
+
# Sunspot.search(Post) do
|
|
71
|
+
# with(:average_rating).greater_than(3.0)
|
|
72
|
+
# end
|
|
73
|
+
#
|
|
74
|
+
# Restriction by identity:
|
|
75
|
+
#
|
|
76
|
+
# Sunspot.search(Post) do
|
|
77
|
+
# with(some_post_instance)
|
|
78
|
+
# end
|
|
79
|
+
#
|
|
80
|
+
def with(*args)
|
|
81
|
+
add_restriction(false, *args)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
#
|
|
85
|
+
# Build a negative restriction (exclusion). This method works the same way
|
|
86
|
+
# asthe #with method.
|
|
87
|
+
#
|
|
88
|
+
def without(*args)
|
|
89
|
+
add_restriction(true, *args)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
#
|
|
93
|
+
# Create a disjunction, scoping the results to documents that match any
|
|
94
|
+
# of the enclosed restrictions.
|
|
95
|
+
#
|
|
96
|
+
# ==== Example
|
|
97
|
+
#
|
|
98
|
+
# Sunspot.search(Post) do
|
|
99
|
+
# any_of do
|
|
100
|
+
# with(:expired_at).greater_than Time.now
|
|
101
|
+
# with :expired_at, nil
|
|
102
|
+
# end
|
|
103
|
+
# end
|
|
104
|
+
#
|
|
105
|
+
# This will return all documents who either have an expiration time in the
|
|
106
|
+
# future, or who do not have any expiration time at all.
|
|
107
|
+
#
|
|
108
|
+
def any_of(&block)
|
|
109
|
+
disjunction = @scope.add_disjunction
|
|
110
|
+
Util.instance_eval_or_call(Scope.new(disjunction, @setup), &block)
|
|
111
|
+
disjunction
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
#
|
|
115
|
+
# Create a conjunction, scoping the results to documents that match all of
|
|
116
|
+
# the enclosed restrictions. When called from the top level of a search
|
|
117
|
+
# block, this has no effect, but can be useful for grouping a conjunction
|
|
118
|
+
# inside a disjunction.
|
|
119
|
+
#
|
|
120
|
+
# ==== Example
|
|
121
|
+
#
|
|
122
|
+
# Sunspot.search(Post) do
|
|
123
|
+
# any_of do
|
|
124
|
+
# with(:blog_id, 1)
|
|
125
|
+
# all_of do
|
|
126
|
+
# with(:blog_id, 2)
|
|
127
|
+
# with(:category_ids, 3)
|
|
128
|
+
# end
|
|
129
|
+
# end
|
|
130
|
+
# end
|
|
131
|
+
#
|
|
132
|
+
def all_of(&block)
|
|
133
|
+
conjunction = @scope.add_conjunction
|
|
134
|
+
Util.instance_eval_or_call(Scope.new(conjunction, @setup), &block)
|
|
135
|
+
conjunction
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
#
|
|
139
|
+
# Apply restrictions, facets, and ordering to dynamic field instances.
|
|
140
|
+
# The block API is implemented by Sunspot::DSL::FieldQuery, which is a
|
|
141
|
+
# superclass of the Query DSL (thus providing a subset of the API, in
|
|
142
|
+
# particular only methods that refer to particular fields).
|
|
143
|
+
#
|
|
144
|
+
# ==== Parameters
|
|
145
|
+
#
|
|
146
|
+
# base_name<Symbol>:: The base name for the dynamic field definition
|
|
147
|
+
#
|
|
148
|
+
# ==== Example
|
|
149
|
+
#
|
|
150
|
+
# Sunspot.search Post do
|
|
151
|
+
# dynamic :custom do
|
|
152
|
+
# with :cuisine, 'Pizza'
|
|
153
|
+
# facet :atmosphere
|
|
154
|
+
# order_by :chef_name
|
|
155
|
+
# end
|
|
156
|
+
# end
|
|
157
|
+
#
|
|
158
|
+
def dynamic(base_name, &block)
|
|
159
|
+
Sunspot::Util.instance_eval_or_call(
|
|
160
|
+
Scope.new(@scope, @setup.dynamic_field_factory(base_name)),
|
|
161
|
+
&block
|
|
162
|
+
)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
#
|
|
166
|
+
# Apply scope-type restrictions on fulltext fields. In certain situations,
|
|
167
|
+
# it may be desirable to place logical restrictions on text fields.
|
|
168
|
+
# Remember that text fields are tokenized; your mileage may very.
|
|
169
|
+
#
|
|
170
|
+
# The block works exactly like a normal scope, except that the field names
|
|
171
|
+
# refer to text fields instead of attribute fields.
|
|
172
|
+
#
|
|
173
|
+
# === Example
|
|
174
|
+
#
|
|
175
|
+
# Sunspot.search(Post) do
|
|
176
|
+
# text_fields do
|
|
177
|
+
# with :body, nil
|
|
178
|
+
# end
|
|
179
|
+
# end
|
|
180
|
+
#
|
|
181
|
+
# This will return all documents that do not have a body.
|
|
182
|
+
#
|
|
183
|
+
def text_fields(&block)
|
|
184
|
+
Sunspot::Util.instance_eval_or_call(
|
|
185
|
+
Scope.new(@scope, TextFieldSetup.new(@setup)),
|
|
186
|
+
&block
|
|
187
|
+
)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
private
|
|
191
|
+
|
|
192
|
+
def add_restriction(negated, *args)
|
|
193
|
+
case args.first
|
|
194
|
+
when String, Symbol
|
|
195
|
+
raise ArgumentError if args.length > 2
|
|
196
|
+
field_name = args[0]
|
|
197
|
+
value = args.length > 1 ? args[1] : NONE
|
|
198
|
+
if value == NONE
|
|
199
|
+
DSL::Restriction.new(@setup.field(field_name.to_sym), @scope, negated)
|
|
200
|
+
else
|
|
201
|
+
@scope.add_shorthand_restriction(negated, @setup.field(field_name.to_sym), value)
|
|
202
|
+
end
|
|
203
|
+
else
|
|
204
|
+
instances = args.flatten
|
|
205
|
+
@scope.add_restriction(
|
|
206
|
+
negated,
|
|
207
|
+
IdField.instance,
|
|
208
|
+
Sunspot::Query::Restriction::AnyOf,
|
|
209
|
+
instances.flatten.map { |instance|
|
|
210
|
+
Sunspot::Adapters::InstanceAdapter.adapt(instance).index_id }
|
|
211
|
+
)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
module DSL
|
|
3
|
+
#
|
|
4
|
+
# This top-level DSL class is the context in which the block passed to
|
|
5
|
+
# Sunspot.query. See Sunspot::DSL::Query, Sunspot::DSL::FieldQuery, and
|
|
6
|
+
# Sunspot::DSL::Scope for the full API presented.
|
|
7
|
+
#
|
|
8
|
+
class Search < StandardQuery
|
|
9
|
+
def initialize(search, setup) #:nodoc:
|
|
10
|
+
@search = search
|
|
11
|
+
super(search, search.query, setup)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
#
|
|
15
|
+
# Retrieve the data accessor used to load instances of the given class
|
|
16
|
+
# out of persistent storage. Data accessors are free to implement any
|
|
17
|
+
# extra methods that may be useful in this context.
|
|
18
|
+
#
|
|
19
|
+
# ==== Example
|
|
20
|
+
#
|
|
21
|
+
# Sunspot.search Post do
|
|
22
|
+
# data_acccessor_for(Post).includes = [:blog, :comments]
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
def data_accessor_for(clazz)
|
|
26
|
+
@search.data_accessor_for(clazz)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
module DSL #:nodoc:
|
|
3
|
+
#
|
|
4
|
+
# This class presents a DSL for constructing queries using the
|
|
5
|
+
# Sunspot.search method. Methods of this class are available inside the
|
|
6
|
+
# search block. Much of the DSL's functionality is implemented by this
|
|
7
|
+
# class's superclasses, Sunspot::DSL::FieldQuery and Sunspot::DSL::Scope
|
|
8
|
+
#
|
|
9
|
+
# See Sunspot.search for usage examples
|
|
10
|
+
#
|
|
11
|
+
class StandardQuery < FieldQuery
|
|
12
|
+
include Paginatable, Adjustable
|
|
13
|
+
|
|
14
|
+
# Specify a phrase that should be searched as fulltext. Only +text+
|
|
15
|
+
# fields are searched - see DSL::Fields.text
|
|
16
|
+
#
|
|
17
|
+
# Keyword search is executed using Solr's dismax handler, which strikes
|
|
18
|
+
# a good balance between powerful and foolproof. In particular,
|
|
19
|
+
# well-matched quotation marks can be used to group phrases, and the
|
|
20
|
+
# + and - modifiers work as expected. All other special Solr boolean
|
|
21
|
+
# syntax is escaped, and mismatched quotes are ignored entirely.
|
|
22
|
+
#
|
|
23
|
+
# This method can optionally take a block, which is evaluated by the
|
|
24
|
+
# Fulltext DSL class, and exposes several powerful dismax features.
|
|
25
|
+
#
|
|
26
|
+
# ==== Parameters
|
|
27
|
+
#
|
|
28
|
+
# keywords<String>:: phrase to perform fulltext search on
|
|
29
|
+
#
|
|
30
|
+
# ==== Options
|
|
31
|
+
#
|
|
32
|
+
# :fields<Array>::
|
|
33
|
+
# List of fields that should be searched for keywords. Defaults to all
|
|
34
|
+
# fields configured for the types under search.
|
|
35
|
+
# :highlight<Boolean,Array>::
|
|
36
|
+
# If true, perform keyword highlighting on all searched fields. If an
|
|
37
|
+
# array of field names, perform highlighting on the specified fields.
|
|
38
|
+
# This can also be called from within the fulltext block.
|
|
39
|
+
# :minimum_match<Integer>::
|
|
40
|
+
# The minimum number of search terms that a result must match. By
|
|
41
|
+
# default, all search terms must match; if the number of search terms
|
|
42
|
+
# is less than this number, the default behavior applies.
|
|
43
|
+
# :tie<Float>::
|
|
44
|
+
# A tiebreaker coefficient for scores derived from subqueries that are
|
|
45
|
+
# lower-scoring than the maximum score subquery. Typically a near-zero
|
|
46
|
+
# value is useful. See
|
|
47
|
+
# http://wiki.apache.org/solr/DisMaxRequestHandler#tie_.28Tie_breaker.29
|
|
48
|
+
# for more information.
|
|
49
|
+
# :query_phrase_slop<Integer>::
|
|
50
|
+
# The number of words that can appear between the words in a
|
|
51
|
+
# user-entered phrase (i.e., keywords in quotes) and still match. For
|
|
52
|
+
# instance, in a search for "\"great pizza\"" with a phrase slop of 1,
|
|
53
|
+
# "great pizza" and "great big pizza" will match, but "great monster of
|
|
54
|
+
# a pizza" will not. Default behavior is a query phrase slop of zero.
|
|
55
|
+
#
|
|
56
|
+
def fulltext(keywords, options = {}, &block)
|
|
57
|
+
if keywords && !(keywords.to_s =~ /^\s*$/)
|
|
58
|
+
fulltext_query = @query.add_fulltext(keywords)
|
|
59
|
+
if field_names = options.delete(:fields)
|
|
60
|
+
Util.Array(field_names).each do |field_name|
|
|
61
|
+
@setup.text_fields(field_name).each do |field|
|
|
62
|
+
fulltext_query.add_fulltext_field(field, field.default_boost)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
if minimum_match = options.delete(:minimum_match)
|
|
67
|
+
fulltext_query.minimum_match = minimum_match.to_i
|
|
68
|
+
end
|
|
69
|
+
if tie = options.delete(:tie)
|
|
70
|
+
fulltext_query.tie = tie.to_f
|
|
71
|
+
end
|
|
72
|
+
if query_phrase_slop = options.delete(:query_phrase_slop)
|
|
73
|
+
fulltext_query.query_phrase_slop = query_phrase_slop.to_i
|
|
74
|
+
end
|
|
75
|
+
if highlight_field_names = options.delete(:highlight)
|
|
76
|
+
if highlight_field_names == true
|
|
77
|
+
fulltext_query.add_highlight
|
|
78
|
+
else
|
|
79
|
+
highlight_fields = []
|
|
80
|
+
Util.Array(highlight_field_names).each do |field_name|
|
|
81
|
+
highlight_fields.concat(@setup.text_fields(field_name))
|
|
82
|
+
end
|
|
83
|
+
fulltext_query.add_highlight(highlight_fields)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
if block && fulltext_query
|
|
87
|
+
fulltext_dsl = Fulltext.new(fulltext_query, @setup)
|
|
88
|
+
Util.instance_eval_or_call(
|
|
89
|
+
fulltext_dsl,
|
|
90
|
+
&block
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
if !field_names && (!fulltext_dsl || !fulltext_dsl.fields_added?)
|
|
94
|
+
@setup.all_text_fields.each do |field|
|
|
95
|
+
unless fulltext_query.has_fulltext_field?(field)
|
|
96
|
+
unless fulltext_dsl && fulltext_dsl.exclude_fields.include?(field.name)
|
|
97
|
+
fulltext_query.add_fulltext_field(field, field.default_boost)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
alias_method :keywords, :fulltext
|
|
105
|
+
|
|
106
|
+
def with(*args)
|
|
107
|
+
case args.first
|
|
108
|
+
when String, Symbol
|
|
109
|
+
field_name = args[0]
|
|
110
|
+
value = args.length > 1 ? args[1] : Scope::NONE
|
|
111
|
+
if value == Scope::NONE
|
|
112
|
+
return DSL::RestrictionWithNear.new(@setup.field(field_name.to_sym), @scope, @query, false)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# else
|
|
117
|
+
super
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
data/lib/sunspot/dsl.rb
ADDED