kuahyeow-sunspot 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. data/History.txt +83 -0
  2. data/LICENSE +18 -0
  3. data/README.rdoc +154 -0
  4. data/Rakefile +9 -0
  5. data/TODO +9 -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/adapters.rb +265 -0
  11. data/lib/sunspot/composite_setup.rb +186 -0
  12. data/lib/sunspot/configuration.rb +38 -0
  13. data/lib/sunspot/data_extractor.rb +47 -0
  14. data/lib/sunspot/date_facet.rb +36 -0
  15. data/lib/sunspot/date_facet_row.rb +17 -0
  16. data/lib/sunspot/dsl/field_query.rb +72 -0
  17. data/lib/sunspot/dsl/fields.rb +86 -0
  18. data/lib/sunspot/dsl/query.rb +59 -0
  19. data/lib/sunspot/dsl/query_facet.rb +31 -0
  20. data/lib/sunspot/dsl/restriction.rb +25 -0
  21. data/lib/sunspot/dsl/scope.rb +193 -0
  22. data/lib/sunspot/dsl/search.rb +30 -0
  23. data/lib/sunspot/dsl.rb +3 -0
  24. data/lib/sunspot/facet.rb +51 -0
  25. data/lib/sunspot/facet_row.rb +34 -0
  26. data/lib/sunspot/field.rb +157 -0
  27. data/lib/sunspot/field_factory.rb +126 -0
  28. data/lib/sunspot/indexer.rb +123 -0
  29. data/lib/sunspot/instantiated_facet.rb +38 -0
  30. data/lib/sunspot/instantiated_facet_row.rb +12 -0
  31. data/lib/sunspot/query/base_query.rb +94 -0
  32. data/lib/sunspot/query/connective.rb +126 -0
  33. data/lib/sunspot/query/dynamic_query.rb +69 -0
  34. data/lib/sunspot/query/field_facet.rb +149 -0
  35. data/lib/sunspot/query/field_query.rb +57 -0
  36. data/lib/sunspot/query/pagination.rb +39 -0
  37. data/lib/sunspot/query/query_facet.rb +72 -0
  38. data/lib/sunspot/query/query_facet_row.rb +19 -0
  39. data/lib/sunspot/query/restriction.rb +233 -0
  40. data/lib/sunspot/query/scope.rb +165 -0
  41. data/lib/sunspot/query/sort.rb +36 -0
  42. data/lib/sunspot/query/sort_composite.rb +33 -0
  43. data/lib/sunspot/query.rb +190 -0
  44. data/lib/sunspot/query_facet.rb +33 -0
  45. data/lib/sunspot/query_facet_row.rb +21 -0
  46. data/lib/sunspot/schema.rb +165 -0
  47. data/lib/sunspot/search/hit.rb +66 -0
  48. data/lib/sunspot/search.rb +220 -0
  49. data/lib/sunspot/session.rb +201 -0
  50. data/lib/sunspot/setup.rb +271 -0
  51. data/lib/sunspot/type.rb +200 -0
  52. data/lib/sunspot/util.rb +164 -0
  53. data/lib/sunspot.rb +470 -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 +1018 -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 +335 -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 +289 -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 +48 -0
  92. data/spec/mocks/photo.rb +8 -0
  93. data/spec/mocks/post.rb +73 -0
  94. data/spec/mocks/user.rb +8 -0
  95. data/spec/spec_helper.rb +47 -0
  96. data/tasks/gemspec.rake +24 -0
  97. data/tasks/rcov.rake +28 -0
  98. data/tasks/rdoc.rake +22 -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 +247 -0
