gojee-sunspot 2.0.2

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 (176) 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.rb +579 -0
  9. data/lib/sunspot/adapters.rb +265 -0
  10. data/lib/sunspot/batcher.rb +62 -0
  11. data/lib/sunspot/class_set.rb +23 -0
  12. data/lib/sunspot/composite_setup.rb +202 -0
  13. data/lib/sunspot/configuration.rb +53 -0
  14. data/lib/sunspot/data_extractor.rb +50 -0
  15. data/lib/sunspot/dsl.rb +5 -0
  16. data/lib/sunspot/dsl/adjustable.rb +47 -0
  17. data/lib/sunspot/dsl/field_group.rb +57 -0
  18. data/lib/sunspot/dsl/field_query.rb +327 -0
  19. data/lib/sunspot/dsl/fields.rb +103 -0
  20. data/lib/sunspot/dsl/fulltext.rb +243 -0
  21. data/lib/sunspot/dsl/function.rb +27 -0
  22. data/lib/sunspot/dsl/functional.rb +44 -0
  23. data/lib/sunspot/dsl/more_like_this_query.rb +56 -0
  24. data/lib/sunspot/dsl/paginatable.rb +32 -0
  25. data/lib/sunspot/dsl/query_facet.rb +36 -0
  26. data/lib/sunspot/dsl/restriction.rb +25 -0
  27. data/lib/sunspot/dsl/restriction_with_near.rb +160 -0
  28. data/lib/sunspot/dsl/scope.rb +217 -0
  29. data/lib/sunspot/dsl/search.rb +30 -0
  30. data/lib/sunspot/dsl/standard_query.rb +123 -0
  31. data/lib/sunspot/field.rb +193 -0
  32. data/lib/sunspot/field_factory.rb +129 -0
  33. data/lib/sunspot/indexer.rb +136 -0
  34. data/lib/sunspot/query.rb +11 -0
  35. data/lib/sunspot/query/abstract_field_facet.rb +52 -0
  36. data/lib/sunspot/query/bbox.rb +15 -0
  37. data/lib/sunspot/query/boost_query.rb +24 -0
  38. data/lib/sunspot/query/common_query.rb +96 -0
  39. data/lib/sunspot/query/composite_fulltext.rb +36 -0
  40. data/lib/sunspot/query/connective.rb +206 -0
  41. data/lib/sunspot/query/date_field_facet.rb +14 -0
  42. data/lib/sunspot/query/dismax.rb +132 -0
  43. data/lib/sunspot/query/field_facet.rb +41 -0
  44. data/lib/sunspot/query/field_group.rb +36 -0
  45. data/lib/sunspot/query/filter.rb +38 -0
  46. data/lib/sunspot/query/function_query.rb +52 -0
  47. data/lib/sunspot/query/geo.rb +53 -0
  48. data/lib/sunspot/query/geofilt.rb +16 -0
  49. data/lib/sunspot/query/highlighting.rb +62 -0
  50. data/lib/sunspot/query/more_like_this.rb +61 -0
  51. data/lib/sunspot/query/more_like_this_query.rb +12 -0
  52. data/lib/sunspot/query/pagination.rb +42 -0
  53. data/lib/sunspot/query/query_facet.rb +16 -0
  54. data/lib/sunspot/query/restriction.rb +262 -0
  55. data/lib/sunspot/query/scope.rb +9 -0
  56. data/lib/sunspot/query/sort.rb +109 -0
  57. data/lib/sunspot/query/sort_composite.rb +34 -0
  58. data/lib/sunspot/query/standard_query.rb +16 -0
  59. data/lib/sunspot/query/text_field_boost.rb +17 -0
  60. data/lib/sunspot/schema.rb +151 -0
  61. data/lib/sunspot/search.rb +9 -0
  62. data/lib/sunspot/search/abstract_search.rb +281 -0
  63. data/lib/sunspot/search/date_facet.rb +35 -0
  64. data/lib/sunspot/search/facet_row.rb +27 -0
  65. data/lib/sunspot/search/field_facet.rb +88 -0
  66. data/lib/sunspot/search/field_group.rb +32 -0
  67. data/lib/sunspot/search/group.rb +50 -0
  68. data/lib/sunspot/search/highlight.rb +38 -0
  69. data/lib/sunspot/search/hit.rb +150 -0
  70. data/lib/sunspot/search/hit_enumerable.rb +72 -0
  71. data/lib/sunspot/search/more_like_this_search.rb +31 -0
  72. data/lib/sunspot/search/paginated_collection.rb +57 -0
  73. data/lib/sunspot/search/query_facet.rb +67 -0
  74. data/lib/sunspot/search/standard_search.rb +21 -0
  75. data/lib/sunspot/session.rb +262 -0
  76. data/lib/sunspot/session_proxy.rb +95 -0
  77. data/lib/sunspot/session_proxy/abstract_session_proxy.rb +29 -0
  78. data/lib/sunspot/session_proxy/class_sharding_session_proxy.rb +66 -0
  79. data/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +89 -0
  80. data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +43 -0
  81. data/lib/sunspot/session_proxy/multicore_session_proxy.rb +67 -0
  82. data/lib/sunspot/session_proxy/sharding_session_proxy.rb +222 -0
  83. data/lib/sunspot/session_proxy/silent_fail_session_proxy.rb +42 -0
  84. data/lib/sunspot/session_proxy/thread_local_session_proxy.rb +37 -0
  85. data/lib/sunspot/setup.rb +350 -0
  86. data/lib/sunspot/text_field_setup.rb +29 -0
  87. data/lib/sunspot/type.rb +393 -0
  88. data/lib/sunspot/util.rb +252 -0
  89. data/lib/sunspot/version.rb +3 -0
  90. data/script/console +10 -0
  91. data/spec/api/adapters_spec.rb +33 -0
  92. data/spec/api/batcher_spec.rb +112 -0
  93. data/spec/api/binding_spec.rb +50 -0
  94. data/spec/api/class_set_spec.rb +24 -0
  95. data/spec/api/hit_enumerable_spec.rb +47 -0
  96. data/spec/api/indexer/attributes_spec.rb +149 -0
  97. data/spec/api/indexer/batch_spec.rb +72 -0
  98. data/spec/api/indexer/dynamic_fields_spec.rb +42 -0
  99. data/spec/api/indexer/fixed_fields_spec.rb +57 -0
  100. data/spec/api/indexer/fulltext_spec.rb +43 -0
  101. data/spec/api/indexer/removal_spec.rb +53 -0
  102. data/spec/api/indexer/spec_helper.rb +1 -0
  103. data/spec/api/indexer_spec.rb +14 -0
  104. data/spec/api/query/advanced_manipulation_examples.rb +35 -0
  105. data/spec/api/query/connectives_examples.rb +189 -0
  106. data/spec/api/query/dsl_spec.rb +18 -0
  107. data/spec/api/query/dynamic_fields_examples.rb +165 -0
  108. data/spec/api/query/faceting_examples.rb +397 -0
  109. data/spec/api/query/fulltext_examples.rb +313 -0
  110. data/spec/api/query/function_spec.rb +79 -0
  111. data/spec/api/query/geo_examples.rb +68 -0
  112. data/spec/api/query/group_spec.rb +32 -0
  113. data/spec/api/query/highlighting_examples.rb +245 -0
  114. data/spec/api/query/more_like_this_spec.rb +140 -0
  115. data/spec/api/query/ordering_pagination_examples.rb +116 -0
  116. data/spec/api/query/scope_examples.rb +275 -0
  117. data/spec/api/query/spatial_examples.rb +27 -0
  118. data/spec/api/query/spec_helper.rb +1 -0
  119. data/spec/api/query/standard_spec.rb +29 -0
  120. data/spec/api/query/text_field_scoping_examples.rb +30 -0
  121. data/spec/api/query/types_spec.rb +20 -0
  122. data/spec/api/search/dynamic_fields_spec.rb +33 -0
  123. data/spec/api/search/faceting_spec.rb +360 -0
  124. data/spec/api/search/highlighting_spec.rb +69 -0
  125. data/spec/api/search/hits_spec.rb +131 -0
  126. data/spec/api/search/paginated_collection_spec.rb +36 -0
  127. data/spec/api/search/results_spec.rb +72 -0
  128. data/spec/api/search/search_spec.rb +23 -0
  129. data/spec/api/search/spec_helper.rb +1 -0
  130. data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +85 -0
  131. data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +30 -0
  132. data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +41 -0
  133. data/spec/api/session_proxy/sharding_session_proxy_spec.rb +77 -0
  134. data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +24 -0
  135. data/spec/api/session_proxy/spec_helper.rb +9 -0
  136. data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +39 -0
  137. data/spec/api/session_spec.rb +232 -0
  138. data/spec/api/spec_helper.rb +3 -0
  139. data/spec/api/sunspot_spec.rb +29 -0
  140. data/spec/ext.rb +11 -0
  141. data/spec/helpers/indexer_helper.rb +17 -0
  142. data/spec/helpers/integration_helper.rb +8 -0
  143. data/spec/helpers/mock_session_helper.rb +13 -0
  144. data/spec/helpers/query_helper.rb +26 -0
  145. data/spec/helpers/search_helper.rb +68 -0
  146. data/spec/integration/dynamic_fields_spec.rb +57 -0
  147. data/spec/integration/faceting_spec.rb +251 -0
  148. data/spec/integration/field_grouping_spec.rb +66 -0
  149. data/spec/integration/geospatial_spec.rb +85 -0
  150. data/spec/integration/highlighting_spec.rb +44 -0
  151. data/spec/integration/indexing_spec.rb +55 -0
  152. data/spec/integration/keyword_search_spec.rb +317 -0
  153. data/spec/integration/local_search_spec.rb +64 -0
  154. data/spec/integration/more_like_this_spec.rb +43 -0
  155. data/spec/integration/scoped_search_spec.rb +354 -0
  156. data/spec/integration/stored_fields_spec.rb +12 -0
  157. data/spec/integration/test_pagination.rb +43 -0
  158. data/spec/integration/unicode_spec.rb +15 -0
  159. data/spec/mocks/adapters.rb +32 -0
  160. data/spec/mocks/blog.rb +3 -0
  161. data/spec/mocks/comment.rb +21 -0
  162. data/spec/mocks/connection.rb +126 -0
  163. data/spec/mocks/mock_adapter.rb +30 -0
  164. data/spec/mocks/mock_class_sharding_session_proxy.rb +24 -0
  165. data/spec/mocks/mock_record.rb +52 -0
  166. data/spec/mocks/mock_sharding_session_proxy.rb +15 -0
  167. data/spec/mocks/photo.rb +11 -0
  168. data/spec/mocks/post.rb +86 -0
  169. data/spec/mocks/super_class.rb +2 -0
  170. data/spec/mocks/user.rb +13 -0
  171. data/spec/spec_helper.rb +40 -0
  172. data/sunspot.gemspec +42 -0
  173. data/tasks/rdoc.rake +27 -0
  174. data/tasks/schema.rake +19 -0
  175. data/tasks/todo.rake +4 -0
  176. metadata +409 -0
