outoftime-sunspot 0.8.9 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +13 -21
- data/Rakefile +0 -2
- data/TODO +2 -15
- data/VERSION.yml +2 -2
- data/bin/sunspot-configure-solr +46 -0
- data/bin/sunspot-solr +15 -7
- data/lib/sunspot/adapters.rb +5 -1
- data/lib/sunspot/composite_setup.rb +186 -0
- data/lib/sunspot/configuration.rb +7 -1
- data/lib/sunspot/data_extractor.rb +10 -0
- data/lib/sunspot/date_facet.rb +36 -0
- data/lib/sunspot/date_facet_row.rb +17 -0
- data/lib/sunspot/dsl/field_query.rb +72 -0
- data/lib/sunspot/dsl/fields.rb +30 -3
- data/lib/sunspot/dsl/query.rb +16 -35
- data/lib/sunspot/dsl/query_facet.rb +31 -0
- data/lib/sunspot/dsl/scope.rb +76 -20
- data/lib/sunspot/dsl/search.rb +30 -0
- data/lib/sunspot/dsl.rb +1 -1
- data/lib/sunspot/facet.rb +17 -3
- data/lib/sunspot/facet_row.rb +4 -4
- data/lib/sunspot/field.rb +130 -207
- data/lib/sunspot/field_factory.rb +126 -0
- data/lib/sunspot/indexer.rb +61 -14
- data/lib/sunspot/instantiated_facet.rb +38 -0
- data/lib/sunspot/instantiated_facet_row.rb +12 -0
- data/lib/sunspot/query/base_query.rb +90 -0
- data/lib/sunspot/query/connective.rb +77 -0
- data/lib/sunspot/query/dynamic_query.rb +39 -56
- data/lib/sunspot/query/field_facet.rb +132 -4
- data/lib/sunspot/query/field_query.rb +57 -0
- data/lib/sunspot/query/pagination.rb +1 -1
- data/lib/sunspot/query/query_facet.rb +72 -0
- data/lib/sunspot/query/query_facet_row.rb +19 -0
- data/lib/sunspot/query/restriction.rb +9 -7
- data/lib/sunspot/query/scope.rb +165 -0
- data/lib/sunspot/query/sort.rb +17 -14
- data/lib/sunspot/query/sort_composite.rb +33 -0
- data/lib/sunspot/query.rb +162 -351
- data/lib/sunspot/query_facet.rb +33 -0
- data/lib/sunspot/query_facet_row.rb +21 -0
- data/lib/sunspot/schema.rb +165 -0
- data/lib/sunspot/search/hit.rb +62 -0
- data/lib/sunspot/search.rb +104 -41
- data/lib/sunspot/session.rb +64 -32
- data/lib/sunspot/setup.rb +119 -48
- data/lib/sunspot/type.rb +48 -2
- data/lib/sunspot.rb +74 -8
- data/solr/solr/conf/schema.xml +44 -225
- data/spec/api/build_search_spec.rb +557 -63
- data/spec/api/indexer_spec.rb +156 -74
- data/spec/api/query_spec.rb +55 -31
- data/spec/api/search_retrieval_spec.rb +210 -33
- data/spec/api/session_spec.rb +81 -26
- data/spec/api/sunspot_spec.rb +5 -7
- data/spec/integration/faceting_spec.rb +130 -0
- data/spec/integration/keyword_search_spec.rb +72 -31
- data/spec/integration/scoped_search_spec.rb +13 -0
- data/spec/integration/stored_fields_spec.rb +10 -0
- data/spec/mocks/blog.rb +3 -0
- data/spec/mocks/comment.rb +12 -23
- data/spec/mocks/connection.rb +84 -0
- data/spec/mocks/mock_adapter.rb +11 -3
- data/spec/mocks/mock_record.rb +41 -0
- data/spec/mocks/photo.rb +8 -0
- data/spec/mocks/post.rb +18 -23
- data/spec/spec_helper.rb +29 -14
- data/tasks/gemspec.rake +4 -3
- data/tasks/rdoc.rake +2 -2
- data/tasks/schema.rake +19 -0
- data/templates/schema.xml.haml +24 -0
- metadata +48 -7
- data/spec/mocks/base_class.rb +0 -2
@@ -0,0 +1,31 @@
|
|
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_facet) #:nodoc:
|
9
|
+
@query_facet = query_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
|
+
Scope.new(@query_facet.add_row(label)).instance_eval(&block)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/sunspot/dsl/scope.rb
CHANGED
@@ -17,14 +17,17 @@ module Sunspot
|
|
17
17
|
#
|
18
18
|
# Build a positive restriction. With one argument, this method returns
|
19
19
|
# another DSL object which presents methods for attaching various
|
20
|
-
# restriction types. With two arguments,
|
21
|
-
# an equality restriction
|
20
|
+
# restriction types. With two arguments, this creates a shorthand
|
21
|
+
# restriction: if the second argument is a scalar, an equality restriction
|
22
|
+
# is created; if it is a Range, a between restriction will be created; and
|
23
|
+
# if it is an Array, an any_of restriction will be created.
|
22
24
|
#
|
23
25
|
# ==== Parameters
|
24
26
|
#
|
25
27
|
# field_name<Symbol>:: Name of the field on which to place the restriction
|
26
|
-
# value<
|
27
|
-
# If passed, creates an equality restriction
|
28
|
+
# value<Object,Range,Array>::
|
29
|
+
# If passed, creates an equality, range, or any-of restriction based on
|
30
|
+
# the type of value passed.
|
28
31
|
#
|
29
32
|
# ==== Returns
|
30
33
|
#
|
@@ -38,6 +41,18 @@ module Sunspot
|
|
38
41
|
# Sunspot.search do
|
39
42
|
# with(:blog_id, 1)
|
40
43
|
# end
|
44
|
+
#
|
45
|
+
# Restrict by range:
|
46
|
+
#
|
47
|
+
# Sunspot.search do
|
48
|
+
# with(:average_rating, 3.0..5.0)
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# Restrict by a set of allowed values:
|
52
|
+
#
|
53
|
+
# Sunspot.search do
|
54
|
+
# with(:category_ids, [1, 5, 9])
|
55
|
+
# end
|
41
56
|
#
|
42
57
|
# Other restriction types:
|
43
58
|
#
|
@@ -49,7 +64,7 @@ module Sunspot
|
|
49
64
|
if value == NONE
|
50
65
|
DSL::Restriction.new(field_name.to_sym, @query, false)
|
51
66
|
else
|
52
|
-
@query.
|
67
|
+
@query.add_shorthand_restriction(field_name, value)
|
53
68
|
end
|
54
69
|
end
|
55
70
|
|
@@ -98,7 +113,7 @@ module Sunspot
|
|
98
113
|
if value == NONE
|
99
114
|
DSL::Restriction.new(field_name.to_sym, @query, true)
|
100
115
|
else
|
101
|
-
@query.
|
116
|
+
@query.add_negated_shorthand_restriction(field_name, value)
|
102
117
|
end
|
103
118
|
else
|
104
119
|
instances = args
|
@@ -108,29 +123,70 @@ module Sunspot
|
|
108
123
|
end
|
109
124
|
end
|
110
125
|
|
111
|
-
#
|
112
|
-
#
|
126
|
+
#
|
127
|
+
# Create a disjunction, scoping the results to documents that match any
|
128
|
+
# of the enclosed restrictions.
|
113
129
|
#
|
114
|
-
# ====
|
130
|
+
# ==== Example
|
131
|
+
#
|
132
|
+
# Sunspot.search(Post) do
|
133
|
+
# any_of do
|
134
|
+
# with(:expired_at).greater_than Time.now
|
135
|
+
# with :expired_at, nil
|
136
|
+
# end
|
137
|
+
# end
|
115
138
|
#
|
116
|
-
#
|
117
|
-
#
|
139
|
+
# This will return all documents who either have an expiration time in the
|
140
|
+
# future, or who do not have any expiration time at all.
|
118
141
|
#
|
119
|
-
def
|
120
|
-
@query.
|
142
|
+
def any_of(&block)
|
143
|
+
Util.instance_eval_or_call(Scope.new(@query.add_disjunction), &block)
|
121
144
|
end
|
122
145
|
|
123
|
-
#
|
124
|
-
#
|
146
|
+
#
|
147
|
+
# Create a conjunction, scoping the results to documents that match all of
|
148
|
+
# the enclosed restrictions. When called from the top level of a search
|
149
|
+
# block, this has no effect, but can be useful for grouping a conjunction
|
150
|
+
# inside a disjunction.
|
151
|
+
#
|
152
|
+
# ==== Example
|
125
153
|
#
|
154
|
+
# Sunspot.search(Post) do
|
155
|
+
# any_of do
|
156
|
+
# with(:blog_id, 1)
|
157
|
+
# all_of do
|
158
|
+
# with(:blog_id, 2)
|
159
|
+
# with(:category_ids, 3)
|
160
|
+
# end
|
161
|
+
# end
|
162
|
+
# end
|
163
|
+
#
|
164
|
+
def all_of(&block)
|
165
|
+
Util.instance_eval_or_call(Scope.new(@query.add_conjunction), &block)
|
166
|
+
end
|
167
|
+
|
168
|
+
#
|
169
|
+
# Apply restrictions, facets, and ordering to dynamic field instances.
|
170
|
+
# The block API is implemented by Sunspot::DSL::FieldQuery, which is a
|
171
|
+
# superclass of the Query DSL (thus providing a subset of the API, in
|
172
|
+
# particular only methods that refer to particular fields).
|
173
|
+
#
|
126
174
|
# ==== Parameters
|
175
|
+
#
|
176
|
+
# base_name<Symbol>:: The base name for the dynamic field definition
|
127
177
|
#
|
128
|
-
#
|
178
|
+
# ==== Example
|
129
179
|
#
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
180
|
+
# Sunspot.search Post do
|
181
|
+
# dynamic :custom do
|
182
|
+
# with :cuisine, 'Pizza'
|
183
|
+
# facet :atmosphere
|
184
|
+
# order_by :chef_name
|
185
|
+
# end
|
186
|
+
# end
|
187
|
+
#
|
188
|
+
def dynamic(base_name, &block)
|
189
|
+
FieldQuery.new(@query.dynamic_query(base_name)).instance_eval(&block)
|
134
190
|
end
|
135
191
|
end
|
136
192
|
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 < Query
|
9
|
+
def initialize(search) #:nodoc:
|
10
|
+
@search = search
|
11
|
+
@query = search.query
|
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
|
data/lib/sunspot/dsl.rb
CHANGED
data/lib/sunspot/facet.rb
CHANGED
@@ -1,12 +1,16 @@
|
|
1
|
+
require 'enumerator'
|
2
|
+
|
1
3
|
module Sunspot
|
2
4
|
#
|
3
5
|
# The facet class encapsulates the information returned by Solr for a
|
4
|
-
#
|
6
|
+
# field facet request.
|
5
7
|
#
|
6
8
|
# See http://wiki.apache.org/solr/SolrFacetingOverview for more information
|
7
9
|
# on Solr's faceting capabilities.
|
8
10
|
#
|
9
11
|
class Facet
|
12
|
+
attr_reader :field
|
13
|
+
|
10
14
|
def initialize(facet_values, field) #:nodoc:
|
11
15
|
@facet_values, @field = facet_values, field
|
12
16
|
end
|
@@ -29,9 +33,19 @@ module Sunspot
|
|
29
33
|
#
|
30
34
|
def rows
|
31
35
|
@rows ||=
|
32
|
-
|
33
|
-
|
36
|
+
begin
|
37
|
+
rows = []
|
38
|
+
@facet_values.each_slice(2) do |pair|
|
39
|
+
rows << new_row(pair)
|
40
|
+
end
|
41
|
+
rows
|
34
42
|
end
|
35
43
|
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def new_row(pair)
|
48
|
+
FacetRow.new(pair, self)
|
49
|
+
end
|
36
50
|
end
|
37
51
|
end
|
data/lib/sunspot/facet_row.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
module Sunspot
|
2
2
|
# This class encapsulates a facet row (value) for a facet.
|
3
3
|
class FacetRow
|
4
|
-
def initialize(
|
5
|
-
@
|
4
|
+
def initialize(pair, facet) #:nodoc:
|
5
|
+
@pair, @facet = pair, facet
|
6
6
|
end
|
7
7
|
|
8
8
|
# The value associated with the facet. This will be cast according to the
|
@@ -17,7 +17,7 @@ module Sunspot
|
|
17
17
|
# Object:: The value associated with the row, cast to the appropriate type
|
18
18
|
#
|
19
19
|
def value
|
20
|
-
@value ||= @field.cast(@
|
20
|
+
@value ||= @facet.field.cast(@pair[0])
|
21
21
|
end
|
22
22
|
|
23
23
|
# The number of documents matching the search parameters that have this
|
@@ -28,7 +28,7 @@ module Sunspot
|
|
28
28
|
# Integer:: Document count for this value
|
29
29
|
#
|
30
30
|
def count
|
31
|
-
@count ||= @
|
31
|
+
@count ||= @pair[1]
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
data/lib/sunspot/field.rb
CHANGED
@@ -1,234 +1,157 @@
|
|
1
1
|
module Sunspot
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
# setting up the field, such as field name, type of access, data type,
|
8
|
-
# whether multiple values are allowed, etc.
|
9
|
-
# They are also capable of extracting data from a model in a format
|
10
|
-
# that can be passed directly to the indexer.
|
11
|
-
# Field instances::
|
12
|
-
# Field instances represent an actual field in Solr; thus, they are able to
|
13
|
-
# return the indexed field name, convert the value to its appropriate type,
|
14
|
-
# etc.
|
15
|
-
#
|
16
|
-
# StaticField objects play both the definition and the instance role.
|
17
|
-
# DynamicField objects act only as definitions, and spawn DynamicFieldInstance
|
18
|
-
# objects to play the instance role.
|
19
|
-
#
|
20
|
-
module Field #:nodoc: all
|
21
|
-
#
|
22
|
-
# The FieldInstance module encapsulates functionality associated with
|
23
|
-
# acting as a concrete instance of a field for the purposes of search.
|
24
|
-
# In particular, FieldInstances need to be able to return indexed names,
|
25
|
-
# convert values to their indexed representation, and cast returned values
|
26
|
-
# to the appropriate native Ruby type.
|
27
|
-
#
|
28
|
-
module FieldInstance
|
29
|
-
# The name of the field as it is indexed in Solr. The indexed name
|
30
|
-
# contains a suffix that contains information about the type as well as
|
31
|
-
# whether the field allows multiple values for a document.
|
32
|
-
#
|
33
|
-
# ==== Returns
|
34
|
-
#
|
35
|
-
# String:: The field's indexed name
|
36
|
-
#
|
37
|
-
def indexed_name
|
38
|
-
"#{@type.indexed_name(name)}#{'m' if @multiple}"
|
39
|
-
end
|
40
|
-
|
41
|
-
# Convert a value to its representation for Solr indexing. This delegates
|
42
|
-
# to the #to_indexed method on the field's type.
|
43
|
-
#
|
44
|
-
# ==== Parameters
|
45
|
-
#
|
46
|
-
# value<Object>:: Value to convert to Solr representation
|
47
|
-
#
|
48
|
-
# ==== Returns
|
49
|
-
#
|
50
|
-
# String:: Solr representation of the object
|
51
|
-
#
|
52
|
-
# ==== Raises
|
53
|
-
#
|
54
|
-
# ArgumentError::
|
55
|
-
# the value is an array, but this field does not allow multiple values
|
56
|
-
#
|
57
|
-
def to_indexed(value)
|
58
|
-
if value.is_a? Array
|
59
|
-
if @multiple
|
60
|
-
value.map { |val| to_indexed(val) }
|
61
|
-
else
|
62
|
-
raise ArgumentError, "#{name} is not a multiple-value field, so it cannot index values #{value.inspect}"
|
63
|
-
end
|
64
|
-
else
|
65
|
-
@type.to_indexed(value)
|
66
|
-
end
|
67
|
-
end
|
2
|
+
class Field #:nodoc:
|
3
|
+
attr_accessor :name # The public-facing name of the field
|
4
|
+
attr_accessor :type # The Type of the field
|
5
|
+
attr_accessor :reference # Model class that the value of this field refers to
|
6
|
+
attr_accessor :attributes
|
68
7
|
|
69
|
-
# Cast the value into the appropriate Ruby class for the field's type
|
70
|
-
#
|
71
|
-
# ==== Parameters
|
72
|
-
#
|
73
|
-
# value<String>:: Solr's representation of the value
|
74
|
-
#
|
75
|
-
# ==== Returns
|
76
|
-
#
|
77
|
-
# Object:: The cast value
|
78
|
-
#
|
79
|
-
def cast(value)
|
80
|
-
@type.cast(value)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
#
|
85
|
-
# This module adds a (class) method for building a field definition given
|
86
|
-
# a standard set of arguments
|
87
8
|
#
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
#
|
93
|
-
def build(name, type, options = {}, &block)
|
94
|
-
data_extractor =
|
95
|
-
if block
|
96
|
-
DataExtractor::BlockExtractor.new(&block)
|
97
|
-
else
|
98
|
-
DataExtractor::AttributeExtractor.new(options.delete(:using) || name)
|
99
|
-
end
|
100
|
-
new(name, type, data_extractor, options)
|
101
|
-
end
|
9
|
+
#
|
10
|
+
def initialize(name, type) #:nodoc
|
11
|
+
@name, @type = name.to_sym, type
|
12
|
+
@attributes = {}
|
102
13
|
end
|
103
14
|
|
15
|
+
# Convert a value to its representation for Solr indexing. This delegates
|
16
|
+
# to the #to_indexed method on the field's type.
|
104
17
|
#
|
105
|
-
#
|
106
|
-
# for search and indexing. They expose methods that are useful for both
|
107
|
-
# operations.
|
18
|
+
# ==== Parameters
|
108
19
|
#
|
109
|
-
#
|
20
|
+
# value<Object>:: Value to convert to Solr representation
|
110
21
|
#
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
raise ArgumentError, "Unknown field option #{options.keys.first.inspect} provided for field #{name.inspect}" unless options.empty?
|
125
|
-
end
|
126
|
-
|
127
|
-
# A key-value pair where the key is the field's indexed name and the
|
128
|
-
# value is the value that should be indexed for the given model. This can
|
129
|
-
# be merged directly into the document hash for adding to solr-ruby.
|
130
|
-
#
|
131
|
-
# ==== Parameters
|
132
|
-
#
|
133
|
-
# model<Object>:: the model from which to extract the value
|
134
|
-
#
|
135
|
-
# ==== Returns
|
136
|
-
#
|
137
|
-
# Hash:: a single key-value pair with the field name and value
|
138
|
-
#
|
139
|
-
def pairs_for(model)
|
140
|
-
unless (value = @data_extractor.value_for(model)).nil?
|
141
|
-
{ indexed_name.to_sym => to_indexed(value) }
|
22
|
+
# ==== Returns
|
23
|
+
#
|
24
|
+
# String:: Solr representation of the object
|
25
|
+
#
|
26
|
+
# ==== Raises
|
27
|
+
#
|
28
|
+
# ArgumentError::
|
29
|
+
# the value is an array, but this field does not allow multiple values
|
30
|
+
#
|
31
|
+
def to_indexed(value)
|
32
|
+
if value.is_a? Array
|
33
|
+
if @multiple
|
34
|
+
value.map { |val| to_indexed(val) }
|
142
35
|
else
|
143
|
-
{}
|
36
|
+
raise ArgumentError, "#{name} is not a multiple-value field, so it cannot index values #{value.inspect}"
|
144
37
|
end
|
38
|
+
else
|
39
|
+
@type.to_indexed(value)
|
145
40
|
end
|
146
41
|
end
|
147
42
|
|
43
|
+
# Cast the value into the appropriate Ruby class for the field's type
|
44
|
+
#
|
45
|
+
# ==== Parameters
|
46
|
+
#
|
47
|
+
# value<String>:: Solr's representation of the value
|
48
|
+
#
|
49
|
+
# ==== Returns
|
50
|
+
#
|
51
|
+
# Object:: The cast value
|
52
|
+
#
|
53
|
+
def cast(value)
|
54
|
+
@type.cast(value)
|
55
|
+
end
|
56
|
+
|
148
57
|
#
|
149
|
-
#
|
150
|
-
#
|
151
|
-
#
|
152
|
-
#
|
153
|
-
#
|
154
|
-
#
|
155
|
-
#
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
# The use cases for dynamic fields are fairly limited, but certain
|
160
|
-
# applications with highly dynamic data models might find them userful.
|
161
|
-
#
|
162
|
-
class DynamicField
|
163
|
-
extend Buildable
|
58
|
+
# Name with which this field is indexed internally. Based on public name and
|
59
|
+
# type.
|
60
|
+
#
|
61
|
+
# ==== Returns
|
62
|
+
#
|
63
|
+
# String:: Internal name of the field
|
64
|
+
#
|
65
|
+
def indexed_name
|
66
|
+
@type.indexed_name(@name)
|
67
|
+
end
|
164
68
|
|
165
|
-
|
69
|
+
#
|
70
|
+
# Whether this field accepts multiple values.
|
71
|
+
#
|
72
|
+
# ==== Returns
|
73
|
+
#
|
74
|
+
# Boolean:: True if this field accepts multiple values.
|
75
|
+
#
|
76
|
+
def multiple?
|
77
|
+
!!@multiple
|
78
|
+
end
|
79
|
+
end
|
166
80
|
|
167
|
-
|
168
|
-
|
169
|
-
|
81
|
+
#
|
82
|
+
# FulltextField instances represent fields that are indexed as fulltext.
|
83
|
+
# These fields are tokenized in the index, and can have boost applied to
|
84
|
+
# them. They also always allow multiple values (since the only downside of
|
85
|
+
# allowing multiple values is that it prevents the field from being sortable,
|
86
|
+
# and sorting on tokenized fields is nonsensical anyway, there is no reason
|
87
|
+
# to do otherwise). FulltextField instances always have the type TextType.
|
88
|
+
#
|
89
|
+
class FulltextField < Field #:nodoc:
|
90
|
+
def initialize(name, options = {})
|
91
|
+
super(name, Type::TextType)
|
92
|
+
if options.has_key?(:boost)
|
93
|
+
@attributes[:boost] = options.delete(:boost)
|
170
94
|
end
|
95
|
+
@multiple = true
|
96
|
+
raise ArgumentError, "Unknown field option #{options.keys.first.inspect} provided for field #{name.inspect}" unless options.empty?
|
97
|
+
end
|
98
|
+
end
|
171
99
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
pairs = {}
|
189
|
-
if values = @data_extractor.value_for(model)
|
190
|
-
values.each_pair do |dynamic_name, value|
|
191
|
-
field_instance = build(dynamic_name)
|
192
|
-
pairs[field_instance.indexed_name.to_sym] = field_instance.to_indexed(value)
|
193
|
-
end
|
100
|
+
#
|
101
|
+
# AttributeField instances encapsulate non-tokenized attribute data.
|
102
|
+
# AttributeFields can have any type except TextType, and can also have
|
103
|
+
# a reference (for instantiated facets), optionally allow multiple values
|
104
|
+
# (false by default), and can store their values (false by default). All
|
105
|
+
# scoping, sorting, and faceting is done with attribute fields.
|
106
|
+
#
|
107
|
+
class AttributeField < Field #:nodoc:
|
108
|
+
def initialize(name, type, options = {})
|
109
|
+
super(name, type)
|
110
|
+
@multiple = !!options.delete(:multiple)
|
111
|
+
@reference =
|
112
|
+
if (reference = options.delete(:references)).respond_to?(:name)
|
113
|
+
reference.name
|
114
|
+
elsif reference.respond_to?(:to_sym)
|
115
|
+
reference.to_sym
|
194
116
|
end
|
195
|
-
|
196
|
-
|
117
|
+
@stored = !!options.delete(:stored)
|
118
|
+
raise ArgumentError, "Unknown field option #{options.keys.first.inspect} provided for field #{name.inspect}" unless options.empty?
|
119
|
+
end
|
197
120
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
#
|
208
|
-
# DynamicFieldInstance:: Dynamic field instance
|
209
|
-
#
|
210
|
-
def build(dynamic_name)
|
211
|
-
DynamicFieldInstance.new(@name, dynamic_name, @type, @data_extractor, @multiple)
|
212
|
-
end
|
121
|
+
# The name of the field as it is indexed in Solr. The indexed name
|
122
|
+
# contains a suffix that contains information about the type as well as
|
123
|
+
# whether the field allows multiple values for a document.
|
124
|
+
#
|
125
|
+
# ==== Returns
|
126
|
+
#
|
127
|
+
# String:: The field's indexed name
|
128
|
+
#
|
129
|
+
def indexed_name
|
130
|
+
"#{super}#{'m' if @multiple}#{'s' if @stored}"
|
213
131
|
end
|
132
|
+
end
|
214
133
|
|
134
|
+
#
|
135
|
+
# RandomField instances are used for random sorting.
|
136
|
+
#
|
137
|
+
class RandomField #:nodoc:
|
215
138
|
#
|
216
|
-
#
|
217
|
-
#
|
218
|
-
#
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
def initialize(base_name, dynamic_name, type, data_extractor, multiple)
|
223
|
-
@base_name, @dynamic_name, @type, @data_extractor, @multiple =
|
224
|
-
base_name, dynamic_name, type, data_extractor, multiple
|
225
|
-
end
|
226
|
-
|
227
|
-
protected
|
139
|
+
# Never multiple, but this has to return false so Sunspot doesn't barf
|
140
|
+
# when you try to order by it.
|
141
|
+
#
|
142
|
+
def multiple?
|
143
|
+
false
|
144
|
+
end
|
228
145
|
|
229
|
-
|
230
|
-
|
231
|
-
|
146
|
+
#
|
147
|
+
# Solr uses the dynamic field name as a seed for random, so we randomize the
|
148
|
+
# field name accordingly.
|
149
|
+
#
|
150
|
+
# #XXX I think it's bad to use a random number as a seed. Would it be
|
151
|
+
# better to pass in the current timestamp or some such thing?
|
152
|
+
#
|
153
|
+
def indexed_name
|
154
|
+
"random_#{rand(1<<16)}"
|
232
155
|
end
|
233
156
|
end
|
234
157
|
end
|