gojee-sunspot 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
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,32 @@
1
+ module Sunspot
2
+ module DSL #:nodoc
3
+ module Paginatable
4
+ # Paginate your search. This works the same way as WillPaginate's
5
+ # paginate().
6
+ #
7
+ # Note that Solr searches are _always_ paginated. Not calling #paginate is
8
+ # the equivalent of calling:
9
+ #
10
+ # paginate(:page => 1, :per_page => Sunspot.config.pagination.default_per_page)
11
+ #
12
+ # ==== Options (options)
13
+ #
14
+ # :page<Integer,String>:: The requested page. The default is 1.
15
+ #
16
+ # :per_page<Integer,String>::
17
+ # How many results to return per page. The default is the value in
18
+ # +Sunspot.config.pagination.default_per_page+
19
+ #
20
+ # :offset<Integer,String>::
21
+ # Applies a shift to paginated records. The default is 0.
22
+ #
23
+ def paginate(options = {})
24
+ page = options.delete(:page)
25
+ per_page = options.delete(:per_page)
26
+ offset = options.delete(:offset)
27
+ raise ArgumentError, "unknown argument #{options.keys.first.inspect} passed to paginate" unless options.empty?
28
+ @query.paginate(page, per_page, offset)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,36 @@
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, setup, facet) #:nodoc:
9
+ @query, @setup, @facet = query, setup, 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
+ query_facet = Sunspot::Query::QueryFacet.new
28
+ Sunspot::Util.instance_eval_or_call(
29
+ Scope.new(@query.add_query_facet(query_facet), @setup),
30
+ &block
31
+ )
32
+ @facet.add_row(label, query_facet.to_boolean_phrase)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,25 @@
1
+ module Sunspot
2
+ module DSL
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
+ # Sunspot::Restriction module, with the exception of Base. All
7
+ # methods take a single argument, which is the value to be applied to the
8
+ # restriction.
9
+ #
10
+ class Restriction
11
+ def initialize(field, scope, negative) #:nodoc:
12
+ @field, @scope, @negative = field, scope, 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
+ @scope.add_restriction(@negative, @field, Sunspot::Query::Restriction::#{class_name}, *value)
20
+ end
21
+ RUBY
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,160 @@
1
+ module Sunspot
2
+ module DSL
3
+ class RestrictionWithNear < Restriction
4
+ def initialize(field, scope, query, negated)
5
+ super(field, scope, negated)
6
+ @query = query
7
+ end
8
+
9
+ #
10
+ # Perform a Geohash-based location restriction for the given `location`
11
+ # field. Though this uses the same API as other attribute-field
12
+ # restrictions, there are several differences between this and other
13
+ # scoping methods:
14
+ #
15
+ # * It can only be called from the top-level query; it cannot be nested
16
+ # in a `dynamic`, `any_of`, or `all_of` block. This is because geohash
17
+ # queries are not sent to Solr as filter queries like other scopes, but
18
+ # rather are part of the fulltext query sent to Solr.
19
+ # * Because it is included with the fulltext query (if any), location
20
+ # restrictions can be given boost. By default, an "exact"
21
+ # (maximum-precision) match will give the result a boost of 1.0; each
22
+ # lower level of precision gives a boost of 1/2 the next highest
23
+ # precision. See below for options to modify this behavior.
24
+ #
25
+ # ==== What is a Geohash?
26
+ #
27
+ # Geohash is a clever algorithm that creates a decodable digest of a
28
+ # geographical point. It does this by dividing the globe into
29
+ # quadrants, encoding the quadrant in which the point sits in the hash,
30
+ # dividing the quadrant into smaller quadrants, and repeating an arbitrary
31
+ # number of times (the "precision"). Because of the way Geohash are
32
+ # built, the shared Geohash prefix length of two locations will
33
+ # <em>usually</em> increase as the distance between the points decreases.
34
+ # Put another way, the geohashes of two nearby points will
35
+ # <em>usually</em> have a longer shared prefix than two points which are
36
+ # distant from one another.
37
+ #
38
+ # Read more about Geohashes on
39
+ # {Wikipedia}[http://en.wikipedia.org/wiki/Geohash] or play around with
40
+ # generating your own at {geohash.org}[http://geohash.org/].
41
+ #
42
+ # In Sunspot, GeoHashes can have a precision between 3 and 12; this is the
43
+ # number of characters in the hash. The precisions have the following
44
+ # maximum bounding box sizes, in miles:
45
+ #
46
+ # <dt>3</dt>
47
+ # <dd>389.07812</dd>
48
+ # <dt>4</dt>
49
+ # <dd>97.26953</dd>
50
+ # <dt>5</dt>
51
+ # <dd>24.31738</dd>
52
+ # <dt>6</dt>
53
+ # <dd>6.07935</dd>
54
+ # <dt>7</dt>
55
+ # <dd>1.51984
56
+ # <dt>8</dt>
57
+ # <dd>0.37996</dd>
58
+ # <dt>9</dt>
59
+ # <dd>0.09499</dd>
60
+ # <dt>10</dt>
61
+ # <dd>0.02375</dd>
62
+ # <dt>11</dt>
63
+ # <dd>0.00594</dd>
64
+ # <dt>12</dt>
65
+ # <dd>0.00148</dd>
66
+ #
67
+ # ==== Score, boost, and sorting with location search
68
+ #
69
+ # The concept of relevance scoring is a familiar one from fulltext search;
70
+ # Solr (or Lucene, actually) gives each result document a score based on
71
+ # how relevant the document's text is to the search phrase. Sunspot's
72
+ # location search also uses scoring to determine geographical relevance;
73
+ # using boosts, longer prefix matches (which are, in general,
74
+ # geographically closer to the search origin) are assigned higher
75
+ # relevance. This means that the results of a pure location search are
76
+ # <em>roughly</em> in order of geographical distance, as long as no other
77
+ # sort is specified explicitly.
78
+ #
79
+ # This geographical relevance plays on the same field as fulltext scoring;
80
+ # if you use both fulltext and geographical components in a single search,
81
+ # both types of relevance will be taken into account when scoring the
82
+ # matches. Thus, a very close fulltext match that's further away from the
83
+ # geographical origin will be scored similarly to a less precise fulltext
84
+ # match that is very close to the geographical origin. That's likely to be
85
+ # consistent with the way most users would expect a fulltext geographical
86
+ # search to work.
87
+ #
88
+ # ==== Options
89
+ #
90
+ # <dt><code>:precision</code></dt>
91
+ # <dd>The minimum precision at which locations should match. See the table
92
+ # of precisions and bounding-box sizes above; the proximity value will
93
+ # ensure that all matching documents share a bounding box of the
94
+ # corresponding maximum size with your origin point. The default value
95
+ # is 7, meaning all results will share a bounding box with edges of
96
+ # about one and a half miles with the origin.</dd>
97
+ # <dt><code>:boost</code></dt>
98
+ # <dd>The boost to apply to maximum-precision matches. Default is 1.0. You
99
+ # can use this option to adjust the weight given to geographic
100
+ # proximity versus fulltext matching, if you are doing both in a
101
+ # search.</dd>
102
+ # <dt><code>:precision_factor</code></dt>
103
+ # <dd>This option determines how much boost is applied to matches at lower
104
+ # precisions. The default value, 16.0, means that a match at precision
105
+ # N is 1/16 as relevant as a match at precision N+1 (this is consistent
106
+ # with the fact that each precision's bounding box is about sixteen
107
+ # times the size of the next highest precision.)</dd>
108
+ #
109
+ # ==== Example
110
+ #
111
+ # Sunspot.search(Post) do
112
+ # fulltext('pizza')
113
+ # with(:location).near(-40.0, -70.0, :boost => 2, :precision => 6)
114
+ # end
115
+ #
116
+ def near(lat, lng, options = {})
117
+ @query.fulltext.add_location(@field, lat, lng, options)
118
+ end
119
+
120
+ #
121
+ # Performs a query that is filtered by a radius around a given
122
+ # latitude and longitude.
123
+ #
124
+ # ==== Parameters
125
+ #
126
+ # :lat<Numeric>::
127
+ # Latitude (in degrees)
128
+ # :lon<Numeric>::
129
+ # Longitude (in degrees)
130
+ # :radius<Numeric>::
131
+ # Radius (in kilometers)
132
+ #
133
+ # ==== Options
134
+ #
135
+ # <dt><code>:bbox</code></dt>
136
+ # <dd>If `true`, performs the search using `bbox`. `bbox` is
137
+ # more performant, but also more inexact (guaranteed to encompass
138
+ # all of the points of interest, but may also include other points
139
+ # that are slightly outside of the required distance).</dd>
140
+ #
141
+ def in_radius(lat, lon, radius, options = {})
142
+ @query.add_geo(Sunspot::Query::Geofilt.new(@field, lat, lon, radius, options))
143
+ end
144
+
145
+ #
146
+ # Performs a query that is filtered by a bounding box
147
+ #
148
+ # ==== Parameters
149
+ #
150
+ # :first_corner<Array>::
151
+ # First corner (expressed as an array `[latitude, longitude]`)
152
+ # :second_corner<Array>::
153
+ # Second corner (expressed as an array `[latitude, longitude]`)
154
+ #
155
+ def in_bounding_box(first_corner, second_corner)
156
+ @query.add_geo(Sunspot::Query::Bbox.new(@field, first_corner, second_corner))
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,217 @@
1
+ module Sunspot
2
+ module DSL #:nodoc:
3
+ #
4
+ # This DSL presents methods for constructing restrictions and other query
5
+ # elements that are specific to fields. As well as being a superclass of
6
+ # Sunspot::DSL::StandardQuery, which presents the main query block, this
7
+ # DSL class is also used directly inside the #dynamic() block, which only
8
+ # allows operations on specific fields.
9
+ #
10
+ class Scope
11
+ NONE = Object.new
12
+
13
+ def initialize(scope, setup) #:nodoc:
14
+ @scope, @setup = scope, setup
15
+ end
16
+
17
+ #
18
+ # Build a positive restriction. This method can take three forms: equality
19
+ # restriction, restriction by another restriction, or identity
20
+ # restriction.
21
+ # In the first two forms, the first argument is a field name. If only a
22
+ # field name is specified, this method returns another DSL object which
23
+ # presents methods for attaching various restriction types.
24
+ # With two arguments, this creates a shorthand restriction: if the second
25
+ # argument is a scalar, an equality restriction is created; if it is a
26
+ # Range, a between restriction will be created; and if it is an Array, an
27
+ # any_of restriction will be created.
28
+ # The third from restricts the search results to a specific instance.
29
+ #
30
+ # ==== Parameters (restriction by field value)
31
+ #
32
+ # field_name<Symbol>:: Name of the field on which to place the restriction
33
+ # value<Object,Range,Array>::
34
+ # If passed, creates an equality, range, or any-of restriction based on
35
+ # the type of value passed.
36
+ #
37
+ # ==== Parameters (restriction by identity)
38
+ #
39
+ # args<Object>...::
40
+ # One or more instances that should be included in the results
41
+ #
42
+ # ==== Returns
43
+ #
44
+ # Sunspot::DSL::Restriction::
45
+ # Restriction DSL object (if only one argument is passed which is a
46
+ # field name)
47
+ #
48
+ # ==== Examples
49
+ #
50
+ # An equality restriction:
51
+ #
52
+ # Sunspot.search do
53
+ # with(:blog_id, 1)
54
+ # end
55
+ #
56
+ # Restrict by range:
57
+ #
58
+ # Sunspot.search do
59
+ # with(:average_rating, 3.0..5.0)
60
+ # end
61
+ #
62
+ # Restrict by a set of allowed values:
63
+ #
64
+ # Sunspot.search do
65
+ # with(:category_ids, [1, 5, 9])
66
+ # end
67
+ #
68
+ # Other restriction types:
69
+ #
70
+ # Sunspot.search(Post) do
71
+ # with(:average_rating).greater_than(3.0)
72
+ # end
73
+ #
74
+ # Restriction by identity:
75
+ #
76
+ # Sunspot.search(Post) do
77
+ # with(some_post_instance)
78
+ # end
79
+ #
80
+ def with(*args)
81
+ add_restriction(false, *args)
82
+ end
83
+
84
+ #
85
+ # Build a negative restriction (exclusion). This method works the same way
86
+ # asthe #with method.
87
+ #
88
+ def without(*args)
89
+ add_restriction(true, *args)
90
+ end
91
+
92
+ #
93
+ # Create a disjunction, scoping the results to documents that match any
94
+ # of the enclosed restrictions.
95
+ #
96
+ # ==== Example
97
+ #
98
+ # Sunspot.search(Post) do
99
+ # any_of do
100
+ # with(:expired_at).greater_than Time.now
101
+ # with :expired_at, nil
102
+ # end
103
+ # end
104
+ #
105
+ # This will return all documents who either have an expiration time in the
106
+ # future, or who do not have any expiration time at all.
107
+ #
108
+ def any_of(&block)
109
+ disjunction = @scope.add_disjunction
110
+ Util.instance_eval_or_call(Scope.new(disjunction, @setup), &block)
111
+ disjunction
112
+ end
113
+
114
+ #
115
+ # Create a conjunction, scoping the results to documents that match all of
116
+ # the enclosed restrictions. When called from the top level of a search
117
+ # block, this has no effect, but can be useful for grouping a conjunction
118
+ # inside a disjunction.
119
+ #
120
+ # ==== Example
121
+ #
122
+ # Sunspot.search(Post) do
123
+ # any_of do
124
+ # with(:blog_id, 1)
125
+ # all_of do
126
+ # with(:blog_id, 2)
127
+ # with(:category_ids, 3)
128
+ # end
129
+ # end
130
+ # end
131
+ #
132
+ def all_of(&block)
133
+ conjunction = @scope.add_conjunction
134
+ Util.instance_eval_or_call(Scope.new(conjunction, @setup), &block)
135
+ conjunction
136
+ end
137
+
138
+ #
139
+ # Apply restrictions, facets, and ordering to dynamic field instances.
140
+ # The block API is implemented by Sunspot::DSL::FieldQuery, which is a
141
+ # superclass of the Query DSL (thus providing a subset of the API, in
142
+ # particular only methods that refer to particular fields).
143
+ #
144
+ # ==== Parameters
145
+ #
146
+ # base_name<Symbol>:: The base name for the dynamic field definition
147
+ #
148
+ # ==== Example
149
+ #
150
+ # Sunspot.search Post do
151
+ # dynamic :custom do
152
+ # with :cuisine, 'Pizza'
153
+ # facet :atmosphere
154
+ # order_by :chef_name
155
+ # end
156
+ # end
157
+ #
158
+ def dynamic(base_name, &block)
159
+ Sunspot::Util.instance_eval_or_call(
160
+ Scope.new(@scope, @setup.dynamic_field_factory(base_name)),
161
+ &block
162
+ )
163
+ end
164
+
165
+ #
166
+ # Apply scope-type restrictions on fulltext fields. In certain situations,
167
+ # it may be desirable to place logical restrictions on text fields.
168
+ # Remember that text fields are tokenized; your mileage may very.
169
+ #
170
+ # The block works exactly like a normal scope, except that the field names
171
+ # refer to text fields instead of attribute fields.
172
+ #
173
+ # === Example
174
+ #
175
+ # Sunspot.search(Post) do
176
+ # text_fields do
177
+ # with :body, nil
178
+ # end
179
+ # end
180
+ #
181
+ # This will return all documents that do not have a body.
182
+ #
183
+ def text_fields(&block)
184
+ Sunspot::Util.instance_eval_or_call(
185
+ Scope.new(@scope, TextFieldSetup.new(@setup)),
186
+ &block
187
+ )
188
+ end
189
+
190
+ private
191
+
192
+ def add_restriction(negated, *args)
193
+ case args.first
194
+ when String, Symbol
195
+ raise ArgumentError if args.length > 2
196
+ field_name = args[0]
197
+ value = args.length > 1 ? args[1] : NONE
198
+ if value == NONE
199
+ DSL::Restriction.new(@setup.field(field_name.to_sym), @scope, negated)
200
+ else
201
+ @scope.add_shorthand_restriction(negated, @setup.field(field_name.to_sym), value)
202
+ end
203
+ else
204
+ instances = args.flatten
205
+ @scope.add_restriction(
206
+ negated,
207
+ IdField.instance,
208
+ Sunspot::Query::Restriction::AnyOf,
209
+ instances.flatten.map { |instance|
210
+ Sunspot::Adapters::InstanceAdapter.adapt(instance).index_id }
211
+ )
212
+ end
213
+ end
214
+
215
+ end
216
+ end
217
+ end