UnderpantsGnome-sunspot 0.9.1.1

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 (103) hide show
  1. data/History.txt +39 -0
  2. data/LICENSE +18 -0
  3. data/README.rdoc +154 -0
  4. data/Rakefile +9 -0
  5. data/TODO +4 -0
  6. data/VERSION.yml +4 -0
  7. data/bin/sunspot-configure-solr +46 -0
  8. data/bin/sunspot-solr +62 -0
  9. data/lib/light_config.rb +40 -0
  10. data/lib/sunspot.rb +470 -0
  11. data/lib/sunspot/adapters.rb +265 -0
  12. data/lib/sunspot/composite_setup.rb +186 -0
  13. data/lib/sunspot/configuration.rb +38 -0
  14. data/lib/sunspot/data_extractor.rb +47 -0
  15. data/lib/sunspot/date_facet.rb +36 -0
  16. data/lib/sunspot/date_facet_row.rb +17 -0
  17. data/lib/sunspot/dsl.rb +3 -0
  18. data/lib/sunspot/dsl/field_query.rb +72 -0
  19. data/lib/sunspot/dsl/fields.rb +86 -0
  20. data/lib/sunspot/dsl/query.rb +59 -0
  21. data/lib/sunspot/dsl/query_facet.rb +31 -0
  22. data/lib/sunspot/dsl/restriction.rb +25 -0
  23. data/lib/sunspot/dsl/scope.rb +193 -0
  24. data/lib/sunspot/dsl/search.rb +30 -0
  25. data/lib/sunspot/facet.rb +51 -0
  26. data/lib/sunspot/facet_row.rb +34 -0
  27. data/lib/sunspot/field.rb +157 -0
  28. data/lib/sunspot/field_factory.rb +126 -0
  29. data/lib/sunspot/indexer.rb +127 -0
  30. data/lib/sunspot/instantiated_facet.rb +38 -0
  31. data/lib/sunspot/instantiated_facet_row.rb +12 -0
  32. data/lib/sunspot/query.rb +190 -0
  33. data/lib/sunspot/query/base_query.rb +90 -0
  34. data/lib/sunspot/query/connective.rb +77 -0
  35. data/lib/sunspot/query/dynamic_query.rb +69 -0
  36. data/lib/sunspot/query/field_facet.rb +149 -0
  37. data/lib/sunspot/query/field_query.rb +57 -0
  38. data/lib/sunspot/query/pagination.rb +39 -0
  39. data/lib/sunspot/query/query_facet.rb +72 -0
  40. data/lib/sunspot/query/query_facet_row.rb +19 -0
  41. data/lib/sunspot/query/restriction.rb +225 -0
  42. data/lib/sunspot/query/scope.rb +165 -0
  43. data/lib/sunspot/query/sort.rb +36 -0
  44. data/lib/sunspot/query/sort_composite.rb +33 -0
  45. data/lib/sunspot/query_facet.rb +33 -0
  46. data/lib/sunspot/query_facet_row.rb +21 -0
  47. data/lib/sunspot/schema.rb +165 -0
  48. data/lib/sunspot/search.rb +222 -0
  49. data/lib/sunspot/search/hit.rb +62 -0
  50. data/lib/sunspot/session.rb +201 -0
  51. data/lib/sunspot/setup.rb +271 -0
  52. data/lib/sunspot/type.rb +200 -0
  53. data/lib/sunspot/util.rb +164 -0
  54. data/solr/etc/jetty.xml +212 -0
  55. data/solr/etc/webdefault.xml +379 -0
  56. data/solr/lib/jetty-6.1.3.jar +0 -0
  57. data/solr/lib/jetty-util-6.1.3.jar +0 -0
  58. data/solr/lib/jsp-2.1/ant-1.6.5.jar +0 -0
  59. data/solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
  60. data/solr/lib/jsp-2.1/jsp-2.1.jar +0 -0
  61. data/solr/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
  62. data/solr/lib/servlet-api-2.5-6.1.3.jar +0 -0
  63. data/solr/solr/conf/elevate.xml +36 -0
  64. data/solr/solr/conf/protwords.txt +21 -0
  65. data/solr/solr/conf/schema.xml +50 -0
  66. data/solr/solr/conf/solrconfig.xml +696 -0
  67. data/solr/solr/conf/stopwords.txt +57 -0
  68. data/solr/solr/conf/synonyms.txt +31 -0
  69. data/solr/start.jar +0 -0
  70. data/solr/webapps/solr.war +0 -0
  71. data/spec/api/adapters_spec.rb +33 -0
  72. data/spec/api/build_search_spec.rb +918 -0
  73. data/spec/api/indexer_spec.rb +311 -0
  74. data/spec/api/query_spec.rb +153 -0
  75. data/spec/api/search_retrieval_spec.rb +325 -0
  76. data/spec/api/session_spec.rb +157 -0
  77. data/spec/api/spec_helper.rb +1 -0
  78. data/spec/api/sunspot_spec.rb +18 -0
  79. data/spec/integration/dynamic_fields_spec.rb +55 -0
  80. data/spec/integration/faceting_spec.rb +169 -0
  81. data/spec/integration/keyword_search_spec.rb +83 -0
  82. data/spec/integration/scoped_search_spec.rb +188 -0
  83. data/spec/integration/spec_helper.rb +1 -0
  84. data/spec/integration/stored_fields_spec.rb +10 -0
  85. data/spec/integration/test_pagination.rb +32 -0
  86. data/spec/mocks/adapters.rb +32 -0
  87. data/spec/mocks/blog.rb +3 -0
  88. data/spec/mocks/comment.rb +19 -0
  89. data/spec/mocks/connection.rb +84 -0
  90. data/spec/mocks/mock_adapter.rb +30 -0
  91. data/spec/mocks/mock_record.rb +41 -0
  92. data/spec/mocks/photo.rb +8 -0
  93. data/spec/mocks/post.rb +70 -0
  94. data/spec/mocks/user.rb +8 -0
  95. data/spec/spec_helper.rb +47 -0
  96. data/tasks/gemspec.rake +25 -0
  97. data/tasks/rcov.rake +28 -0
  98. data/tasks/rdoc.rake +21 -0
  99. data/tasks/schema.rake +19 -0
  100. data/tasks/spec.rake +24 -0
  101. data/tasks/todo.rake +4 -0
  102. data/templates/schema.xml.haml +24 -0
  103. metadata +245 -0
