gojee-sunspot 2.0.3 → 2.0.4
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 +5 -0
- data/History.txt +252 -0
- data/LICENSE +18 -0
- data/Rakefile +13 -0
- data/TODO +13 -0
- data/lib/light_config.rb +40 -0
- data/lib/sunspot/adapters.rb +265 -0
- data/lib/sunspot/batcher.rb +62 -0
- data/lib/sunspot/class_set.rb +23 -0
- data/lib/sunspot/composite_setup.rb +202 -0
- data/lib/sunspot/configuration.rb +53 -0
- data/lib/sunspot/data_extractor.rb +50 -0
- data/lib/sunspot/dsl/adjustable.rb +47 -0
- data/lib/sunspot/dsl/field_group.rb +57 -0
- data/lib/sunspot/dsl/field_query.rb +327 -0
- data/lib/sunspot/dsl/fields.rb +103 -0
- data/lib/sunspot/dsl/fulltext.rb +243 -0
- data/lib/sunspot/dsl/function.rb +27 -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 +32 -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 +160 -0
- data/lib/sunspot/dsl/scope.rb +217 -0
- data/lib/sunspot/dsl/search.rb +30 -0
- data/lib/sunspot/dsl/standard_query.rb +123 -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 +136 -0
- data/lib/sunspot/query/abstract_field_facet.rb +52 -0
- data/lib/sunspot/query/bbox.rb +15 -0
- data/lib/sunspot/query/boost_query.rb +24 -0
- data/lib/sunspot/query/common_query.rb +96 -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 +132 -0
- data/lib/sunspot/query/field_facet.rb +41 -0
- data/lib/sunspot/query/field_group.rb +36 -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/geofilt.rb +16 -0
- data/lib/sunspot/query/highlighting.rb +62 -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 +42 -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 +109 -0
- data/lib/sunspot/query/sort_composite.rb +34 -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 +281 -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/field_group.rb +32 -0
- data/lib/sunspot/search/group.rb +50 -0
- data/lib/sunspot/search/highlight.rb +38 -0
- data/lib/sunspot/search/hit.rb +150 -0
- data/lib/sunspot/search/hit_enumerable.rb +72 -0
- data/lib/sunspot/search/more_like_this_search.rb +31 -0
- data/lib/sunspot/search/paginated_collection.rb +57 -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/session.rb +262 -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/multicore_session_proxy.rb +67 -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 +95 -0
- data/lib/sunspot/setup.rb +350 -0
- data/lib/sunspot/text_field_setup.rb +29 -0
- data/lib/sunspot/type.rb +393 -0
- data/lib/sunspot/util.rb +252 -0
- data/lib/sunspot/version.rb +3 -0
- data/lib/sunspot.rb +579 -0
- data/log/.gitignore +1 -0
- data/pkg/.gitignore +1 -0
- data/script/console +10 -0
- data/spec/api/adapters_spec.rb +33 -0
- data/spec/api/batcher_spec.rb +112 -0
- data/spec/api/binding_spec.rb +50 -0
- data/spec/api/class_set_spec.rb +24 -0
- data/spec/api/hit_enumerable_spec.rb +47 -0
- data/spec/api/indexer/attributes_spec.rb +149 -0
- data/spec/api/indexer/batch_spec.rb +72 -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 +79 -0
- data/spec/api/query/geo_examples.rb +68 -0
- data/spec/api/query/group_spec.rb +32 -0
- data/spec/api/query/highlighting_examples.rb +245 -0
- data/spec/api/query/more_like_this_spec.rb +140 -0
- data/spec/api/query/ordering_pagination_examples.rb +116 -0
- data/spec/api/query/scope_examples.rb +275 -0
- data/spec/api/query/spatial_examples.rb +27 -0
- data/spec/api/query/spec_helper.rb +1 -0
- data/spec/api/query/standard_spec.rb +29 -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 +131 -0
- data/spec/api/search/paginated_collection_spec.rb +36 -0
- data/spec/api/search/results_spec.rb +72 -0
- data/spec/api/search/search_spec.rb +23 -0
- data/spec/api/search/spec_helper.rb +1 -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 +39 -0
- data/spec/api/session_spec.rb +232 -0
- data/spec/api/spec_helper.rb +3 -0
- data/spec/api/sunspot_spec.rb +29 -0
- data/spec/ext.rb +11 -0
- data/spec/helpers/indexer_helper.rb +17 -0
- data/spec/helpers/integration_helper.rb +8 -0
- data/spec/helpers/mock_session_helper.rb +13 -0
- data/spec/helpers/query_helper.rb +26 -0
- data/spec/helpers/search_helper.rb +68 -0
- data/spec/integration/dynamic_fields_spec.rb +57 -0
- data/spec/integration/faceting_spec.rb +251 -0
- data/spec/integration/field_grouping_spec.rb +66 -0
- data/spec/integration/geospatial_spec.rb +85 -0
- data/spec/integration/highlighting_spec.rb +44 -0
- data/spec/integration/indexing_spec.rb +55 -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/stored_fields_spec.rb +12 -0
- data/spec/integration/test_pagination.rb +43 -0
- data/spec/integration/unicode_spec.rb +15 -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 +86 -0
- data/spec/mocks/super_class.rb +2 -0
- data/spec/mocks/user.rb +13 -0
- data/spec/spec_helper.rb +40 -0
- data/sunspot.gemspec +42 -0
- data/tasks/rdoc.rake +27 -0
- data/tasks/schema.rake +19 -0
- data/tasks/todo.rake +4 -0
- metadata +261 -3
data/lib/sunspot/type.rb
ADDED
@@ -0,0 +1,393 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
begin
|
3
|
+
require 'geohash'
|
4
|
+
rescue LoadError => e
|
5
|
+
require 'pr_geohash'
|
6
|
+
end
|
7
|
+
|
8
|
+
module Sunspot
|
9
|
+
#
|
10
|
+
# This module contains singleton objects that represent the types that can be
|
11
|
+
# indexed and searched using Sunspot. Plugin developers should be able to
|
12
|
+
# add new constants to the Type module; as long as they implement the
|
13
|
+
# appropriate methods, Sunspot should be able to integrate them (note that
|
14
|
+
# this capability is untested at the moment). The required methods are:
|
15
|
+
#
|
16
|
+
# +indexed_name+::
|
17
|
+
# Convert a given field name into its form as stored in Solr. This
|
18
|
+
# generally means adding a suffix to match a Solr dynamicField definition.
|
19
|
+
# +to_indexed+::
|
20
|
+
# Convert a value of this type into the appropriate Solr string
|
21
|
+
# representation.
|
22
|
+
# +cast+::
|
23
|
+
# Convert a Solr string representation of a value into the appropriate
|
24
|
+
# Ruby type.
|
25
|
+
#
|
26
|
+
module Type
|
27
|
+
class <<self
|
28
|
+
def register(sunspot_type, *classes)
|
29
|
+
classes.each do |clazz|
|
30
|
+
ruby_type_map[clazz.name.to_sym] = sunspot_type.instance
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def for_class(clazz)
|
35
|
+
if clazz
|
36
|
+
ruby_type_map[clazz.name.to_sym] || for_class(clazz.superclass)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def for(object)
|
41
|
+
for_class(object.class)
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_indexed(object)
|
45
|
+
if type = self.for(object)
|
46
|
+
type.to_indexed(object)
|
47
|
+
else
|
48
|
+
object.to_s
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_literal(object)
|
53
|
+
if type = self.for(object)
|
54
|
+
type.to_literal(object)
|
55
|
+
else
|
56
|
+
raise ArgumentError, "Can't use #{object.inspect} as Solr literal: #{object.class} has no registered Solr type"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def ruby_type_map
|
63
|
+
@ruby_type_map ||= {}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class AbstractType #:nodoc:
|
68
|
+
include Singleton
|
69
|
+
|
70
|
+
def accepts_dynamic?
|
71
|
+
true
|
72
|
+
end
|
73
|
+
|
74
|
+
def accepts_more_like_this?
|
75
|
+
false
|
76
|
+
end
|
77
|
+
|
78
|
+
def to_literal(object)
|
79
|
+
raise(
|
80
|
+
ArgumentError,
|
81
|
+
"#{self.class.name} cannot be used as a Solr literal"
|
82
|
+
)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# Text is a special type that stores data for fulltext search. Unlike other
|
88
|
+
# types, Text fields are tokenized and are made available to the keyword
|
89
|
+
# search phrase. Text fields cannot be faceted, ordered upon, or used in
|
90
|
+
# restrictions. Similarly, text fields are the only fields that are made
|
91
|
+
# available to keyword search.
|
92
|
+
#
|
93
|
+
class TextType < AbstractType
|
94
|
+
def indexed_name(name) #:nodoc:
|
95
|
+
"#{name}_text"
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_indexed(value) #:nodoc:
|
99
|
+
value.to_s if value
|
100
|
+
end
|
101
|
+
|
102
|
+
def cast(text)
|
103
|
+
text
|
104
|
+
end
|
105
|
+
|
106
|
+
def accepts_dynamic?
|
107
|
+
false
|
108
|
+
end
|
109
|
+
|
110
|
+
def accepts_more_like_this?
|
111
|
+
true
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
#
|
116
|
+
# The String type represents string data.
|
117
|
+
#
|
118
|
+
class StringType < AbstractType
|
119
|
+
def indexed_name(name) #:nodoc:
|
120
|
+
"#{name}_s"
|
121
|
+
end
|
122
|
+
|
123
|
+
def to_indexed(value) #:nodoc:
|
124
|
+
value.to_s if value
|
125
|
+
end
|
126
|
+
|
127
|
+
def cast(string) #:nodoc:
|
128
|
+
string
|
129
|
+
end
|
130
|
+
end
|
131
|
+
register(StringType, String)
|
132
|
+
|
133
|
+
#
|
134
|
+
# The Integer type represents integers.
|
135
|
+
#
|
136
|
+
class IntegerType < AbstractType
|
137
|
+
def indexed_name(name) #:nodoc:
|
138
|
+
"#{name}_i"
|
139
|
+
end
|
140
|
+
|
141
|
+
def to_indexed(value) #:nodoc:
|
142
|
+
value.to_i.to_s if value
|
143
|
+
end
|
144
|
+
|
145
|
+
def to_literal(value)
|
146
|
+
to_indexed(value)
|
147
|
+
end
|
148
|
+
|
149
|
+
def cast(string) #:nodoc:
|
150
|
+
string.to_i
|
151
|
+
end
|
152
|
+
end
|
153
|
+
register(IntegerType, Integer)
|
154
|
+
|
155
|
+
#
|
156
|
+
# The Long type indexes Ruby Fixnum and Bignum numbers into Java Longs
|
157
|
+
#
|
158
|
+
class LongType < IntegerType
|
159
|
+
def indexed_name(name) #:nodoc:
|
160
|
+
"#{name}_l"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
#
|
165
|
+
# The Float type represents floating-point numbers.
|
166
|
+
#
|
167
|
+
class FloatType < AbstractType
|
168
|
+
def indexed_name(name) #:nodoc:
|
169
|
+
"#{name}_f"
|
170
|
+
end
|
171
|
+
|
172
|
+
def to_indexed(value) #:nodoc:
|
173
|
+
value.to_f.to_s if value
|
174
|
+
end
|
175
|
+
|
176
|
+
def to_literal(value)
|
177
|
+
to_indexed(value)
|
178
|
+
end
|
179
|
+
|
180
|
+
def cast(string) #:nodoc:
|
181
|
+
string.to_f
|
182
|
+
end
|
183
|
+
end
|
184
|
+
register(FloatType, Float)
|
185
|
+
|
186
|
+
#
|
187
|
+
# The Double type indexes Ruby Floats (which are in fact doubles) into Java
|
188
|
+
# Double fields
|
189
|
+
#
|
190
|
+
class DoubleType < FloatType
|
191
|
+
def indexed_name(name)
|
192
|
+
"#{name}_e"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
#
|
197
|
+
# The time type represents times. Note that times are always converted to
|
198
|
+
# UTC before indexing, and facets of Time fields always return times in UTC.
|
199
|
+
#
|
200
|
+
class TimeType < AbstractType
|
201
|
+
XMLSCHEMA = "%Y-%m-%dT%H:%M:%SZ"
|
202
|
+
|
203
|
+
def indexed_name(name) #:nodoc:
|
204
|
+
"#{name}_d"
|
205
|
+
end
|
206
|
+
|
207
|
+
def to_indexed(value) #:nodoc:
|
208
|
+
if value
|
209
|
+
value_to_utc_time(value).strftime(XMLSCHEMA)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def to_literal(value)
|
214
|
+
to_indexed(value)
|
215
|
+
end
|
216
|
+
|
217
|
+
def cast(string) #:nodoc:
|
218
|
+
begin
|
219
|
+
Time.xmlschema(string)
|
220
|
+
rescue ArgumentError
|
221
|
+
DateTime.strptime(string, XMLSCHEMA)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
private
|
226
|
+
|
227
|
+
def value_to_utc_time(value)
|
228
|
+
if value.respond_to?(:utc)
|
229
|
+
value.utc
|
230
|
+
elsif value.respond_to?(:new_offset)
|
231
|
+
value.new_offset
|
232
|
+
else
|
233
|
+
begin
|
234
|
+
Time.parse(value.to_s).utc
|
235
|
+
rescue ArgumentError
|
236
|
+
DateTime.parse(value.to_s).new_offset
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
register TimeType, Time, DateTime
|
242
|
+
|
243
|
+
#
|
244
|
+
# The DateType encapsulates dates (without time information). Internally,
|
245
|
+
# Solr does not have a date-only type, so this type indexes data using
|
246
|
+
# Solr's DateField type (which is actually date/time), midnight UTC of the
|
247
|
+
# indexed date.
|
248
|
+
#
|
249
|
+
class DateType < TimeType
|
250
|
+
def to_indexed(value) #:nodoc:
|
251
|
+
if value
|
252
|
+
time =
|
253
|
+
if %w(year mon mday).all? { |method| value.respond_to?(method) }
|
254
|
+
Time.utc(value.year, value.mon, value.mday)
|
255
|
+
else
|
256
|
+
date = Date.parse(value.to_s)
|
257
|
+
Time.utc(date.year, date.mon, date.mday)
|
258
|
+
end
|
259
|
+
super(time)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def cast(string) #:nodoc:
|
264
|
+
time = super
|
265
|
+
Date.civil(time.year, time.mon, time.mday)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
register DateType, Date
|
269
|
+
|
270
|
+
#
|
271
|
+
# Store integers in a TrieField, which makes range queries much faster.
|
272
|
+
#
|
273
|
+
class TrieIntegerType < IntegerType
|
274
|
+
def indexed_name(name)
|
275
|
+
"#{super}t"
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
#
|
280
|
+
# Store floats in a TrieField, which makes range queries much faster.
|
281
|
+
#
|
282
|
+
class TrieFloatType < FloatType
|
283
|
+
def indexed_name(name)
|
284
|
+
"#{super}t"
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
#
|
289
|
+
# Index times using a TrieField. Internally, trie times are indexed as
|
290
|
+
# Unix timestamps in a trie integer field, as TrieField does not support
|
291
|
+
# datetime types natively. This distinction should have no effect from the
|
292
|
+
# standpoint of the library's API.
|
293
|
+
#
|
294
|
+
class TrieTimeType < TimeType
|
295
|
+
def indexed_name(name)
|
296
|
+
"#{super}t"
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
|
301
|
+
#
|
302
|
+
# The boolean type represents true/false values. Note that +nil+ will not be
|
303
|
+
# indexed at all; only +false+ will be indexed with a false value.
|
304
|
+
#
|
305
|
+
class BooleanType < AbstractType
|
306
|
+
def indexed_name(name) #:nodoc:
|
307
|
+
"#{name}_b"
|
308
|
+
end
|
309
|
+
|
310
|
+
def to_indexed(value) #:nodoc:
|
311
|
+
unless value.nil?
|
312
|
+
value ? 'true' : 'false'
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def cast(string) #:nodoc:
|
317
|
+
case string
|
318
|
+
when 'true'
|
319
|
+
true
|
320
|
+
when 'false'
|
321
|
+
false
|
322
|
+
when true, false
|
323
|
+
string
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
register BooleanType, TrueClass, FalseClass
|
328
|
+
|
329
|
+
#
|
330
|
+
# The Location type encodes geographical coordinates as a GeoHash.
|
331
|
+
# The data for this type must respond to the `lat` and `lng` methods; you
|
332
|
+
# can use Sunspot::Util::Coordinates as a wrapper if your source data does
|
333
|
+
# not follow this API.
|
334
|
+
#
|
335
|
+
# Location fields are most usefully searched using the
|
336
|
+
# Sunspot::DSL::RestrictionWithType#near method; see that method for more
|
337
|
+
# information on geographical search.
|
338
|
+
#
|
339
|
+
# ==== Example
|
340
|
+
#
|
341
|
+
# Sunspot.setup(Post) do
|
342
|
+
# location :coordinates do
|
343
|
+
# Sunspot::Util::Coordinates.new(coordinates[0], coordinates[1])
|
344
|
+
# end
|
345
|
+
# end
|
346
|
+
#
|
347
|
+
class LocationType < AbstractType
|
348
|
+
def indexed_name(name)
|
349
|
+
"#{name}_s"
|
350
|
+
end
|
351
|
+
|
352
|
+
def to_indexed(value)
|
353
|
+
GeoHash.encode(value.lat.to_f, value.lng.to_f, 12)
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
#
|
358
|
+
# The Latlon type encodes geographical coordinates in the native
|
359
|
+
# Solr LatLonType.
|
360
|
+
#
|
361
|
+
# The data for this type must respond to the `lat` and `lng` methods; you
|
362
|
+
# can use Sunspot::Util::Coordinates as a wrapper if your source data does
|
363
|
+
# not follow this API.
|
364
|
+
#
|
365
|
+
# Location fields can be used with the geospatial DSL. See the
|
366
|
+
# Geospatial section of the README for examples.
|
367
|
+
#
|
368
|
+
class LatlonType < AbstractType
|
369
|
+
def indexed_name(name)
|
370
|
+
"#{name}_ll"
|
371
|
+
end
|
372
|
+
|
373
|
+
def to_indexed(value)
|
374
|
+
"#{value.lat.to_f},#{value.lng.to_f}"
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
class ClassType < AbstractType
|
379
|
+
def indexed_name(name) #:nodoc:
|
380
|
+
'class_name'
|
381
|
+
end
|
382
|
+
|
383
|
+
def to_indexed(value) #:nodoc:
|
384
|
+
value.name
|
385
|
+
end
|
386
|
+
|
387
|
+
def cast(string) #:nodoc:
|
388
|
+
Sunspot::Util.full_const_get(string)
|
389
|
+
end
|
390
|
+
end
|
391
|
+
register ClassType, Class
|
392
|
+
end
|
393
|
+
end
|
data/lib/sunspot/util.rb
ADDED
@@ -0,0 +1,252 @@
|
|
1
|
+
module Sunspot
|
2
|
+
#
|
3
|
+
# The Sunspot::Util module provides utility methods used elsewhere in the
|
4
|
+
# library.
|
5
|
+
#
|
6
|
+
module Util #:nodoc:
|
7
|
+
class <<self
|
8
|
+
#
|
9
|
+
# Get all of the superclasses for a given class, including the class
|
10
|
+
# itself.
|
11
|
+
#
|
12
|
+
# ==== Parameters
|
13
|
+
#
|
14
|
+
# clazz<Class>:: class for which to get superclasses
|
15
|
+
#
|
16
|
+
# ==== Returns
|
17
|
+
#
|
18
|
+
# Array:: Collection containing class and its superclasses
|
19
|
+
#
|
20
|
+
def superclasses_for(clazz)
|
21
|
+
superclasses = [clazz]
|
22
|
+
superclasses << (clazz = clazz.superclass) while clazz.superclass != Object
|
23
|
+
superclasses
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# Convert a string to snake case
|
28
|
+
#
|
29
|
+
# ==== Parameters
|
30
|
+
#
|
31
|
+
# string<String>:: String to convert to snake case
|
32
|
+
#
|
33
|
+
# ==== Returns
|
34
|
+
#
|
35
|
+
# String:: String in snake case
|
36
|
+
#
|
37
|
+
def snake_case(string)
|
38
|
+
string.scan(/(^|[A-Z])([^A-Z]+)/).map! { |word| word.join.downcase }.join('_')
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Convert a string to camel case
|
43
|
+
#
|
44
|
+
# ==== Parameters
|
45
|
+
#
|
46
|
+
# string<String>:: String to convert to camel case
|
47
|
+
#
|
48
|
+
# ==== Returns
|
49
|
+
#
|
50
|
+
# String:: String in camel case
|
51
|
+
#
|
52
|
+
def camel_case(string)
|
53
|
+
string.split('_').map! { |word| word.capitalize }.join
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Get a constant from a fully qualified name
|
58
|
+
#
|
59
|
+
# ==== Parameters
|
60
|
+
#
|
61
|
+
# string<String>:: The fully qualified name of a constant
|
62
|
+
#
|
63
|
+
# ==== Returns
|
64
|
+
#
|
65
|
+
# Object:: Value of constant named
|
66
|
+
#
|
67
|
+
def full_const_get(string)
|
68
|
+
string.split('::').inject(Object) do |context, const_name|
|
69
|
+
context.const_defined?(const_name) ? context.const_get(const_name) : context.const_missing(const_name)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# Evaluate the given proc in the context of the given object if the
|
75
|
+
# block's arity is non-positive, or by passing the given object as an
|
76
|
+
# argument if it is negative.
|
77
|
+
#
|
78
|
+
# ==== Parameters
|
79
|
+
#
|
80
|
+
# object<Object>:: Object to pass to the proc
|
81
|
+
#
|
82
|
+
def instance_eval_or_call(object, &block)
|
83
|
+
if block.arity > 0
|
84
|
+
block.call(object)
|
85
|
+
else
|
86
|
+
ContextBoundDelegate.instance_eval_with_context(object, &block)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def extract_options_from(args)
|
91
|
+
if args.last.is_a?(Hash)
|
92
|
+
args.pop
|
93
|
+
else
|
94
|
+
{}
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Ruby's treatment of Strings as Enumerables is heavily annoying. As far
|
100
|
+
# as I know the behavior of Kernel.Array() is otherwise fine.
|
101
|
+
#
|
102
|
+
def Array(object)
|
103
|
+
case object
|
104
|
+
when String, Hash
|
105
|
+
[object]
|
106
|
+
else
|
107
|
+
super
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
#
|
112
|
+
# When generating boosts, Solr requires that the values be in standard
|
113
|
+
# (not scientific) notation. We would like to ensure a minimum number of
|
114
|
+
# significant digits (i.e., digits that are not prefix zeros) for small
|
115
|
+
# float values.
|
116
|
+
#
|
117
|
+
def format_float(f, digits)
|
118
|
+
if f < 1
|
119
|
+
sprintf('%.*f', digits - Math.log10(f), f)
|
120
|
+
else
|
121
|
+
f.to_s
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
#
|
126
|
+
# Perform a deep merge of hashes, returning the result as a new hash.
|
127
|
+
# See #deep_merge_into for rules used to merge the hashes
|
128
|
+
#
|
129
|
+
# ==== Parameters
|
130
|
+
#
|
131
|
+
# left<Hash>:: Hash to merge
|
132
|
+
# right<Hash>:: The other hash to merge
|
133
|
+
#
|
134
|
+
# ==== Returns
|
135
|
+
#
|
136
|
+
# Hash:: New hash containing the given hashes deep-merged.
|
137
|
+
#
|
138
|
+
def deep_merge(left, right)
|
139
|
+
deep_merge_into({}, left, right)
|
140
|
+
end
|
141
|
+
|
142
|
+
#
|
143
|
+
# Perform a deep merge of the right hash into the left hash
|
144
|
+
#
|
145
|
+
# ==== Parameters
|
146
|
+
#
|
147
|
+
# left:: Hash to receive merge
|
148
|
+
# right:: Hash to merge into left
|
149
|
+
#
|
150
|
+
# ==== Returns
|
151
|
+
#
|
152
|
+
# Hash:: left
|
153
|
+
#
|
154
|
+
def deep_merge!(left, right)
|
155
|
+
deep_merge_into(left, left, right)
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
#
|
161
|
+
# Deep merge two hashes into a third hash, using rules that produce nice
|
162
|
+
# merged parameter hashes. The rules are as follows, for a given key:
|
163
|
+
#
|
164
|
+
# * If only one hash has a value, or if both hashes have the same value,
|
165
|
+
# just use the value.
|
166
|
+
# * If either of the values is not a hash, create arrays out of both
|
167
|
+
# values and concatenate them.
|
168
|
+
# * Otherwise, deep merge the two values (which are both hashes)
|
169
|
+
#
|
170
|
+
# ==== Parameters
|
171
|
+
#
|
172
|
+
# destination<Hash>:: Hash into which to perform the merge
|
173
|
+
# left<Hash>:: One hash to merge
|
174
|
+
# right<Hash>:: The other hash to merge
|
175
|
+
#
|
176
|
+
# ==== Returns
|
177
|
+
#
|
178
|
+
# Hash:: destination
|
179
|
+
#
|
180
|
+
def deep_merge_into(destination, left, right)
|
181
|
+
left.each_pair do |name, left_value|
|
182
|
+
right_value = right[name] if right
|
183
|
+
destination[name] =
|
184
|
+
if right_value.nil? || left_value == right_value
|
185
|
+
left_value
|
186
|
+
elsif !left_value.respond_to?(:each_pair) || !right_value.respond_to?(:each_pair)
|
187
|
+
Array(left_value) + Array(right_value)
|
188
|
+
else
|
189
|
+
merged_value = {}
|
190
|
+
deep_merge_into(merged_value, left_value, right_value)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
left_keys = Set.new(left.keys)
|
194
|
+
destination.merge!(right.reject { |k, v| left_keys.include?(k) })
|
195
|
+
destination
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
Coordinates = Struct.new(:lat, :lng)
|
200
|
+
|
201
|
+
class ContextBoundDelegate
|
202
|
+
class <<self
|
203
|
+
def instance_eval_with_context(receiver, &block)
|
204
|
+
calling_context = eval('self', block.binding)
|
205
|
+
if parent_calling_context = calling_context.instance_eval{@__calling_context__}
|
206
|
+
calling_context = parent_calling_context
|
207
|
+
end
|
208
|
+
new(receiver, calling_context).instance_eval(&block)
|
209
|
+
end
|
210
|
+
private :new
|
211
|
+
end
|
212
|
+
|
213
|
+
BASIC_METHODS = Set[:==, :equal?, :"!", :"!=", :instance_eval,
|
214
|
+
:object_id, :__send__, :__id__]
|
215
|
+
|
216
|
+
instance_methods.each do |method|
|
217
|
+
unless BASIC_METHODS.include?(method.to_sym)
|
218
|
+
undef_method(method)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def initialize(receiver, calling_context)
|
223
|
+
@__receiver__, @__calling_context__ = receiver, calling_context
|
224
|
+
end
|
225
|
+
|
226
|
+
def id
|
227
|
+
@__calling_context__.__send__(:id)
|
228
|
+
end
|
229
|
+
|
230
|
+
# Special case due to `Kernel#sub`'s existence
|
231
|
+
def sub(*args, &block)
|
232
|
+
__proxy_method__(:sub, *args, &block)
|
233
|
+
end
|
234
|
+
|
235
|
+
def method_missing(method, *args, &block)
|
236
|
+
__proxy_method__(method, *args, &block)
|
237
|
+
end
|
238
|
+
|
239
|
+
def __proxy_method__(method, *args, &block)
|
240
|
+
begin
|
241
|
+
@__receiver__.__send__(method.to_sym, *args, &block)
|
242
|
+
rescue ::NoMethodError => e
|
243
|
+
begin
|
244
|
+
@__calling_context__.__send__(method.to_sym, *args, &block)
|
245
|
+
rescue ::NoMethodError
|
246
|
+
raise(e)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|