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,123 @@
1
+ module Sunspot
2
+ module DSL #:nodoc:
3
+ #
4
+ # This class presents a DSL for constructing queries using the
5
+ # Sunspot.search method. Methods of this class are available inside the
6
+ # search block. Much of the DSL's functionality is implemented by this
7
+ # class's superclasses, Sunspot::DSL::FieldQuery and Sunspot::DSL::Scope
8
+ #
9
+ # See Sunspot.search for usage examples
10
+ #
11
+ class StandardQuery < FieldQuery
12
+ include Paginatable, Adjustable
13
+
14
+ # Specify a phrase that should be searched as fulltext. Only +text+
15
+ # fields are searched - see DSL::Fields.text
16
+ #
17
+ # Keyword search is executed using Solr's dismax handler, which strikes
18
+ # a good balance between powerful and foolproof. In particular,
19
+ # well-matched quotation marks can be used to group phrases, and the
20
+ # + and - modifiers work as expected. All other special Solr boolean
21
+ # syntax is escaped, and mismatched quotes are ignored entirely.
22
+ #
23
+ # This method can optionally take a block, which is evaluated by the
24
+ # Fulltext DSL class, and exposes several powerful dismax features.
25
+ #
26
+ # ==== Parameters
27
+ #
28
+ # keywords<String>:: phrase to perform fulltext search on
29
+ #
30
+ # ==== Options
31
+ #
32
+ # :fields<Array>::
33
+ # List of fields that should be searched for keywords. Defaults to all
34
+ # fields configured for the types under search.
35
+ # :highlight<Boolean,Array>::
36
+ # If true, perform keyword highlighting on all searched fields. If an
37
+ # array of field names, perform highlighting on the specified fields.
38
+ # Note that for highlighting to work, the desired fields have to be set
39
+ # up with :stored => true.
40
+ # This can also be called from within the fulltext block.
41
+ # :minimum_match<Integer>::
42
+ # The minimum number of search terms that a result must match. By
43
+ # default, all search terms must match; if the number of search terms
44
+ # is less than this number, the default behavior applies.
45
+ # :tie<Float>::
46
+ # A tiebreaker coefficient for scores derived from subqueries that are
47
+ # lower-scoring than the maximum score subquery. Typically a near-zero
48
+ # value is useful. See
49
+ # http://wiki.apache.org/solr/DisMaxRequestHandler#tie_.28Tie_breaker.29
50
+ # for more information.
51
+ # :query_phrase_slop<Integer>::
52
+ # The number of words that can appear between the words in a
53
+ # user-entered phrase (i.e., keywords in quotes) and still match. For
54
+ # instance, in a search for "\"great pizza\"" with a phrase slop of 1,
55
+ # "great pizza" and "great big pizza" will match, but "great monster of
56
+ # a pizza" will not. Default behavior is a query phrase slop of zero.
57
+ #
58
+ def fulltext(keywords, options = {}, &block)
59
+ if keywords && !(keywords.to_s =~ /^\s*$/)
60
+ fulltext_query = @query.add_fulltext(keywords)
61
+ if field_names = options.delete(:fields)
62
+ Util.Array(field_names).each do |field_name|
63
+ @setup.text_fields(field_name).each do |field|
64
+ fulltext_query.add_fulltext_field(field, field.default_boost)
65
+ end
66
+ end
67
+ end
68
+ if minimum_match = options.delete(:minimum_match)
69
+ fulltext_query.minimum_match = minimum_match.to_i
70
+ end
71
+ if tie = options.delete(:tie)
72
+ fulltext_query.tie = tie.to_f
73
+ end
74
+ if query_phrase_slop = options.delete(:query_phrase_slop)
75
+ fulltext_query.query_phrase_slop = query_phrase_slop.to_i
76
+ end
77
+ if highlight_field_names = options.delete(:highlight)
78
+ if highlight_field_names == true
79
+ fulltext_query.add_highlight
80
+ else
81
+ highlight_fields = []
82
+ Util.Array(highlight_field_names).each do |field_name|
83
+ highlight_fields.concat(@setup.text_fields(field_name))
84
+ end
85
+ fulltext_query.add_highlight(highlight_fields)
86
+ end
87
+ end
88
+ if block && fulltext_query
89
+ fulltext_dsl = Fulltext.new(fulltext_query, @setup)
90
+ Util.instance_eval_or_call(
91
+ fulltext_dsl,
92
+ &block
93
+ )
94
+ end
95
+ if !field_names && (!fulltext_dsl || !fulltext_dsl.fields_added?)
96
+ @setup.all_text_fields.each do |field|
97
+ unless fulltext_query.has_fulltext_field?(field)
98
+ unless fulltext_dsl && fulltext_dsl.exclude_fields.include?(field.name)
99
+ fulltext_query.add_fulltext_field(field, field.default_boost)
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ alias_method :keywords, :fulltext
107
+
108
+ def with(*args)
109
+ case args.first
110
+ when String, Symbol
111
+ field_name = args[0]
112
+ value = args.length > 1 ? args[1] : Scope::NONE
113
+ if value == Scope::NONE
114
+ return DSL::RestrictionWithNear.new(@setup.field(field_name.to_sym), @scope, @query, false)
115
+ end
116
+ end
117
+
118
+ # else
119
+ super
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,5 @@
1
+ %w(fields scope paginatable adjustable field_query standard_query query_facet
2
+ functional fulltext restriction restriction_with_near search
3
+ more_like_this_query function field_group).each do |file|
4
+ require File.join(File.dirname(__FILE__), 'dsl', file)
5
+ end
@@ -0,0 +1,193 @@
1
+ module Sunspot
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_reader :boost
7
+ attr_reader :indexed_name # Name with which this field is indexed internally. Based on public name and type or the +:as+ option.
8
+
9
+ #
10
+ #
11
+ def initialize(name, type, options = {}) #:nodoc
12
+ @name, @type = name.to_sym, type
13
+ @stored = !!options.delete(:stored)
14
+ @more_like_this = !!options.delete(:more_like_this)
15
+ set_indexed_name(options)
16
+ raise ArgumentError, "Field of type #{type} cannot be used for more_like_this" unless type.accepts_more_like_this? or !@more_like_this
17
+ end
18
+
19
+ # Convert a value to its representation for Solr indexing. This delegates
20
+ # to the #to_indexed method on the field's type.
21
+ #
22
+ # ==== Parameters
23
+ #
24
+ # value<Object>:: Value to convert to Solr representation
25
+ #
26
+ # ==== Returns
27
+ #
28
+ # String:: Solr representation of the object
29
+ #
30
+ # ==== Raises
31
+ #
32
+ # ArgumentError::
33
+ # the value is an array, but this field does not allow multiple values
34
+ #
35
+ def to_indexed(value)
36
+ if value.is_a? Array
37
+ if @multiple
38
+ value.map { |val| to_indexed(val) }
39
+ else
40
+ raise ArgumentError, "#{name} is not a multiple-value field, so it cannot index values #{value.inspect}"
41
+ end
42
+ else
43
+ @type.to_indexed(value)
44
+ end
45
+ end
46
+
47
+ # Cast the value into the appropriate Ruby class for the field's type
48
+ #
49
+ # ==== Parameters
50
+ #
51
+ # value<String>:: Solr's representation of the value
52
+ #
53
+ # ==== Returns
54
+ #
55
+ # Object:: The cast value
56
+ #
57
+ def cast(value)
58
+ @type.cast(value)
59
+ end
60
+
61
+ #
62
+ # Whether this field accepts multiple values.
63
+ #
64
+ # ==== Returns
65
+ #
66
+ # Boolean:: True if this field accepts multiple values.
67
+ #
68
+ def multiple?
69
+ !!@multiple
70
+ end
71
+
72
+ #
73
+ # Whether this field can be used for more_like_this queries.
74
+ # If true, the field is configured to store termVectors.
75
+ #
76
+ # ==== Returns
77
+ #
78
+ # Boolean:: True if this field can be used for more_like_this queries.
79
+ #
80
+ def more_like_this?
81
+ !!@more_like_this
82
+ end
83
+
84
+ def hash
85
+ indexed_name.hash
86
+ end
87
+
88
+ def eql?(field)
89
+ indexed_name == field.indexed_name
90
+ end
91
+ alias_method :==, :eql?
92
+
93
+ private
94
+
95
+ #
96
+ # Determine the indexed name. If the :as option is given use that, otherwise
97
+ # create the value based on the indexed_name of the type with additional
98
+ # suffixes for multiple, stored, and more_like_this.
99
+ #
100
+ # ==== Returns
101
+ #
102
+ # String: The field's indexed name
103
+ #
104
+ def set_indexed_name(options)
105
+ @indexed_name =
106
+ if options[:as]
107
+ options.delete(:as)
108
+ else
109
+ "#{@type.indexed_name(@name).to_s}#{'m' if @multiple }#{'s' if @stored}#{'v' if more_like_this?}"
110
+ end
111
+ end
112
+
113
+ end
114
+
115
+ #
116
+ # FulltextField instances represent fields that are indexed as fulltext.
117
+ # These fields are tokenized in the index, and can have boost applied to
118
+ # them. They also always allow multiple values (since the only downside of
119
+ # allowing multiple values is that it prevents the field from being sortable,
120
+ # and sorting on tokenized fields is nonsensical anyway, there is no reason
121
+ # to do otherwise). FulltextField instances always have the type TextType.
122
+ #
123
+ class FulltextField < Field #:nodoc:
124
+ attr_reader :default_boost
125
+
126
+ def initialize(name, options = {})
127
+ super(name, Type::TextType.instance, options)
128
+ @multiple = true
129
+ @boost = options.delete(:boost)
130
+ @default_boost = options.delete(:default_boost)
131
+ raise ArgumentError, "Unknown field option #{options.keys.first.inspect} provided for field #{name.inspect}" unless options.empty?
132
+ end
133
+
134
+ def indexed_name
135
+ "#{super}"
136
+ end
137
+ end
138
+
139
+ #
140
+ # AttributeField instances encapsulate non-tokenized attribute data.
141
+ # AttributeFields can have any type except TextType, and can also have
142
+ # a reference (for instantiated facets), optionally allow multiple values
143
+ # (false by default), and can store their values (false by default). All
144
+ # scoping, sorting, and faceting is done with attribute fields.
145
+ #
146
+ class AttributeField < Field #:nodoc:
147
+ def initialize(name, type, options = {})
148
+ @multiple = !!options.delete(:multiple)
149
+ super(name, type, options)
150
+ @reference =
151
+ if (reference = options.delete(:references)).respond_to?(:name)
152
+ reference.name
153
+ elsif reference.respond_to?(:to_sym)
154
+ reference.to_sym
155
+ end
156
+ raise ArgumentError, "Unknown field option #{options.keys.first.inspect} provided for field #{name.inspect}" unless options.empty?
157
+ end
158
+
159
+ end
160
+
161
+ class TypeField #:nodoc:
162
+ class <<self
163
+ def instance
164
+ @instance ||= new
165
+ end
166
+ end
167
+
168
+ def indexed_name
169
+ 'type'
170
+ end
171
+
172
+ def to_indexed(clazz)
173
+ clazz.name
174
+ end
175
+ end
176
+
177
+ class IdField #:nodoc:
178
+ class <<self
179
+ def instance
180
+ @instance ||= new
181
+ end
182
+ end
183
+
184
+ def indexed_name
185
+ 'id'
186
+ end
187
+
188
+ def to_indexed(id)
189
+ id.to_s
190
+ end
191
+ end
192
+ end
193
+
@@ -0,0 +1,129 @@
1
+ module Sunspot
2
+ #
3
+ # The FieldFactory module contains classes for generating fields. FieldFactory
4
+ # implementation classes should implement a #build method, although the arity
5
+ # of the method depends on the type of factory. They also must implement a
6
+ # #populate_document method, which extracts field data from a given model and
7
+ # adds it into the Solr document for indexing.
8
+ #
9
+ module FieldFactory #:nodoc:all
10
+ #
11
+ # Base class for field factories.
12
+ #
13
+ class Abstract
14
+ attr_reader :name
15
+
16
+ def initialize(name, options = {}, &block)
17
+ @name = name.to_sym
18
+ @data_extractor =
19
+ if block
20
+ DataExtractor::BlockExtractor.new(&block)
21
+ else
22
+ DataExtractor::AttributeExtractor.new(options.delete(:using) || name)
23
+ end
24
+ end
25
+ end
26
+
27
+ #
28
+ # A StaticFieldFactory generates normal static fields. Each factory instance
29
+ # contains an eager-initialized field instance, which is returned by the
30
+ # #build method.
31
+ #
32
+ class Static < Abstract
33
+ def initialize(name, type, options = {}, &block)
34
+ super(name, options, &block)
35
+ unless name.to_s =~ /^\w+$/
36
+ raise ArgumentError, "Invalid field name #{name}: only letters, numbers, and underscores are allowed."
37
+ end
38
+ @field =
39
+ if type.is_a?(Type::TextType)
40
+ FulltextField.new(name, options)
41
+ else
42
+ AttributeField.new(name, type, options)
43
+ end
44
+ end
45
+
46
+ #
47
+ # Return the field instance built by this factory
48
+ #
49
+ def build
50
+ @field
51
+ end
52
+
53
+ #
54
+ # Extract the encapsulated field's data from the given model and add it
55
+ # into the Solr document for indexing.
56
+ #
57
+ def populate_document(document, model) #:nodoc:
58
+ unless (value = @data_extractor.value_for(model)).nil?
59
+ Util.Array(@field.to_indexed(value)).each do |scalar_value|
60
+ options = {}
61
+ options[:boost] = @field.boost if @field.boost
62
+ document.add_field(
63
+ @field.indexed_name.to_sym,
64
+ scalar_value,
65
+ options
66
+ )
67
+ end
68
+ end
69
+ end
70
+
71
+ #
72
+ # A unique signature identifying this field by name and type.
73
+ #
74
+ def signature
75
+ [@field.name, @field.type]
76
+ end
77
+ end
78
+
79
+ #
80
+ # DynamicFieldFactories create dynamic field instances based on dynamic
81
+ # configuration.
82
+ #
83
+ class Dynamic < Abstract
84
+ attr_accessor :name, :type
85
+
86
+ def initialize(name, type, options = {}, &block)
87
+ super(name, options, &block)
88
+ @type, @options = type, options
89
+ end
90
+
91
+ #
92
+ # Build a field based on the dynamic name given.
93
+ #
94
+ def build(dynamic_name)
95
+ AttributeField.new("#{@name}:#{dynamic_name}", @type, @options.dup)
96
+ end
97
+ #
98
+ # This alias allows a DynamicFieldFactory to be used in place of a Setup
99
+ # or CompositeSetup instance by query components.
100
+ #
101
+ alias_method :field, :build
102
+
103
+ #
104
+ # Generate dynamic fields based on hash returned by data accessor and
105
+ # add the field data to the document.
106
+ #
107
+ def populate_document(document, model)
108
+ if values = @data_extractor.value_for(model)
109
+ values.each_pair do |dynamic_name, value|
110
+ field_instance = build(dynamic_name)
111
+ Util.Array(field_instance.to_indexed(value)).each do |scalar_value|
112
+ document.add_field(
113
+ field_instance.indexed_name.to_sym,
114
+ scalar_value
115
+ )
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ #
122
+ # Unique signature identifying this dynamic field based on name and type
123
+ #
124
+ def signature
125
+ [@name, @type]
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,136 @@
1
+ require 'sunspot/batcher'
2
+
3
+ module Sunspot
4
+ #
5
+ # This class presents a service for adding, updating, and removing data
6
+ # from the Solr index. An Indexer instance is associated with a particular
7
+ # setup, and thus is capable of indexing instances of a certain class (and its
8
+ # subclasses).
9
+ #
10
+ class Indexer #:nodoc:
11
+ include RSolr::Char
12
+
13
+ def initialize(connection)
14
+ @connection = connection
15
+ end
16
+
17
+ #
18
+ # Construct a representation of the model for indexing and send it to the
19
+ # connection for indexing
20
+ #
21
+ # ==== Parameters
22
+ #
23
+ # model<Object>:: the model to index
24
+ #
25
+ def add(model)
26
+ documents = Util.Array(model).map { |m| prepare(m) }
27
+ if batcher.batching?
28
+ batcher.concat(documents)
29
+ else
30
+ add_documents(documents)
31
+ end
32
+ end
33
+
34
+ #
35
+ # Remove the given model from the Solr index
36
+ #
37
+ def remove(*models)
38
+ @connection.delete_by_id(
39
+ models.map { |model| Adapters::InstanceAdapter.adapt(model).index_id }
40
+ )
41
+ end
42
+
43
+ #
44
+ # Remove the model from the Solr index by specifying the class and ID
45
+ #
46
+ def remove_by_id(class_name, *ids)
47
+ @connection.delete_by_id(
48
+ ids.map { |id| Adapters::InstanceAdapter.index_id_for(class_name, id) }
49
+ )
50
+ end
51
+
52
+ #
53
+ # Delete all documents of the class indexed by this indexer from Solr.
54
+ #
55
+ def remove_all(clazz = nil)
56
+ if clazz
57
+ @connection.delete_by_query("type:#{escape(clazz.name)}")
58
+ else
59
+ @connection.delete_by_query("*:*")
60
+ end
61
+ end
62
+
63
+ #
64
+ # Remove all documents that match the scope given in the Query
65
+ #
66
+ def remove_by_scope(scope)
67
+ @connection.delete_by_query(scope.to_boolean_phrase)
68
+ end
69
+
70
+ #
71
+ # Start batch processing
72
+ #
73
+ def start_batch
74
+ batcher.start_new
75
+ end
76
+
77
+ #
78
+ # Write batch out to Solr and clear it
79
+ #
80
+ def flush_batch
81
+ add_documents(batcher.end_current)
82
+ end
83
+
84
+ private
85
+
86
+ def batcher
87
+ @batcher ||= Batcher.new
88
+ end
89
+
90
+ #
91
+ # Convert documents into hash of indexed properties
92
+ #
93
+ def prepare(model)
94
+ document = document_for(model)
95
+ setup = setup_for(model)
96
+ if boost = setup.document_boost_for(model)
97
+ document.attrs[:boost] = boost
98
+ end
99
+ setup.all_field_factories.each do |field_factory|
100
+ field_factory.populate_document(document, model)
101
+ end
102
+ document
103
+ end
104
+
105
+ def add_documents(documents)
106
+ @connection.add(documents)
107
+ end
108
+
109
+ #
110
+ # All indexed documents index and store the +id+ and +type+ fields.
111
+ # This method constructs the document hash containing those key-value
112
+ # pairs.
113
+ #
114
+ def document_for(model)
115
+ RSolr::Xml::Document.new(
116
+ :id => Adapters::InstanceAdapter.adapt(model).index_id,
117
+ :type => Util.superclasses_for(model.class).map { |clazz| clazz.name }
118
+ )
119
+ end
120
+
121
+ #
122
+ # Get the Setup object for the given object's class.
123
+ #
124
+ # ==== Parameters
125
+ #
126
+ # object<Object>:: The object whose setup is to be retrieved
127
+ #
128
+ # ==== Returns
129
+ #
130
+ # Sunspot::Setup:: The setup for the object's class
131
+ #
132
+ def setup_for(object)
133
+ Setup.for(object.class) || raise(NoSetupError, "Sunspot is not configured for #{object.class.inspect}")
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,52 @@
1
+ module Sunspot
2
+ module Query
3
+ class AbstractFieldFacet
4
+ include RSolr::Char
5
+
6
+ def initialize(field, options)
7
+ @field, @options = field, options
8
+ end
9
+
10
+ def to_params
11
+ params = {
12
+ :facet => 'true',
13
+ }
14
+ case @options[:sort]
15
+ when :count
16
+ params[qualified_param('sort')] = 'true'
17
+ when :index
18
+ params[qualified_param('sort')] = 'false'
19
+ when nil
20
+ else
21
+ raise(
22
+ ArgumentError,
23
+ "#{@options[:sort].inspect} is not an allowed value for :sort. Allowed options are :count and :index"
24
+ )
25
+ end
26
+ if @options[:limit]
27
+ params[qualified_param('limit')] = @options[:limit].to_i
28
+ end
29
+ if @options[:prefix]
30
+ params[qualified_param('prefix')] = escape(@options[:prefix].to_s)
31
+ end
32
+ params[qualified_param('mincount')] =
33
+ case
34
+ when @options[:minimum_count] then @options[:minimum_count].to_i
35
+ when @options[:zeros] then 0
36
+ else 1
37
+ end
38
+ params
39
+ end
40
+
41
+ private
42
+
43
+ def qualified_param(param)
44
+ :"f.#{key}.facet.#{param}"
45
+ end
46
+
47
+ def key
48
+ @key ||= @options[:name] || @field.indexed_name
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,15 @@
1
+ module Sunspot
2
+ module Query
3
+ class Bbox
4
+ def initialize(field, first_corner, second_corner)
5
+ @field, @first_corner, @second_corner = field, first_corner, second_corner
6
+ end
7
+
8
+ def to_params
9
+ filter = "#{@field.indexed_name}:[#{@first_corner.join(",")} TO #{@second_corner.join(",")}]"
10
+
11
+ {:fq => filter}
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,24 @@
1
+ module Sunspot
2
+ module Query
3
+ #
4
+ # Representation of a BoostQuery, which allows the searcher to specify a
5
+ # scope for which matching documents should have an extra boost. This is
6
+ # essentially a conjunction, with an extra instance variable containing
7
+ # the boost that should be applied.
8
+ #
9
+ class BoostQuery < Connective::Conjunction #:nodoc:
10
+ def initialize(boost)
11
+ super(false)
12
+ @boost = boost
13
+ end
14
+
15
+ def to_boolean_phrase
16
+ if @boost.is_a?(FunctionQuery)
17
+ "#{@boost}"
18
+ else
19
+ "#{super}^#{@boost}"
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end