@@ -0,0 +1,186 @@
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_field(field_name)
52
+ text_fields_hash[field_name.to_sym] || raise(
53
+ UnrecognizedFieldError,
54
+ "No text field configured for #{@types * ', '} with name '#{field_name}'"
55
+ )
56
+ end
57
+
58
+ #
59
+ # Get a Sunspot::AttributeField instance corresponding to the given field name
60
+ #
61
+ # ==== Parameters
62
+ #
63
+ # field_name<Symbol>:: The public field name for which to find a field
64
+ #
65
+ # ==== Returns
66
+ #
67
+ # Sunspot::AttributeField The field object corresponding to the given name
68
+ #
69
+ # ==== Raises
70
+ #
71
+ # ArgumentError::
72
+ # If the given field name is not configured for the types being queried
73
+ #
74
+ def field(field_name) #:nodoc:
75
+ fields_hash[field_name.to_sym] || raise(
76
+ UnrecognizedFieldError,
77
+ "No field configured for #{@types * ', '} with name '#{field_name}'"
78
+ )
79
+ end
80
+
81
+ #
82
+ # Get a dynamic field factory for the given base name.
83
+ #
84
+ # ==== Returns
85
+ #
86
+ # DynamicFieldFactory:: Factory for dynamic fields with the given base name
87
+ #
88
+ # ==== Raises
89
+ #
90
+ # UnrecognizedFieldError::
91
+ # If the given base name is not configured as a dynamic field for the types being queried
92
+ #
93
+ def dynamic_field_factory(field_name)
94
+ dynamic_field_factories_hash[field_name.to_sym] || raise(
95
+ UnrecognizedFieldError,
96
+ "No dynamic field configured for #{@types * ', '} with name #{field_name.inspect}"
97
+ )
98
+ end
99
+
100
+ #
101
+ # Collection of all text fields configured for any of the enclosed types.
102
+ #
103
+ # === Returns
104
+ #
105
+ # Array:: Text fields configured for the enclosed types
106
+ #
107
+ def text_fields
108
+ @text_fields ||= text_fields_hash.values
109
+ end
110
+
111
+ private
112
+
113
+ #
114
+ # Return a hash of field names to text field objects, containing all fields
115
+ # that are configured for any of the types enclosed.
116
+ #
117
+ # ==== Returns
118
+ #
119
+ # Hash:: Hash of field names to text field objects.
120
+ #
121
+ def text_fields_hash
122
+ @text_fields_hash ||=
123
+ setups.inject({}) do |hash, setup|
124
+ setup.text_fields.each do |text_field|
125
+ hash[text_field.name] ||= text_field
126
+ end
127
+ hash
128
+ end
129
+ end
130
+
131
+ #
132
+ # Return a hash of field names to field objects, containing all fields
133
+ # that are common to all of the classes enclosed. In order for fields
134
+ # to be common, they must be of the same type and have the same
135
+ # value for allow_multiple? and stored?. This method is memoized.
136
+ #
137
+ # ==== Returns
138
+ #
139
+ # Hash:: field names keyed to field objects
140
+ #
141
+ def fields_hash
142
+ @fields_hash ||=
143
+ begin
144
+ fields_hash = @types.inject({}) do |hash, type|
145
+ Setup.for(type).fields.each do |field|
146
+ (hash[field.name.to_sym] ||= {})[type.name] = field
147
+ end
148
+ hash
149
+ end
150
+ fields_hash.each_pair do |field_name, field_configurations_hash|
151
+ if @types.any? { |type| field_configurations_hash[type.name].nil? } # at least one type doesn't have this field configured
152
+ fields_hash.delete(field_name)
153
+ elsif field_configurations_hash.values.map { |configuration| configuration.indexed_name }.uniq.length != 1 # fields with this name have different configs
154
+ fields_hash.delete(field_name)
155
+ else
156
+ fields_hash[field_name] = field_configurations_hash.values.first
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ #
163
+ # Return a hash of dynamic field base names to dynamic field factories for
164
+ # those base names. Criteria for the inclusion are the same as for
165
+ # #fields_hash()
166
+ #
167
+ def dynamic_field_factories_hash
168
+ @dynamic_field_factories_hash ||=
169
+ begin
170
+ dynamic_field_factories_hash = @types.inject({}) do |hash, type|
171
+ Setup.for(type).dynamic_field_factories.each do |field_factory|
172
+ (hash[field_factory.name.to_sym] ||= {})[type.name] = field_factory
173
+ end
174
+ hash
175
+ end
176
+ dynamic_field_factories_hash.each_pair do |field_name, field_configurations_hash|
177
+ if @types.any? { |type| field_configurations_hash[type.name].nil? }
178
+ dynamic_field_factories_hash.delete(field_name)
179
+ else
180
+ dynamic_field_factories_hash[field_name] = field_configurations_hash.values.first
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,38 @@
1
+ module Sunspot
2
+ # The Sunspot::Configuration module provides a factory method for Sunspot
3
+ # configuration objects. Available properties are:
4
+ #
5
+ # Sunspot.config.http_client::
6
+ # The client to use for HTTP communication with Solr. Available options are
7
+ # :net_http, which is the default and uses Ruby's built-in pure-Ruby HTTP
8
+ # library; and :curb, which uses Ruby's libcurl bindings and requires
9
+ # installation of the 'curb' gem.
10
+ # Sunspot.config.solr.url::
11
+ # The URL at which to connect to Solr
12
+ # (default: 'http://localhost:8983/solr')
13
+ # Sunspot.config.pagination.default_per_page::
14
+ # Solr always paginates its results. This sets Sunspot's default result
15
+ # count per page if it is not explicitly specified in the query.
16
+ #
17
+ module Configuration
18
+ class <<self
19
+ # Factory method to build configuration instances.
20
+ #
21
+ # ==== Returns
22
+ #
23
+ # LightConfig::Configuration:: new configuration instance with defaults
24
+ #
25
+ def build #:nodoc:
26
+ LightConfig.build do
27
+ http_client :net_http
28
+ solr do
29
+ url 'http://127.0.0.1:8983/solr'
30
+ end
31
+ pagination do
32
+ default_per_page 30
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,47 @@
1
+ module Sunspot
2
+ #
3
+ # DataExtractors present an internal API for the indexer to use to extract
4
+ # field values from models for indexing. They must implement the #value_for
5
+ # method, which takes an object and returns the value extracted from it.
6
+ #
7
+ module DataExtractor #:nodoc: all
8
+ #
9
+ # AttributeExtractors extract data by simply calling a method on the block.
10
+ #
11
+ class AttributeExtractor
12
+ def initialize(attribute_name)
13
+ @attribute_name = attribute_name
14
+ end
15
+
16
+ def value_for(object)
17
+ object.send(@attribute_name)
18
+ end
19
+ end
20
+
21
+ #
22
+ # BlockExtractors extract data by evaluating a block in the context of the
23
+ # object instance, or if the block takes an argument, by passing the object
24
+ # as the argument to the block. Either way, the return value of the block is
25
+ # the value returned by the extractor.
26
+ #
27
+ class BlockExtractor
28
+ def initialize(&block)
29
+ @block = block
30
+ end
31
+
32
+ def value_for(object)
33
+ Util.instance_eval_or_call(object, &@block)
34
+ end
35
+ end
36
+
37
+ class Constant
38
+ def initialize(value)
39
+ @value = value
40
+ end
41
+
42
+ def value_for(object)
43
+ @value
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,36 @@
1
+ module Sunspot
2
+ #
3
+ # Date facets are retrieved by passing a :time_range key into the
4
+ # DSL::FieldQuery#facet options. They are only available for Date and Time
5
+ # type fields. The #value for date facet rows is a Range object encapsulating
6
+ # the time range covered by the row.
7
+ #
8
+ class DateFacet < Facet
9
+ def initialize(facet_values, field) #:nodoc:
10
+ @gap = facet_values.delete('gap')[/\+(\d+)SECONDS/,1].to_i
11
+ %w(start end).each { |key| facet_values.delete(key) }
12
+ super(facet_values.to_a.flatten, field)
13
+ end
14
+
15
+ #
16
+ # Get the rows of this date facet, which are instances of DateFacetRow.
17
+ # The rows will always be sorted in chronological order.
18
+ #
19
+ #--
20
+ #
21
+ # The date facet info comes back from Solr as a hash, so we need to sort
22
+ # it manually. FIXME this currently assumes we want to do a "lexical"
23
+ # sort, but we should support count sort as well, even if it's not a
24
+ # common use case.
25
+ #
26
+ def rows
27
+ super.sort { |a, b| a.value.first <=> b.value.first }
28
+ end
29
+
30
+ private
31
+
32
+ def new_row(pair)
33
+ DateFacetRow.new(pair, @gap, self)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,17 @@
1
+ module Sunspot
2
+ #TODO document
3
+ class DateFacetRow < FacetRow
4
+ def initialize(pair, gap, facet)
5
+ @gap = gap
6
+ super(pair, facet)
7
+ end
8
+
9
+ def value
10
+ @value ||=
11
+ begin
12
+ start_date = @facet.field.cast(@pair[0])
13
+ Range.new(start_date, start_date + @gap)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,72 @@
1
+ module Sunspot
2
+ module DSL
3
+ #
4
+ # Provides an API for areas of the query DSL that operate on specific
5
+ # fields. This functionality is provided by the query DSL and the dynamic
6
+ # query DSL.
7
+ #
8
+ class FieldQuery < Scope
9
+ # Specify the order that results should be returned in. This method can
10
+ # be called multiple times; precedence will be in the order given.
11
+ #
12
+ # ==== Parameters
13
+ #
14
+ # field_name<Symbol>:: the field to use for ordering
15
+ # direction<Symbol>:: :asc or :desc (default :asc)
16
+ #
17
+ def order_by(field_name, direction = nil)
18
+ @query.order_by(field_name, direction)
19
+ end
20
+
21
+ #
22
+ # Order results randomly. This will (generally) return the results in a
23
+ # different order each time a search is called.
24
+ #
25
+ def order_by_random
26
+ @query.order_by_random
27
+ end
28
+
29
+ # Request facets on the given field names. If the last argument is a hash,
30
+ # the given options will be applied to all specified fields. See
31
+ # Sunspot::Search#facet and Sunspot::Facet for information on what is
32
+ # returned.
33
+ #
34
+ # ==== Parameters
35
+ #
36
+ # field_names...<Symbol>:: fields for which to return field facets
37
+ #
38
+ # ==== Options
39
+ #
40
+ # :sort<Symbol>::
41
+ # Either :count (values matching the most terms first) or :index (lexical)
42
+ # :limit<Integer>::
43
+ # The maximum number of facet rows to return
44
+ # :minimum_count<Integer>::
45
+ # The minimum count a facet row must have to be returned
46
+ # :zeros<Boolean>::
47
+ # Return facet rows for which there are no matches (equivalent to
48
+ # :minimum_count => 0). Default is false.
49
+ #
50
+ def facet(*field_names, &block)
51
+ if block
52
+ if field_names.length != 1
53
+ raise(
54
+ ArgumentError,
55
+ "wrong number of arguments (#{field_names.length} for 1)"
56
+ )
57
+ end
58
+ name = field_names.first
59
+ DSL::QueryFacet.new(@query.add_query_facet(name)).instance_eval(&block)
60
+ else
61
+ options =
62
+ if field_names.last.is_a?(Hash)
63
+ field_names.pop
64
+ end
65
+ for field_name in field_names
66
+ @query.add_field_facet(field_name, options)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,86 @@
1
+ module Sunspot
2
+ module DSL #:nodoc:
3
+ # The Fields class provides a DSL for specifying field definitions in the
4
+ # Sunspot.setup block. As well as the #text method, which creates fulltext
5
+ # fields, uses #method_missing to allow definition of typed fields. The
6
+ # available methods are determined by the constants defined in
7
+ # Sunspot::Type - in theory (though this is untested), plugin developers
8
+ # should be able to add support for new types simply by creating new
9
+ # implementations in Sunspot::Type
10
+ #
11
+ class Fields
12
+ def initialize(setup) #:nodoc:
13
+ @setup = setup
14
+ end
15
+
16
+ # Add a text field. Text fields are tokenized before indexing and are
17
+ # the only fields searched in fulltext searches. If a block is passed,
18
+ # create a virtual field; otherwise create an attribute field.
19
+ #
20
+ # If options are passed, they will be applied to all the given fields.
21
+ #
22
+ # ==== Parameters
23
+ #
24
+ # names...<Symbol>:: One or more field names
25
+ #
26
+ # ==== Options
27
+ #
28
+ # :boost<Float>::
29
+ # Boost that should be applied to this field for keyword search
30
+ #
31
+ def text(*names, &block)
32
+ options = names.pop if names.last.is_a?(Hash)
33
+ for name in names
34
+ @setup.add_text_field_factory(
35
+ name,
36
+ options || {},
37
+ &block
38
+ )
39
+ end
40
+ end
41
+
42
+ #
43
+ # Specify a document-level boost. As with fields, you have the option of
44
+ # passing an attribute name which will be called on each model, or a block
45
+ # to be evaluated in the model's context. As well as these two options,
46
+ # this method can also take a constant number, meaning that all indexed
47
+ # documents of this class will have the specified boost.
48
+ #
49
+ # ==== Parameters
50
+ #
51
+ # attr_name<Symbol,~.to_f>:: Attribute name to call or a numeric constant
52
+ #
53
+ def boost(attr_name = nil, &block)
54
+ @setup.add_document_boost(attr_name, &block)
55
+ end
56
+
57
+ # method_missing is used to provide access to typed fields, because
58
+ # developers should be able to add new Sunspot::Type implementations
59
+ # dynamically and have them recognized inside the Fields DSL. Like #text,
60
+ # these methods will create a VirtualField if a block is passed, or an
61
+ # AttributeField if not.
62
+ #
63
+ # ==== Example
64
+ #
65
+ # Sunspot.setup(File) do
66
+ # time :mtime
67
+ # end
68
+ #
69
+ # The call to +time+ will create a field of type Sunspot::Types::TimeType
70
+ #
71
+ def method_missing(method, *args, &block)
72
+ begin
73
+ type = Type.const_get("#{Util.camel_case(method.to_s.sub(/^dynamic_/, ''))}Type")
74
+ rescue(NameError)
75
+ super(method.to_sym, *args, &block) and return
76
+ end
77
+ name = args.shift
78
+ if method.to_s =~ /^dynamic_/
79
+ @setup.add_dynamic_field_factory(name, type, *args, &block)
80
+ else
81
+ @setup.add_field_factory(name, type, *args, &block)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,59 @@
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 Query < FieldQuery
12
+ # Specify a phrase that should be searched as fulltext. Only +text+
13
+ # fields are searched - see DSL::Fields.text
14
+ #
15
+ # Keyword search is executed using Solr's dismax handler, which strikes
16
+ # a good balance between powerful and foolproof. In particular,
17
+ # well-matched quotation marks can be used to group phrases, and the
18
+ # + and - modifiers work as expected. All other special Solr boolean
19
+ # syntax is escaped, and mismatched quotes are ignored entirely.
20
+ #
21
+ # ==== Parameters
22
+ #
23
+ # keywords<String>:: phrase to perform fulltext search on
24
+ #
25
+ # ==== Options
26
+ #
27
+ # :fields<Array>::
28
+ # List of fields that should be searched for keywords. Defaults to all
29
+ # fields configured for the types under search.
30
+ #
31
+ def keywords(keywords, options = {})
32
+ @query.set_keywords(keywords, options)
33
+ end
34
+
35
+ # Paginate your search. This works the same way as WillPaginate's
36
+ # paginate().
37
+ #
38
+ # Note that Solr searches are _always_ paginated. Not calling #paginate is
39
+ # the equivalent of calling:
40
+ #
41
+ # paginate(:page => 1, :per_page => Sunspot.config.pagination.default_per_page)
42
+ #
43
+ # ==== Options (options)
44
+ #
45
+ # :page<Integer>:: The requested page (required)
46
+ #
47
+ # :per_page<Integer>::
48
+ # How many results to return per page. The default is the value in
49
+ # +Sunspot.config.pagination.default_per_page+
50
+ #
51
+ def paginate(options = {})
52
+ page = options.delete(:page) || raise(ArgumentError, "paginate requires a :page argument")
53
+ per_page = options.delete(:per_page)
54
+ raise ArgumentError, "unknown argument #{options.keys.first.inspect} passed to paginate" unless options.empty?
55
+ @query.paginate(page, per_page)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,31 @@
1
+ module Sunspot
2
+ module DSL
3
+ #
4
+ # This tiny DSL class implements the DSL for the FieldQuery.facet
5
+ # method.
6
+ #
7
+ class QueryFacet
8
+ def initialize(query_facet) #:nodoc:
9
+ @query_facet = query_facet
10
+ end
11
+
12
+ #
13
+ # Add a row to this query facet. The label argument can be anything; it's
14
+ # simply the value that's passed into the Sunspot::QueryFacetRow object
15
+ # corresponding to the row that's created. Use whatever seems most
16
+ # intuitive.
17
+ #
18
+ # The block is evaluated in the context of a Sunspot::DSL::Scope, meaning
19
+ # any restrictions can be placed on the documents matching this facet row.
20
+ #
21
+ # ==== Parameters
22
+ #
23
+ # label<Object>::
24
+ # An object used to identify this facet row in the results.
25
+ #
26
+ def row(label, &block)
27
+ Scope.new(@query_facet.add_row(label)).instance_eval(&block)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,25 @@
1
+ module Sunspot
2
+ module DSL #:nodoc:
3
+ #
4
+ # This class presents an API for building restrictions in the query DSL. The
5
+ # methods exposed are the snake-cased names of the classes defined in the
6
+ # Restriction module, with the exception of Base and SameAs. All methods
7
+ # take a single argument, which is the value to be applied to the
8
+ # restriction.
9
+ #
10
+ class Restriction #:nodoc:
11
+ def initialize(field_name, query, negative)
12
+ @field_name, @query, @negative = field_name, query, negative
13
+ end
14
+
15
+ Sunspot::Query::Restriction.names.each do |class_name|
16
+ method_name = Util.snake_case(class_name.to_s)
17
+ module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
18
+ def #{method_name}(value)
19
+ @query.add_restriction(@field_name, Sunspot::Query::Restriction::#{class_name}, value, @negative)
20
+ end
21
+ RUBY
22
+ end
23
+ end
24
+ end
25
+ end