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.
Files changed (178) hide show
  1. data/.gitignore +12 -0
  2. data/Gemfile +5 -0
  3. data/History.txt +252 -0
  4. data/LICENSE +18 -0
  5. data/Rakefile +13 -0
  6. data/TODO +13 -0
  7. data/lib/light_config.rb +40 -0
  8. data/lib/sunspot/adapters.rb +265 -0
  9. data/lib/sunspot/batcher.rb +62 -0
  10. data/lib/sunspot/class_set.rb +23 -0
  11. data/lib/sunspot/composite_setup.rb +202 -0
  12. data/lib/sunspot/configuration.rb +53 -0
  13. data/lib/sunspot/data_extractor.rb +50 -0
  14. data/lib/sunspot/dsl/adjustable.rb +47 -0
  15. data/lib/sunspot/dsl/field_group.rb +57 -0
  16. data/lib/sunspot/dsl/field_query.rb +327 -0
  17. data/lib/sunspot/dsl/fields.rb +103 -0
  18. data/lib/sunspot/dsl/fulltext.rb +243 -0
  19. data/lib/sunspot/dsl/function.rb +27 -0
  20. data/lib/sunspot/dsl/functional.rb +44 -0
  21. data/lib/sunspot/dsl/more_like_this_query.rb +56 -0
  22. data/lib/sunspot/dsl/paginatable.rb +32 -0
  23. data/lib/sunspot/dsl/query_facet.rb +36 -0
  24. data/lib/sunspot/dsl/restriction.rb +25 -0
  25. data/lib/sunspot/dsl/restriction_with_near.rb +160 -0
  26. data/lib/sunspot/dsl/scope.rb +217 -0
  27. data/lib/sunspot/dsl/search.rb +30 -0
  28. data/lib/sunspot/dsl/standard_query.rb +123 -0
  29. data/lib/sunspot/dsl.rb +5 -0
  30. data/lib/sunspot/field.rb +193 -0
  31. data/lib/sunspot/field_factory.rb +129 -0
  32. data/lib/sunspot/indexer.rb +136 -0
  33. data/lib/sunspot/query/abstract_field_facet.rb +52 -0
  34. data/lib/sunspot/query/bbox.rb +15 -0
  35. data/lib/sunspot/query/boost_query.rb +24 -0
  36. data/lib/sunspot/query/common_query.rb +96 -0
  37. data/lib/sunspot/query/composite_fulltext.rb +36 -0
  38. data/lib/sunspot/query/connective.rb +206 -0
  39. data/lib/sunspot/query/date_field_facet.rb +14 -0
  40. data/lib/sunspot/query/dismax.rb +132 -0
  41. data/lib/sunspot/query/field_facet.rb +41 -0
  42. data/lib/sunspot/query/field_group.rb +36 -0
  43. data/lib/sunspot/query/filter.rb +38 -0
  44. data/lib/sunspot/query/function_query.rb +52 -0
  45. data/lib/sunspot/query/geo.rb +53 -0
  46. data/lib/sunspot/query/geofilt.rb +16 -0
  47. data/lib/sunspot/query/highlighting.rb +62 -0
  48. data/lib/sunspot/query/more_like_this.rb +61 -0
  49. data/lib/sunspot/query/more_like_this_query.rb +12 -0
  50. data/lib/sunspot/query/pagination.rb +42 -0
  51. data/lib/sunspot/query/query_facet.rb +16 -0
  52. data/lib/sunspot/query/restriction.rb +262 -0
  53. data/lib/sunspot/query/scope.rb +9 -0
  54. data/lib/sunspot/query/sort.rb +109 -0
  55. data/lib/sunspot/query/sort_composite.rb +34 -0
  56. data/lib/sunspot/query/standard_query.rb +16 -0
  57. data/lib/sunspot/query/text_field_boost.rb +17 -0
  58. data/lib/sunspot/query.rb +11 -0
  59. data/lib/sunspot/schema.rb +151 -0
  60. data/lib/sunspot/search/abstract_search.rb +281 -0
  61. data/lib/sunspot/search/date_facet.rb +35 -0
  62. data/lib/sunspot/search/facet_row.rb +27 -0
  63. data/lib/sunspot/search/field_facet.rb +88 -0
  64. data/lib/sunspot/search/field_group.rb +32 -0
  65. data/lib/sunspot/search/group.rb +50 -0
  66. data/lib/sunspot/search/highlight.rb +38 -0
  67. data/lib/sunspot/search/hit.rb +150 -0
  68. data/lib/sunspot/search/hit_enumerable.rb +72 -0
  69. data/lib/sunspot/search/more_like_this_search.rb +31 -0
  70. data/lib/sunspot/search/paginated_collection.rb +57 -0
  71. data/lib/sunspot/search/query_facet.rb +67 -0
  72. data/lib/sunspot/search/standard_search.rb +21 -0
  73. data/lib/sunspot/search.rb +9 -0
  74. data/lib/sunspot/session.rb +262 -0
  75. data/lib/sunspot/session_proxy/abstract_session_proxy.rb +29 -0
  76. data/lib/sunspot/session_proxy/class_sharding_session_proxy.rb +66 -0
  77. data/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +89 -0
  78. data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +43 -0
  79. data/lib/sunspot/session_proxy/multicore_session_proxy.rb +67 -0
  80. data/lib/sunspot/session_proxy/sharding_session_proxy.rb +222 -0
  81. data/lib/sunspot/session_proxy/silent_fail_session_proxy.rb +42 -0
  82. data/lib/sunspot/session_proxy/thread_local_session_proxy.rb +37 -0
  83. data/lib/sunspot/session_proxy.rb +95 -0
  84. data/lib/sunspot/setup.rb +350 -0
  85. data/lib/sunspot/text_field_setup.rb +29 -0
  86. data/lib/sunspot/type.rb +393 -0
  87. data/lib/sunspot/util.rb +252 -0
  88. data/lib/sunspot/version.rb +3 -0
  89. data/lib/sunspot.rb +579 -0
  90. data/log/.gitignore +1 -0
  91. data/pkg/.gitignore +1 -0
  92. data/script/console +10 -0
  93. data/spec/api/adapters_spec.rb +33 -0
  94. data/spec/api/batcher_spec.rb +112 -0
  95. data/spec/api/binding_spec.rb +50 -0
  96. data/spec/api/class_set_spec.rb +24 -0
  97. data/spec/api/hit_enumerable_spec.rb +47 -0
  98. data/spec/api/indexer/attributes_spec.rb +149 -0
  99. data/spec/api/indexer/batch_spec.rb +72 -0
  100. data/spec/api/indexer/dynamic_fields_spec.rb +42 -0
  101. data/spec/api/indexer/fixed_fields_spec.rb +57 -0
  102. data/spec/api/indexer/fulltext_spec.rb +43 -0
  103. data/spec/api/indexer/removal_spec.rb +53 -0
  104. data/spec/api/indexer/spec_helper.rb +1 -0
  105. data/spec/api/indexer_spec.rb +14 -0
  106. data/spec/api/query/advanced_manipulation_examples.rb +35 -0
  107. data/spec/api/query/connectives_examples.rb +189 -0
  108. data/spec/api/query/dsl_spec.rb +18 -0
  109. data/spec/api/query/dynamic_fields_examples.rb +165 -0
  110. data/spec/api/query/faceting_examples.rb +397 -0
  111. data/spec/api/query/fulltext_examples.rb +313 -0
  112. data/spec/api/query/function_spec.rb +79 -0
  113. data/spec/api/query/geo_examples.rb +68 -0
  114. data/spec/api/query/group_spec.rb +32 -0
  115. data/spec/api/query/highlighting_examples.rb +245 -0
  116. data/spec/api/query/more_like_this_spec.rb +140 -0
  117. data/spec/api/query/ordering_pagination_examples.rb +116 -0
  118. data/spec/api/query/scope_examples.rb +275 -0
  119. data/spec/api/query/spatial_examples.rb +27 -0
  120. data/spec/api/query/spec_helper.rb +1 -0
  121. data/spec/api/query/standard_spec.rb +29 -0
  122. data/spec/api/query/text_field_scoping_examples.rb +30 -0
  123. data/spec/api/query/types_spec.rb +20 -0
  124. data/spec/api/search/dynamic_fields_spec.rb +33 -0
  125. data/spec/api/search/faceting_spec.rb +360 -0
  126. data/spec/api/search/highlighting_spec.rb +69 -0
  127. data/spec/api/search/hits_spec.rb +131 -0
  128. data/spec/api/search/paginated_collection_spec.rb +36 -0
  129. data/spec/api/search/results_spec.rb +72 -0
  130. data/spec/api/search/search_spec.rb +23 -0
  131. data/spec/api/search/spec_helper.rb +1 -0
  132. data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +85 -0
  133. data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +30 -0
  134. data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +41 -0
  135. data/spec/api/session_proxy/sharding_session_proxy_spec.rb +77 -0
  136. data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +24 -0
  137. data/spec/api/session_proxy/spec_helper.rb +9 -0
  138. data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +39 -0
  139. data/spec/api/session_spec.rb +232 -0
  140. data/spec/api/spec_helper.rb +3 -0
  141. data/spec/api/sunspot_spec.rb +29 -0
  142. data/spec/ext.rb +11 -0
  143. data/spec/helpers/indexer_helper.rb +17 -0
  144. data/spec/helpers/integration_helper.rb +8 -0
  145. data/spec/helpers/mock_session_helper.rb +13 -0
  146. data/spec/helpers/query_helper.rb +26 -0
  147. data/spec/helpers/search_helper.rb +68 -0
  148. data/spec/integration/dynamic_fields_spec.rb +57 -0
  149. data/spec/integration/faceting_spec.rb +251 -0
  150. data/spec/integration/field_grouping_spec.rb +66 -0
  151. data/spec/integration/geospatial_spec.rb +85 -0
  152. data/spec/integration/highlighting_spec.rb +44 -0
  153. data/spec/integration/indexing_spec.rb +55 -0
  154. data/spec/integration/keyword_search_spec.rb +317 -0
  155. data/spec/integration/local_search_spec.rb +64 -0
  156. data/spec/integration/more_like_this_spec.rb +43 -0
  157. data/spec/integration/scoped_search_spec.rb +354 -0
  158. data/spec/integration/stored_fields_spec.rb +12 -0
  159. data/spec/integration/test_pagination.rb +43 -0
  160. data/spec/integration/unicode_spec.rb +15 -0
  161. data/spec/mocks/adapters.rb +32 -0
  162. data/spec/mocks/blog.rb +3 -0
  163. data/spec/mocks/comment.rb +21 -0
  164. data/spec/mocks/connection.rb +126 -0
  165. data/spec/mocks/mock_adapter.rb +30 -0
  166. data/spec/mocks/mock_class_sharding_session_proxy.rb +24 -0
  167. data/spec/mocks/mock_record.rb +52 -0
  168. data/spec/mocks/mock_sharding_session_proxy.rb +15 -0
  169. data/spec/mocks/photo.rb +11 -0
  170. data/spec/mocks/post.rb +86 -0
  171. data/spec/mocks/super_class.rb +2 -0
  172. data/spec/mocks/user.rb +13 -0
  173. data/spec/spec_helper.rb +40 -0
  174. data/sunspot.gemspec +42 -0
  175. data/tasks/rdoc.rake +27 -0
  176. data/tasks/schema.rake +19 -0
  177. data/tasks/todo.rake +4 -0
  178. metadata +261 -3
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Sunspot
2
+ VERSION = '2.0.4'
3
+ end