@@ -0,0 +1,265 @@
1
+ module Sunspot
2
+ #
3
+ # Sunspot works by saving references to the primary key (or natural ID) of
4
+ # each indexed object, and then retrieving the objects from persistent storage
5
+ # when their IDs are referenced in search results. In order for Sunspot to
6
+ # know what an object's primary key is, and how to retrieve objects from
7
+ # persistent storage given a primary key, an adapter must be registered for
8
+ # that object's class or one of its superclasses (for instance, an adapter
9
+ # registered for ActiveRecord::Base would be used for all ActiveRecord
10
+ # models).
11
+ #
12
+ # To provide Sunspot with this ability, adapters must have two roles:
13
+ #
14
+ # Data accessor::
15
+ # A subclass of Sunspot::Adapters::DataAccessor, this object is instantiated
16
+ # with a particular class and must respond to the #load() method, which
17
+ # returns an object from persistent storage given that object's primary key.
18
+ # It can also optionally implement the #load_all() method, which returns
19
+ # a collection of objects given a collection of primary keys, if that can be
20
+ # done more efficiently than calling #load() on each key.
21
+ # Instance adapter::
22
+ # A subclass of Sunspot::Adapters::InstanceAdapter, this object is
23
+ # instantiated with a particular instance. Its only job is to tell Sunspot
24
+ # what the object's primary key is, by implementing the #id() method.
25
+ #
26
+ # Adapters are registered by registering their two components, telling Sunspot
27
+ # that they are available for one or more classes, and all of their
28
+ # subclasses. See Sunspot::Adapters::DataAccessor.register and
29
+ # Sunspot::Adapters::InstanceAdapter.register for the details.
30
+ #
31
+ # See spec/mocks/mock_adapter.rb for an example of how adapter classes should
32
+ # be implemented.
33
+ #
34
+ module Adapters
35
+ # Subclasses of the InstanceAdapter class should implement the #id method,
36
+ # which returns the primary key of the instance stored in the @instance
37
+ # variable. The primary key must be unique within the scope of the
38
+ # instance's class.
39
+ #
40
+ # ==== Example:
41
+ #
42
+ # class FileAdapter < Sunspot::Adapters::InstanceAdapter
43
+ # def id
44
+ # File.expand_path(@instance.path)
45
+ # end
46
+ # end
47
+ #
48
+ # # then in your initializer
49
+ # Sunspot::Adapters::InstanceAdapter.register(MyAdapter, File)
50
+ #
51
+ class InstanceAdapter
52
+ def initialize(instance) #:nodoc:
53
+ @instance = instance
54
+ end
55
+
56
+ #
57
+ # The universally-unique ID for this instance that will be stored in solr
58
+ #
59
+ # ==== Returns
60
+ #
61
+ # String:: ID for use in Solr
62
+ #
63
+ def index_id #:nodoc:
64
+ InstanceAdapter.index_id_for(@instance.class.name, id)
65
+ end
66
+
67
+ class <<self
68
+ # Instantiate an InstanceAdapter for the given object, searching for
69
+ # registered adapters for the object's class.
70
+ #
71
+ # ==== Parameters
72
+ #
73
+ # instance<Object>:: The instance to adapt
74
+ #
75
+ # ==== Returns
76
+ #
77
+ # InstanceAdapter::
78
+ # An instance of an InstanceAdapter implementation that
79
+ # wraps the given instance
80
+ #
81
+ def adapt(instance) #:nodoc:
82
+ self.for(instance.class).new(instance)
83
+ end
84
+
85
+ # Register an instance adapter for a set of classes. When searching for
86
+ # an adapter for a given instance, Sunspot starts with the instance's
87
+ # class, and then searches for registered adapters up the class's
88
+ # ancestor chain.
89
+ #
90
+ # ==== Parameters
91
+ #
92
+ # instance_adapter<Class>:: The instance adapter class to register
93
+ # classes...<Class>::
94
+ # One or more classes that this instance adapter adapts
95
+ #
96
+ def register(instance_adapter, *classes)
97
+ classes.each do |clazz|
98
+ instance_adapters[clazz.name.to_sym] = instance_adapter
99
+ end
100
+ end
101
+
102
+ # Find the best InstanceAdapter implementation that adapts the given
103
+ # class. Starting with the class and then moving up the ancestor chain,
104
+ # looks for registered InstanceAdapter implementations.
105
+ #
106
+ # ==== Parameters
107
+ #
108
+ # clazz<Class>:: The class to find an InstanceAdapter for
109
+ #
110
+ # ==== Returns
111
+ #
112
+ # Class:: Subclass of InstanceAdapter, or nil if none found
113
+ #
114
+ # ==== Raises
115
+ #
116
+ # Sunspot::NoAdapterError:: If no adapter is registered for this class
117
+ #
118
+ def for(clazz) #:nodoc:
119
+ original_class_name = clazz.name
120
+ clazz.ancestors.each do |ancestor_class|
121
+ next if ancestor_class.name.nil? || ancestor_class.name.empty?
122
+ class_name = ancestor_class.name.to_sym
123
+ return instance_adapters[class_name] if instance_adapters[class_name]
124
+ end
125
+
126
+ raise(Sunspot::NoAdapterError,
127
+ "No adapter is configured for #{original_class_name} or its superclasses. See the documentation for Sunspot::Adapters")
128
+ end
129
+
130
+ def index_id_for(class_name, id) #:nodoc:
131
+ "#{class_name} #{id}"
132
+ end
133
+
134
+ protected
135
+
136
+ # Lazy-initialize the hash of registered instance adapters
137
+ #
138
+ # ==== Returns
139
+ #
140
+ # Hash:: Hash containing class names keyed to instance adapter classes
141
+ #
142
+ def instance_adapters #:nodoc:
143
+ @instance_adapters ||= {}
144
+ end
145
+ end
146
+ end
147
+
148
+ # Subclasses of the DataAccessor class take care of retreiving instances of
149
+ # the adapted class from (usually persistent) storage. Subclasses must
150
+ # implement the #load method, which takes an id (the value returned by
151
+ # InstanceAdapter#id, as a string), and returns the instance referenced by
152
+ # that ID. Optionally, it can also override the #load_all method, which
153
+ # takes an array of IDs and returns an array of instances in the order
154
+ # given. #load_all need only be implemented if it can be done more
155
+ # efficiently than simply iterating over the IDs and calling #load on each
156
+ # individually.
157
+ #
158
+ # ==== Example
159
+ #
160
+ # class FileAccessor < Sunspot::Adapters::InstanceAdapter
161
+ # def load(id)
162
+ # @clazz.open(id)
163
+ # end
164
+ # end
165
+ #
166
+ # Sunspot::Adapters::DataAccessor.register(FileAccessor, File)
167
+ #
168
+ class DataAccessor
169
+ def initialize(clazz) #:nodoc:
170
+ @clazz = clazz
171
+ end
172
+
173
+ # Subclasses can override this class to provide more efficient bulk
174
+ # loading of instances. Instances must be returned in the same order
175
+ # that the IDs were given.
176
+ #
177
+ # ==== Parameters
178
+ #
179
+ # ids<Array>:: collection of IDs
180
+ #
181
+ # ==== Returns
182
+ #
183
+ # Array:: collection of instances, in order of IDs given
184
+ #
185
+ def load_all(ids)
186
+ ids.map { |id| self.load(id) }
187
+ end
188
+
189
+ class <<self
190
+ # Create a DataAccessor for the given class, searching registered
191
+ # adapters for the best match. See InstanceAdapter#adapt for discussion
192
+ # of inheritence.
193
+ #
194
+ # ==== Parameters
195
+ #
196
+ # clazz<Class>:: Class to create DataAccessor for
197
+ #
198
+ # ==== Returns
199
+ #
200
+ # DataAccessor::
201
+ # DataAccessor implementation which provides access to given class
202
+ #
203
+ def create(clazz) #:nodoc:
204
+ self.for(clazz).new(clazz)
205
+ end
206
+
207
+ # Register data accessor for a set of classes. When searching for
208
+ # an accessor for a given class, Sunspot starts with the class,
209
+ # and then searches for registered adapters up the class's ancestor
210
+ # chain.
211
+ #
212
+ # ==== Parameters
213
+ #
214
+ # data_accessor<Class>:: The data accessor class to register
215
+ # classes...<Class>::
216
+ # One or more classes that this data accessor providess access to
217
+ #
218
+ def register(data_accessor, *classes)
219
+ classes.each do |clazz|
220
+ data_accessors[clazz.name.to_sym] = data_accessor
221
+ end
222
+ end
223
+
224
+ # Find the best DataAccessor implementation that adapts the given class.
225
+ # Starting with the class and then moving up the ancestor chain, looks
226
+ # for registered DataAccessor implementations.
227
+ #
228
+ # ==== Parameters
229
+ #
230
+ # clazz<Class>:: The class to find a DataAccessor for
231
+ #
232
+ # ==== Returns
233
+ #
234
+ # Class:: Implementation of DataAccessor
235
+ #
236
+ # ==== Raises
237
+ #
238
+ # Sunspot::NoAdapterError:: If no data accessor exists for the given class
239
+ #
240
+ def for(clazz) #:nodoc:
241
+ original_class_name = clazz.name
242
+ clazz.ancestors.each do |ancestor_class|
243
+ next if ancestor_class.name.nil? || ancestor_class.name.empty?
244
+ class_name = ancestor_class.name.to_sym
245
+ return data_accessors[class_name] if data_accessors[class_name]
246
+ end
247
+ raise(Sunspot::NoAdapterError,
248
+ "No data accessor is configured for #{original_class_name} or its superclasses. See the documentation for Sunspot::Adapters")
249
+ end
250
+
251
+ protected
252
+
253
+ # Lazy-initialize the hash of registered data accessors
254
+ #
255
+ # ==== Returns
256
+ #
257
+ # Hash:: Hash containing class names keyed to data accessor classes
258
+ #
259
+ def data_accessors #:nodoc:
260
+ @adapters ||= {}
261
+ end
262
+ end
263
+ end
264
+ end
265
+ end
@@ -0,0 +1,62 @@
1
+ module Sunspot
2
+ #
3
+ # Keeps a stack of batches and helps out when Indexer is asked to batch documents.
4
+ #
5
+ # If the client does something like
6
+ #
7
+ # Sunspot.batch do
8
+ # some_code_here
9
+ # which_triggers_some_other_code
10
+ # which_again_calls
11
+ # Sunspot.batch { ... }
12
+ # end
13
+ #
14
+ # it is the Batcher's job to keep track of these nestings. The inner will
15
+ # be sent of to be indexed first.
16
+ #
17
+ class Batcher
18
+ include Enumerable
19
+
20
+ # Raised if you ask to end current, but no current exists
21
+ class NoCurrentBatchError < StandardError; end
22
+
23
+ def initialize
24
+ @stack = []
25
+ end
26
+
27
+ def current
28
+ @stack.last or start_new
29
+ end
30
+
31
+ def start_new
32
+ (@stack << []).last
33
+ end
34
+
35
+ def end_current
36
+ fail NoCurrentBatchError if @stack.empty?
37
+
38
+ @stack.pop
39
+ end
40
+
41
+ def depth
42
+ @stack.length
43
+ end
44
+
45
+ def batching?
46
+ depth > 0
47
+ end
48
+
49
+ def each(&block)
50
+ current.each(&block)
51
+ end
52
+
53
+ def push(value)
54
+ current << value
55
+ end
56
+ alias << push
57
+
58
+ def concat(values)
59
+ current.concat values
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,23 @@
1
+ module Sunspot
2
+ class ClassSet
3
+ include Enumerable
4
+
5
+ def initialize
6
+ @name_to_klass = {}
7
+ end
8
+
9
+ def <<(klass)
10
+ @name_to_klass[klass.name.to_sym] = klass
11
+ self
12
+ end
13
+ alias_method :add, :<<
14
+
15
+ def each(&block)
16
+ @name_to_klass.values.each(&block)
17
+ end
18
+
19
+ def empty?
20
+ @name_to_klass.empty?
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,202 @@
1
+ module Sunspot
2
+ #
3
+ # The CompositeSetup class encapsulates a collection of setups, and responds
4
+ # to a subset of the methods that Setup responds to (in particular, the
5
+ # methods required to build queries).
6
+ #
7
+ class CompositeSetup #:nodoc:
8
+ class << self
9
+ alias_method :for, :new
10
+ end
11
+
12
+ def initialize(types)
13
+ @types = types
14
+ end
15
+
16
+ #
17
+ # Collection of Setup objects for the enclosed types
18
+ #
19
+ # ==== Returns
20
+ #
21
+ # Array:: Collection of Setup objects
22
+ #
23
+ def setups
24
+ @setups ||= @types.map { |type| Setup.for(type) }
25
+ end
26
+
27
+ #
28
+ # Return the names of the encapsulated types
29
+ #
30
+ # ==== Returns
31
+ #
32
+ # Array:: Collection of class names
33
+ #
34
+ def type_names
35
+ @type_names ||= @types.map { |clazz| clazz.name }
36
+ end
37
+
38
+ #
39
+ # Get a text field object by its public name. A field will be returned if
40
+ # it is configured for any of the enclosed types.
41
+ #
42
+ # ==== Returns
43
+ #
44
+ # Sunspot::FulltextField:: Text field with the given public name
45
+ #
46
+ # ==== Raises
47
+ #
48
+ # UnrecognizedFieldError::
49
+ # If no field with that name is configured for any of the enclosed types.
50
+ #
51
+ def text_fields(field_name)
52
+ if text_fields = text_fields_hash[field_name.to_sym]
53
+ text_fields.to_a
54
+ else
55
+ raise(
56
+ UnrecognizedFieldError,
57
+ "No text field configured for #{@types * ', '} with name '#{field_name}'"
58
+ )
59
+ end
60
+ end
61
+
62
+ #
63
+ # Get a Sunspot::AttributeField instance corresponding to the given field name
64
+ #
65
+ # ==== Parameters
66
+ #
67
+ # field_name<Symbol>:: The public field name for which to find a field
68
+ #
69
+ # ==== Returns
70
+ #
71
+ # Sunspot::AttributeField The field object corresponding to the given name
72
+ #
73
+ # ==== Raises
74
+ #
75
+ # ArgumentError::
76
+ # If the given field name is not configured for the types being queried
77
+ #
78
+ def field(field_name) #:nodoc:
79
+ fields_hash[field_name.to_sym] || raise(
80
+ UnrecognizedFieldError,
81
+ "No field configured for #{@types * ', '} with name '#{field_name}'"
82
+ )
83
+ end
84
+
85
+ #
86
+ # Get a dynamic field factory for the given base name.
87
+ #
88
+ # ==== Returns
89
+ #
90
+ # DynamicFieldFactory:: Factory for dynamic fields with the given base name
91
+ #
92
+ # ==== Raises
93
+ #
94
+ # UnrecognizedFieldError::
95
+ # If the given base name is not configured as a dynamic field for the types being queried
96
+ #
97
+ def dynamic_field_factory(field_name)
98
+ dynamic_field_factories_hash[field_name.to_sym] || raise(
99
+ UnrecognizedFieldError,
100
+ "No dynamic field configured for #{@types * ', '} with name #{field_name.inspect}"
101
+ )
102
+ end
103
+
104
+ #
105
+ # Collection of all text fields configured for any of the enclosed types.
106
+ #
107
+ # === Returns
108
+ #
109
+ # Array:: Text fields configured for the enclosed types
110
+ #
111
+ def all_text_fields
112
+ @text_fields ||= text_fields_hash.values.map { |set| set.to_a }.flatten
113
+ end
114
+
115
+ def all_more_like_this_fields
116
+ @more_like_this_fields ||= more_like_this_fields_hash.values.map { |set| set.to_a }.flatten
117
+ end
118
+
119
+ private
120
+
121
+ #
122
+ # Return a hash of field names to text field objects, containing all fields
123
+ # that are configured for any of the types enclosed.
124
+ #
125
+ # ==== Returns
126
+ #
127
+ # Hash:: Hash of field names to text field objects.
128
+ #
129
+ def text_fields_hash
130
+ @text_fields_hash ||=
131
+ setups.inject({}) do |hash, setup|
132
+ setup.all_text_fields.each do |text_field|
133
+ (hash[text_field.name] ||= Set.new) << text_field
134
+ end
135
+ hash
136
+ end
137
+ end
138
+
139
+ def more_like_this_fields_hash
140
+ @more_like_this_fields_hash ||=
141
+ setups.inject({}) do |hash, setup|
142
+ setup.all_more_like_this_fields.each do |more_like_this_field|
143
+ (hash[more_like_this_field.name] ||= Set.new) << more_like_this_field
144
+ end
145
+ hash
146
+ end
147
+ end
148
+
149
+ #
150
+ # Return a hash of field names to field objects, containing all fields
151
+ # that are common to all of the classes enclosed. In order for fields
152
+ # to be common, they must be of the same type and have the same
153
+ # value for allow_multiple? and stored?. This method is memoized.
154
+ #
155
+ # ==== Returns
156
+ #
157
+ # Hash:: field names keyed to field objects
158
+ #
159
+ def fields_hash
160
+ @fields_hash ||=
161
+ begin
162
+ field_sets_hash = Hash.new { |h, k| h[k] = Set.new }
163
+ @types.each do |type|
164
+ Setup.for(type).fields.each do |field|
165
+ field_sets_hash[field.name.to_sym] << field
166
+ end
167
+ end
168
+ fields_hash = {}
169
+ field_sets_hash.each_pair do |field_name, set|
170
+ if set.length == 1
171
+ fields_hash[field_name] = set.to_a.first
172
+ end
173
+ end
174
+ fields_hash
175
+ end
176
+ end
177
+
178
+ #
179
+ # Return a hash of dynamic field base names to dynamic field factories for
180
+ # those base names. Criteria for the inclusion are the same as for
181
+ # #fields_hash()
182
+ #
183
+ def dynamic_field_factories_hash
184
+ @dynamic_field_factories_hash ||=
185
+ begin
186
+ dynamic_field_factories_hash = @types.inject({}) do |hash, type|
187
+ Setup.for(type).dynamic_field_factories.each do |field_factory|
188
+ (hash[field_factory.name.to_sym] ||= {})[type.name] = field_factory
189
+ end
190
+ hash
191
+ end
192
+ dynamic_field_factories_hash.each_pair do |field_name, field_configurations_hash|
193
+ if @types.any? { |type| field_configurations_hash[type.name].nil? }
194
+ dynamic_field_factories_hash.delete(field_name)
195
+ else
196
+ dynamic_field_factories_hash[field_name] = field_configurations_hash.values.first
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end