@@ -0,0 +1,149 @@
1
+ require 'set'
2
+
3
+ module Sunspot
4
+ module Query
5
+ #
6
+ # Encapsulates a query component representing a field facet. Users create
7
+ # instances using DSL::Query#facet
8
+ #
9
+ class FieldFacet #:nodoc:
10
+ class <<self
11
+ protected :new
12
+
13
+ #
14
+ # Return the appropriate FieldFacet instance for the field and options.
15
+ # If a :time_range option is specified, and the field type is TimeType,
16
+ # build a DateFieldFacet. Otherwise, build a normal FieldFacet.
17
+ #
18
+ # ==== Returns
19
+ #
20
+ # FieldFacet:: FieldFacet instance of appropriate class.
21
+ #
22
+ def build(field, options)
23
+ if options.has_key?(:time_range)
24
+ unless field.type == Type::TimeType
25
+ raise(
26
+ ArgumentError,
27
+ ":time_range key can only be specified for time fields"
28
+ )
29
+ end
30
+ DateFieldFacet.new(field, options)
31
+ else
32
+ FieldFacet.new(field, options)
33
+ end
34
+ end
35
+ end
36
+
37
+ def initialize(field, options)
38
+ @field, @options = field, options
39
+ end
40
+
41
+ # ==== Returns
42
+ #
43
+ # Hash:: solr-ruby params for this field facet
44
+ #
45
+ def to_params
46
+ params = { :"facet.field" => [@field.indexed_name], :facet => 'true' }
47
+ params[param_key(:sort)] =
48
+ case @options[:sort]
49
+ when :count then 'true'
50
+ when :index then 'false'
51
+ when nil
52
+ else raise(ArgumentError, 'Allowed facet sort options are :count and :index')
53
+ end
54
+ params[param_key(:limit)] = @options[:limit]
55
+ params[param_key(:mincount)] =
56
+ if @options[:minimum_count] then @options[:minimum_count]
57
+ elsif @options[:zeros] then 0
58
+ else 1
59
+ end
60
+ params
61
+ end
62
+
63
+ private
64
+
65
+ #
66
+ # Given a facet parameter name, return the appropriate Solr parameter for
67
+ # this facet.
68
+ #
69
+ # ==== Returns
70
+ #
71
+ # Symbol:: Solr query parameter key
72
+ #
73
+ def param_key(name)
74
+ :"f.#{@field.indexed_name}.facet.#{name}"
75
+ end
76
+ end
77
+
78
+ class DateFieldFacet < FieldFacet #:nodoc:
79
+ ALLOWED_OTHER = Set.new(%w(before after between none all))
80
+
81
+ #
82
+ # Convert the facet to date params.
83
+ #
84
+ def to_params
85
+ super.merge(
86
+ :"facet.date" => [@field.indexed_name],
87
+ param_key('date.start') => start_time.utc.xmlschema,
88
+ param_key('date.end') => end_time.utc.xmlschema,
89
+ param_key('date.gap') => "+#{interval}SECONDS",
90
+ param_key('date.other') => others
91
+ )
92
+ end
93
+
94
+ private
95
+
96
+ #
97
+ # Start time for facet range
98
+ #
99
+ # ==== Returns
100
+ #
101
+ # Time:: Start time
102
+ #
103
+ def start_time
104
+ @options[:time_range].first
105
+ end
106
+
107
+ #
108
+ # End time for facet range
109
+ #
110
+ # ==== Returns
111
+ #
112
+ # Time:: End time
113
+ #
114
+ def end_time
115
+ @options[:time_range].last
116
+ end
117
+
118
+ #
119
+ # Time interval that each facet row should cover. Default is 1 day.
120
+ #
121
+ # ===== Returns
122
+ #
123
+ # Integer:: Time interval in seconds
124
+ #
125
+ def interval
126
+ @options[:time_interval] || 86400
127
+ end
128
+
129
+ #
130
+ # Other time ranges to create facet rows for. Allowed values are defined
131
+ # in ALLOWED_OTHER constant.
132
+ #
133
+ def others
134
+ if others = @options[:time_other]
135
+ Array(others).map do |other|
136
+ other = other.to_s
137
+ unless ALLOWED_OTHER.include?(other)
138
+ raise(
139
+ ArgumentError,
140
+ "#{other.inspect} is not a valid argument for :time_other"
141
+ )
142
+ end
143
+ other
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,57 @@
1
+ module Sunspot
2
+ module Query
3
+ #
4
+ # This class acts as a base class for query components that encapsulate
5
+ # operations on fields. It is subclassed by the Query::Query class and the
6
+ # Query::DynamicQuery class.
7
+ #
8
+ class FieldQuery < Scope
9
+ #
10
+ # Add a field facet. See Sunspot::Facet for more information.
11
+ #
12
+ # ==== Parameters
13
+ #
14
+ # field_name<Symbol>:: Name of the field on which to get a facet
15
+ #
16
+ # ==== Returns
17
+ #
18
+ # FieldFacet:: The field facet object
19
+ #
20
+ def add_field_facet(field_name, options = nil)
21
+ add_component(FieldFacet.build(build_field(field_name), options || {}))
22
+ end
23
+
24
+ #
25
+ # Add a query facet.
26
+ #
27
+ # ==== Parameters
28
+ #
29
+ # name<Symbol>::
30
+ # The name associated with the query facet. This is not passed to Solr,
31
+ # but allows the user to retrieve the facet result by passing the name
32
+ # to the Search#facet method.
33
+ #
34
+ # ==== Returns
35
+ #
36
+ # QueryFacet:: The query facet object
37
+ #
38
+ def add_query_facet(name)
39
+ add_component(facet = QueryFacet.new(name, setup))
40
+ query_facets[name.to_sym] = facet
41
+ facet
42
+ end
43
+
44
+ #
45
+ # Set result ordering.
46
+ #
47
+ # ==== Parameters
48
+ #
49
+ # field_name<Symbol>:: Name of the field on which to order
50
+ # direction<Symbol>:: :asc or :desc (default :asc)
51
+ #
52
+ def order_by(field_name, direction = nil)
53
+ add_sort(Sort.new(build_field(field_name), direction))
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,39 @@
1
+ module Sunspot
2
+ module Query
3
+ #
4
+ # A query component that holds information about pagination. Unlike other
5
+ # query components, this one is mutable, because the query itself holds a
6
+ # reference to it and updates it if pagination is changed.
7
+ #
8
+ class Pagination #:nodoc:
9
+ attr_reader :page, :per_page
10
+
11
+ def initialize(configuration, page = nil, per_page = nil)
12
+ @configuration = configuration
13
+ self.page, self.per_page = page, per_page
14
+ end
15
+
16
+ def to_params
17
+ { :start => start, :rows => rows }
18
+ end
19
+
20
+ def page=(page)
21
+ @page = page || 1
22
+ end
23
+
24
+ def per_page=(per_page)
25
+ @per_page = per_page || @configuration.pagination.default_per_page
26
+ end
27
+
28
+ private
29
+
30
+ def start
31
+ (@page - 1) * @per_page
32
+ end
33
+
34
+ def rows
35
+ @per_page
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,72 @@
1
+ module Sunspot
2
+ module Query
3
+ #
4
+ # QueryFacets encapsulate requests for Sunspot's query faceting capability.
5
+ # They are created by the FieldQuery#add_query_facet method.
6
+ #
7
+ #--
8
+ #
9
+ # The actual concept of a QueryFacet is somewhat artificial - it provides a
10
+ # grouping for the facet at the Sunspot level, which provides a nicer and
11
+ # more consistent API in Sunspot; Solr does not provide any grouping for
12
+ # query facet rows, instead returning each requested row individually, keyed
13
+ # by the boolean phrase used in the facet query.
14
+ #
15
+ class QueryFacet
16
+ attr_reader :name #:nodoc:
17
+
18
+ def initialize(name, setup) #:nodoc:
19
+ @name = name
20
+ @setup = setup
21
+ @components = []
22
+ end
23
+
24
+ #
25
+ # Add a QueryFacetRow to this facet. The label argument becomes the value
26
+ # of the Sunspot::QueryFacetRow object corresponding to this query facet
27
+ # row.
28
+ #
29
+ # ==== Parameters
30
+ #
31
+ # label<Object>::
32
+ # An object that will become the value of the result row. Use whatever
33
+ # type is most intuitive.
34
+ #
35
+ # ==== Returns
36
+ #
37
+ # QueryFacetRow:: QueryFacetRow object containing scope for this row
38
+ #
39
+ def add_row(label)
40
+ @components << row = QueryFacetRow.new(label, @setup)
41
+ row
42
+ end
43
+
44
+ #
45
+ # Express this query facet as Solr parameters
46
+ #
47
+ # ==== Returns
48
+ #
49
+ # Hash:: Solr params hash
50
+ #
51
+ def to_params #:nodoc:
52
+ components = @components.map { |component| component.to_boolean_phrase }
53
+ components = components.first if components.length == 1
54
+ {
55
+ :facet => 'true',
56
+ :"facet.query" => components
57
+ }
58
+ end
59
+
60
+ #
61
+ # Get query facet rows (used when constructing results)
62
+ #
63
+ # ==== Returns
64
+ #
65
+ # Array:: Array of QueryFacetRow objects.
66
+ #
67
+ def rows #:nodoc:
68
+ @components
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,19 @@
1
+ module Sunspot
2
+ module Query
3
+ #
4
+ # QueryFacetRow objects encapsulate restrictions for a particular
5
+ # QueryFacet. They also contain a label attribute, which is used as the
6
+ # value for the search result's corresponding facet row object.
7
+ #
8
+ # See Query::Scope for the API provided.
9
+ #
10
+ class QueryFacetRow < Connective::Conjunction
11
+ attr_reader :label #:nodoc:
12
+
13
+ def initialize(label, setup) #:nodoc:
14
+ super(setup)
15
+ @label = label
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,225 @@
1
+ module Sunspot
2
+ module Query
3
+ module Restriction #:nodoc:
4
+ class <<self
5
+ #
6
+ # Return the names of all of the restriction classes that should be made
7
+ # available to the DSL.
8
+ #
9
+ # ==== Returns
10
+ #
11
+ # Array:: Collection of restriction class names
12
+ #
13
+ def names
14
+ constants - %w(Base SameAs) #XXX this seems ugly
15
+ end
16
+
17
+ def [](restriction_name)
18
+ @types ||= {}
19
+ @types[restriction_name.to_sym] ||= const_get(Sunspot::Util.camel_case(restriction_name.to_s))
20
+ end
21
+ end
22
+
23
+ #
24
+ # Subclasses of this class represent restrictions that can be applied to
25
+ # a Sunspot query. The Sunspot::DSL::Restriction class presents a builder
26
+ # API for instances of this class.
27
+ #
28
+ # Implementations of this class must respond to #to_params and
29
+ # #to_negative_params. Instead of implementing those methods, they may
30
+ # choose to implement any of:
31
+ #
32
+ # * #to_positive_boolean_phrase, and optionally #to_negative_boolean_phrase
33
+ # * #to_solr_conditional
34
+ #
35
+ class Base #:nodoc:
36
+ include RSolr::Char
37
+
38
+ def initialize(field, value, negative = false)
39
+ @field, @value, @negative = field, value, negative
40
+ end
41
+
42
+ #
43
+ # A hash representing this restriction in solr-ruby's parameter format.
44
+ # All restriction implementations must respond to this method; however,
45
+ # the base implementation delegates to the #to_positive_boolean_phrase method, so
46
+ # subclasses may (and probably should) choose to implement that method
47
+ # instead.
48
+ #
49
+ # ==== Returns
50
+ #
51
+ # Hash:: Representation of this restriction as solr-ruby parameters
52
+ #
53
+ def to_params
54
+ { :fq => [to_boolean_phrase] }
55
+ end
56
+
57
+ #
58
+ # Return the boolean phrase associated with this restriction object.
59
+ # Differentiates between positive and negative boolean phrases depending
60
+ # on whether this restriction is negated.
61
+ #
62
+ def to_boolean_phrase
63
+ unless negative?
64
+ to_positive_boolean_phrase
65
+ else
66
+ to_negative_boolean_phrase
67
+ end
68
+ end
69
+
70
+ #
71
+ # Boolean phrase representing this restriction in the positive. Subclasses
72
+ # may choose to implement this method rather than #to_params; however,
73
+ # this method delegates to the abstract #to_solr_conditional method, which
74
+ # in most cases will be what subclasses will want to implement.
75
+ # #to_solr_conditional contains the boolean phrase representing the
76
+ # condition but leaves out the field name (see built-in implementations
77
+ # for examples)
78
+ #
79
+ # ==== Returns
80
+ #
81
+ # String:: Boolean phrase for restriction in the positive
82
+ #
83
+ def to_positive_boolean_phrase
84
+ "#{escape(@field.indexed_name)}:#{to_solr_conditional}"
85
+ end
86
+
87
+ #
88
+ # Boolean phrase representing this restriction in the negative. Subclasses
89
+ # may choose to implement this method, but it is not necessary, as the
90
+ # base implementation delegates to #to_positive_boolean_phrase.
91
+ #
92
+ # ==== Returns
93
+ #
94
+ # String:: Boolean phrase for restriction in the negative
95
+ #
96
+ def to_negative_boolean_phrase
97
+ "-#{to_positive_boolean_phrase}"
98
+ end
99
+
100
+ protected
101
+
102
+ #
103
+ # Whether this restriction should be negated from its original meaning
104
+ #
105
+ def negative?
106
+ !!@negative
107
+ end
108
+
109
+ #
110
+ # Return escaped Solr API representation of given value
111
+ #
112
+ # ==== Parameters
113
+ #
114
+ # value<Object>::
115
+ # value to convert to Solr representation (default: @value)
116
+ #
117
+ # ==== Returns
118
+ #
119
+ # String:: Solr API representation of given value
120
+ #
121
+ def solr_value(value = @value)
122
+ escape(@field.to_indexed(value))
123
+ end
124
+ end
125
+
126
+ #
127
+ # Results must have field with value equal to given value. If the value
128
+ # is nil, results must have no value for the given field.
129
+ #
130
+ class EqualTo < Base
131
+ def to_positive_boolean_phrase
132
+ unless @value.nil?
133
+ super
134
+ else
135
+ "-#{escape(@field.indexed_name)}:[* TO *]"
136
+ end
137
+ end
138
+
139
+ def to_negative_boolean_phrase
140
+ unless @value.nil?
141
+ super
142
+ else
143
+ "#{escape(@field.indexed_name)}:[* TO *]"
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ def to_solr_conditional
150
+ "#{solr_value}"
151
+ end
152
+ end
153
+
154
+ #
155
+ # Results must have field with value less than given value
156
+ #
157
+ class LessThan < Base
158
+ private
159
+
160
+ def to_solr_conditional
161
+ "[* TO #{solr_value}]"
162
+ end
163
+ end
164
+
165
+ #
166
+ # Results must have field with value greater than given value
167
+ #
168
+ class GreaterThan < Base
169
+ private
170
+
171
+ def to_solr_conditional
172
+ "[#{solr_value} TO *]"
173
+ end
174
+ end
175
+
176
+ #
177
+ # Results must have field with value in given range
178
+ #
179
+ class Between < Base
180
+ private
181
+
182
+ def to_solr_conditional
183
+ "[#{solr_value(@value.first)} TO #{solr_value(@value.last)}]"
184
+ end
185
+ end
186
+
187
+ #
188
+ # Results must have field with value included in given collection
189
+ #
190
+ class AnyOf < Base
191
+ private
192
+
193
+ def to_solr_conditional
194
+ "(#{@value.map { |v| solr_value v } * ' OR '})"
195
+ end
196
+ end
197
+
198
+ #
199
+ # Results must have field with values matching all values in given
200
+ # collection (only makes sense for fields with multiple values)
201
+ #
202
+ class AllOf < Base
203
+ private
204
+
205
+ def to_solr_conditional
206
+ "(#{@value.map { |v| solr_value v } * ' AND '})"
207
+ end
208
+ end
209
+
210
+ #
211
+ # Result must be the exact instance given (only useful when negated).
212
+ #
213
+ class SameAs < Base
214
+ def initialize(object, negative = false)
215
+ @object, @negative = object, negative
216
+ end
217
+
218
+ def to_positive_boolean_phrase
219
+ adapter = Adapters::InstanceAdapter.adapt(@object)
220
+ "id:#{escape(adapter.index_id)}"
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end