Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 566 lines (436 sloc) 14.709 kB
d77e590 @alindeman First shot at a new README
alindeman authored
1 # Sunspot
2
3 [![Build Status](http://travis-ci.org/sunspot/sunspot.png)](http://travis-ci.org/sunspot/sunspot)
4
5 Sunspot is a Ruby library for expressive, powerful interaction with the Solr
6 search engine. Sunspot is built on top of the RSolr library, which
7 provides a low-level interface for Solr interaction; Sunspot provides a simple,
8 intuitive, expressive DSL backed by powerful features for indexing objects and
9 searching for them.
10
11 Sunspot is designed to be easily plugged in to any ORM, or even non-database-backed
12 objects such as the filesystem.
13
14 ## Quickstart with Rails 3
15
16 Add to Gemfile:
17
18 ```ruby
19 gem 'sunspot_rails'
20 gem 'sunspot_solr' # optional pre-packaged Solr distribution for use in development
21 ```
22
23 Bundle it!
24
25 ```bash
26 bundle install
27 ```
28
29 Generate a default configuration file:
30
31 ```bash
32 rails generate sunspot_rails:install
33 ```
34
35 If `sunspot_solr` was installed, start the packaged Solr distribution
36 with:
37
38 ```bash
39 bundle exec rake sunspot:solr:start # or sunspot:solr:run to start in foreground
40 ```
41
42 ## Setting Up Objects
43
44 Add a `searchable` block to the objects you wish to index.
45
46 ```ruby
47 class Post < ActiveRecord::Base
48 searchable do
49 text :title, :body
50 text :comments do
51 comments.map { |comment| comment.body }
52 end
53
54 boolean :featured
55 integer :blog_id
56 integer :author_id
57 integer :category_ids, :multiple => true
58 double :average_rating
59 time :published_at
60 time :expired_at
61
62 string :sort_title do
63 title.downcase.gsub(/^(an?|the)/, '')
64 end
65 end
66 end
67 ```
68
69 `text` fields will be full-text searchable. Other fields (e.g.,
70 `integer` and `string`) can be used to scope queries.
71
72 ## Searching Objects
73
74 ```ruby
75 Post.search do
76 fulltext 'best pizza'
77
78 with :blog_id, 1
79 with(:published_at).less_than Time.now
80 order_by :published_at, :desc
81 paginate :page => 2, :per_page => 15
82 facet :category_ids, :author_id
83 end
84 ```
85
86 ## Search In Depth
87
88 Given an object `Post` setup in earlier steps ...
89
90 ### Full Text
91
92 ```ruby
93 # All posts with a `text` field (:title, :body, or :comments) containing 'pizza'
94 Post.search { fulltext 'pizza' }
95
96 # Posts with pizza, scored higher if pizza appears in the title
97 Post.search do
98 fulltext 'pizza' do
99 boost_fields :title => 2.0
100 end
101 end
102
103 # Posts with pizza, scored higher if featured
104 Post.search do
105 fulltext 'pizza' do
106 boost(2.0) { with(:featured, true) }
107 end
108 end
109
110 # Posts with pizza *only* in the title
111 Post.search do
112 fulltext 'pizza' do
113 fields(:title)
114 end
115 end
116
117 # Posts with pizza in the title (boosted) or in the body (not boosted)
118 Post.search do
119 fulltext 'pizza' do
120 fields(:body, :title => 2.0)
121 end
122 end
123 ```
124
126e507 @alindeman Adds more TODO items for documentation
alindeman authored
125 #### Phrases
126
127 TODO: Slop, phrase boost, etc...
128
d77e590 @alindeman First shot at a new README
alindeman authored
129 ### Scoping (Scalar Fields)
130
131 Fields not defined as `text` (e.g., `integer`, `boolean`, `time`,
6e08971 @alindeman Adds section to README for facets
alindeman authored
132 etc...) can be used to scope (restrict) queries before full-text
133 matching is performed.
d77e590 @alindeman First shot at a new README
alindeman authored
134
135 #### Positive Restrictions
136
137 ```ruby
138 # Posts with a blog_id of 1
139 Post.search do
140 with(:blog_id, 1)
141 end
142
143 # Posts with an average rating between 3.0 and 5.0
144 Post.search do
145 with(:average_rating, 3.0..5.0)
146 end
147
148 # Posts with a category of 1, 3, or 5
149 Post.search do
150 with(:category_ids, [1, 3, 5])
151 end
152
153 # Posts published since a week ago
154 Post.search do
155 with(:published_at).greater_than(1.week.ago)
156 end
157 ```
158
159 #### Negative Restrictions
160
161 ```ruby
162 # Posts not in category 1 or 3
163 Post.search do
164 without(:category_ids, [1, 3])
165 end
166
167 # All examples in "positive" also work negated using `without`
168 ```
169
170 #### Disjunctions and Conjunctions
171
172 ```ruby
173 # Posts that do not have an expired time or have not yet expired
174 Post.search do
175 any_of do
176 with(:expired_at).greater_than(Time.now)
177 with(:expired_at, nil)
178 end
179 end
180 ```
181
182 ```ruby
183 # Posts with blog_id 1 and author_id 2
184 Post.search do
185 all_of do
186 with(:blog_id, 1)
187 with(:author_id, 2)
188 end
189 end
190 ```
191
192 Disjunctions and conjunctions may be nested
193
194 ```ruby
195 Post.search do
196 any_of do
197 with(:blog_id, 1)
198 all_of do
199 with(:blog_id, 2)
200 with(:category_ids, 3)
201 end
202 end
203 end
204 ```
205
6e08971 @alindeman Adds section to README for facets
alindeman authored
206 #### Combined with Full-Text
207
208 Scopes/restrictions can be combined with full-text searching. The
209 scope/restriction pares down the objects that are searched for the
210 full-text term.
211
212 ```ruby
213 # Posts with blog_id 1 and 'pizza' in the title
214 Post.search do
215 with(:blog_id, 1)
216 fulltext("pizza")
217 end
218 ```
219
220 ### Pagination
221
94f2bbe @alindeman Adds documentation for pagination
alindeman authored
222 **All results from Solr are paginated**
223
224 The results array that is returned has methods mixed in that allow it to
225 operate seamlessly with common pagination libraries like will\_paginate
226 and kaminari.
227
228 By default, Sunspot requests the first 30 results from Solr.
229
230 ```ruby
231 search = Post.search do
232 fulltext "pizza"
233 end
234
235 # Imagine there are 60 *total* results (at 30 results/page, that is two pages)
236 results = search.results # => Array with 30 Post elements
237
238 search.total # => 60
239
240 results.total_pages # => 2
241 results.first_page? # => true
242 results.last_page? # => false
243 results.previous_page # => nil
244 results.next_page # => 2
245 results.out_of_bounds? # => false
246 results.offset # => 0
247 ```
248
249 To retrieve the next page of results, recreate the search and use the
250 `paginate` method.
251
252 ```ruby
253 search = Post.search do
254 fulltext "pizza"
255 paginate :page => 2
256 end
257
258 # Again, imagine there are 60 total results; this is the second page
259 results = search.results # => Array with 30 Post elements
260
261 search.total # => 60
262
263 results.total_pages # => 2
264 results.first_page? # => false
265 results.last_page? # => true
266 results.previous_page # => 1
267 results.next_page # => nil
268 results.out_of_bounds? # => false
269 results.offset # => 30
270 ```
271
272 A custom number of results per page can be specified with the
273 `:per_page` option to `paginate`:
274
275 ```ruby
276 search = Post.search do
277 fulltext "pizza"
278 paginate :page => 1, :per_page => 50
279 end
280 ```
6e08971 @alindeman Adds section to README for facets
alindeman authored
281
282 ### Faceting
283
284 Faceting is a feature of Solr that determines the number of documents
285 that match a given search *and* an additional criterion. This allows you
286 to build powerful drill-down interfaces for search.
287
288 Each facet returns zero or more rows, each of which represents a
289 particular criterion conjoined with the actual query being performed.
290 For **field facets**, each row represents a particular value for a given
291 field. For **query facets**, each row represents an arbitrary scope; the
292 facet itself is just a means of logically grouping the scopes.
293
294 #### Field Facets
295
296 ```ruby
297 # Posts that match 'pizza' returning counts for each :author_id
298 search = Post.search do
299 fulltext "pizza"
300 facet :author_id
301 end
302
303 search.facet(:author_id).rows.each do |facet|
304 puts "Author #{facet.value} has #{facet.count} pizza posts!"
305 end
306 ```
307
308 #### Query Facets
309
310 ```ruby
311 # Posts faceted by ranges of average ratings
312 Post.search do
313 facet(:average_rating) do
314 row(1.0..2.0) do
315 with(:average_rating, 1.0..2.0)
316 end
317 row(2.0..3.0) do
318 with(:average_rating, 2.0..3.0)
319 end
320 row(3.0..4.0) do
321 with(:average_rating, 3.0..4.0)
322 end
323 row(4.0..5.0) do
324 with(:average_rating, 4.0..5.0)
325 end
326 end
327 end
328
329 # e.g.,
330 # Number of posts with rating withing 1.0..2.0: 2
331 # Number of posts with rating withing 2.0..3.0: 1
332 search.facet(:average_rating).rows.each do |facet|
333 puts "Number of posts with rating withing #{facet.value}: #{facet.count}"
334 end
335 ```
336
b5f5894 @alindeman Adds section to README for ordering
alindeman authored
337 ### Ordering
338
339 By default, Sunspot orders results by "score": the Solr-determined
8aaf367 @alindeman Fixes wording
alindeman authored
340 relevancy metric. Sorting can be customized with the `order_by` method:
b5f5894 @alindeman Adds section to README for ordering
alindeman authored
341
342 ```ruby
343 # Order by average rating, descending
344 Post.search do
345 fulltext("pizza")
346 order_by(:average_rating, :desc)
347 end
348
349 # Order by relevancy score and in the case of a tie, average rating
350 Post.search do
351 fulltext("pizza")
352
353 order_by(:score, :desc)
354 order_by(:average_rating, :desc)
355 end
356
357 # Randomized ordering
358 Post.search do
359 fulltext("pizza")
360 order_by(:random)
361 end
362 ```
363
d77e590 @alindeman First shot at a new README
alindeman authored
364 ### Geospatial
365
366 TODO
367
368 ### Highlighting
369
7113d46 @alindeman Adds documentation for highlighting
alindeman authored
370 Highlighting allows you to display snippets of the part of the document
371 that matched the query.
372
373 The fields you wish to highlight must be **stored**.
374
375 ```ruby
376 class Post < ActiveRecord::Base
377 searchable do
378 # ...
379 text :body, :stored => true
380 end
381 end
382 ```
383
384 Highlighting matches on the `body` field, for instance, can be acheived
385 like:
386
387 ```ruby
388 search = Post.search do
389 fulltext "pizza" do
390 highlight :body
391 end
392 end
393
394 # Will output something similar to:
395 # Post #1
396 # I really love *pizza*
397 # *Pizza* is my favorite thing
398 # Post #2
399 # Pepperoni *pizza* is delicious
400 search.hits.each do |hit|
401 puts "Post ##{hit.primary_key}"
402
403 hit.highlights(:body).each do |highlight|
404 puts " " + highlight.format { |word| "*#{word}*" }
405 end
406 end
407 ```
d77e590 @alindeman First shot at a new README
alindeman authored
408
e7c2a51 @alindeman Stubs out more README sections
alindeman authored
409 ### Functions
410
411 TODO
412
413 ### More Like This
414
415 TODO
416
d77e590 @alindeman First shot at a new README
alindeman authored
417 ## Indexing In Depth
418
419 TODO
420
e7c2a51 @alindeman Stubs out more README sections
alindeman authored
421 ### Index-Time Boosts
422
a32d14d @alindeman Adds docs for index-time boosts
alindeman authored
423 To specify that a field should be boosted in relation to other fields for
424 all queries, you can specify the boost at index time:
425
426 ```ruby
427 class Post < ActiveRecord::Base
428 searchable do
429 text :title, :boost => 5.0
430 text :body
431 end
432 end
433 ```
e7c2a51 @alindeman Stubs out more README sections
alindeman authored
434
435 ### Stored Fields
436
0f54159 @alindeman Adds documentation for stored fields and hits/results
alindeman authored
437 Stored fields keep an original (untokenized/unanalyzed) version of their
438 contents in Solr.
439
440 Stored fields allow data to be retrieved without also hitting the
441 underlying database (usually an SQL server). They are also required for
442 highlighting and more like this queries.
443
444 Stored fields come at some performance cost in the Solr index, so use
445 them wisely.
446
447 ```ruby
448 class Post < ActiveRecord::Base
449 searchable do
450 text :body, :stored => true
451 end
452 end
453
454 # Retrieving stored contents without hitting the database
455 Post.search.hits.each do |hit|
456 puts hit.stored(:body)
457 end
458 ```
e7c2a51 @alindeman Stubs out more README sections
alindeman authored
459
460 ## Hits vs. Results
461
0f54159 @alindeman Adds documentation for stored fields and hits/results
alindeman authored
462 Sunspot simply stores the type and primary key of objects in Solr.
463 When results are retrieved, those primary keys are used to load the
464 actual object (usually from an SQL database).
465
466 ```ruby
467 # Using #results pulls in the records from the object-relational
468 # mapper (e.g., ActiveRecord + a SQL server)
469 Post.search.results.each do |result|
470 puts result.body
471 end
472 ```
473
474 To access information about the results without querying the underlying
475 database, use `hits`:
476
477 ```ruby
478 # Using #hits gives back all information requested from Solr, but does
479 # not load the object from the object-relational mapper
480 Post.search.hits.each do |hit|
481 puts hit.stored(:body)
482 end
483 ```
484
485 If you need both the result (ORM-loaded object) and `Hit` (e.g., for
486 faceting, highlighting, etc...), you can use the convenience method
487 `each_hit_with_result`:
488
489 ```ruby
490 Post.search.each_hit_with_result do |hit, result|
491 # ...
492 end
493 ```
e7c2a51 @alindeman Stubs out more README sections
alindeman authored
494
d77e590 @alindeman First shot at a new README
alindeman authored
495 ## Reindexing Objects
496
497 If you are using Rails, objects are automatically indexed to Solr as a
498 part of the `save` callbacks.
499
500 If you make a change to the object's "schema" (code in the `searchable` block),
501 you must reindex all objects so the changes are reflected in Solr:
502
503 ```bash
504 bundle exec rake sunspot:solr:reindex
505
506 # or, to be specific to a certain model with a certain batch size:
507 bundle exec rake sunspot:solr:reindex[500,Post] # some shells will require escaping [ with \[ and ] with \]
508 ```
509
510 ## Use Without Rails
511
512 TODO
513
e7c2a51 @alindeman Stubs out more README sections
alindeman authored
514 ## Manually Adjusting Solr Parameters
515
516 To add or modify parameters sent to Solr, use `adjust_solr_params`:
517
518 ```ruby
519 Post.search do
520 adjust_solr_params do |params|
521 params[:q] += " AND something_s:more"
522 end
523 end
524 ```
525
526 ## Session Proxies
527
528 TODO
529
530 ## Type Reference
531
532 TODO
533
d77e590 @alindeman First shot at a new README
alindeman authored
534 ## Tutorials and Articles
535
536 * [Full Text Searching with Solr and Sunspot](http://collectiveidea.com/blog/archives/2011/03/08/full-text-searching-with-solr-and-sunspot/) (Collective Idea)
537 * [Full-text search in Rails with Sunspot](http://tech.favoritemedium.com/2010/01/full-text-search-in-rails-with-sunspot.html) (Tropical Software Observations)
538 * [Sunspot Full-text Search for Rails/Ruby](http://therailworld.com/posts/23-Sunspot-Full-text-Search-for-Rails-Ruby) (The Rail World)
539 * [A Few Sunspot Tips](http://blog.trydionel.com/2009/11/19/a-few-sunspot-tips/) (spiral_code)
540 * [Sunspot: A Solr-Powered Search Engine for Ruby](http://www.linux-mag.com/id/7341) (Linux Magazine)
541 * [Sunspot Showed Me the Light](http://bennyfreshness.com/2010/05/sunspot-helped-me-see-the-light/) (ben koonse)
542 * [RubyGems.org — A case study in upgrading to full-text search](http://blog.websolr.com/post/3505903537/rubygems-search-upgrade-1) (Websolr)
543 * [How to Implement Spatial Search with Sunspot and Solr](http://codequest.eu/articles/how-to-implement-spatial-search-with-sunspot-and-solr) (Code Quest)
544 * [Sunspot 1.2 with Spatial Solr Plugin 2.0](http://joelmats.wordpress.com/2011/02/23/getting-sunspot-1-2-with-spatial-solr-plugin-2-0-to-work/) (joelmats)
545 * [rails3 + heroku + sunspot : madness](http://anhaminha.tumblr.com/post/632682537/rails3-heroku-sunspot-madness) (anhaminha)
546 * [How to get full text search working with Sunspot](http://cookbook.hobocentral.net/recipes/57-how-to-get-full-text-search) (Hobo Cookbook)
547 * [Full text search with Sunspot in Rails](http://hemju.com/2011/01/04/full-text-search-with-sunspot-in-rail/) (hemju)
548 * [Using Sunspot for Free-Text Search with Redis](http://masonoise.wordpress.com/2010/02/06/using-sunspot-for-free-text-search-with-redis/) (While I Pondered...)
549 * [Fuzzy searching in SOLR with Sunspot](http://www.pipetodevnull.com/past/2010/8/5/fuzzy_searching_in_solr_with_sunspot/) (pipe :to => /dev/null)
550 * [Default scope with Sunspot](http://www.cloudspace.com/blog/2010/01/15/default-scope-with-sunspot/) (Cloudspace)
551 * [Index External Models with Sunspot/Solr](http://www.medihack.org/2011/03/19/index-external-models-with-sunspotsolr/) (Medihack)
552 * [Chef recipe for Sunspot in production](http://gist.github.com/336403)
553 * [Testing with Sunspot and Cucumber](http://collectiveidea.com/blog/archives/2011/05/25/testing-with-sunspot-and-cucumber/) (Collective Idea)
554 * [Cucumber and Sunspot](http://opensoul.org/2010/4/7/cucumber-and-sunspot) (opensoul.org)
555 * [Testing Sunspot with Cucumber](http://blog.trydionel.com/2010/02/06/testing-sunspot-with-cucumber/) (spiral_code)
556 * [Running cucumber features with sunspot_rails](http://blog.kabisa.nl/2010/02/03/running-cucumber-features-with-sunspot_rails) (Kabisa Blog)
557 * [Testing Sunspot with Test::Unit](http://timcowlishaw.co.uk/post/3179661158/testing-sunspot-with-test-unit) (Type Slowly)
558 * [How To Use Twitter Lists to Determine Influence](http://www.untitledstartup.com/2010/01/how-to-use-twitter-lists-to-determine-influence/) (Untitled Startup)
559 * [Sunspot Quickstart](http://wiki.websolr.com/index.php/Sunspot_Quickstart) (WebSolr)
560 * [Solr, and Sunspot](http://www.kuahyeow.com/2009/08/solr-and-sunspot.html) (YT!)
561 * [The Saga of the Switch](http://mrb.github.com/2010/04/08/the-saga-of-the-switch.html) (mrb -- includes comparison of Sunspot and Ultrasphinx)
562
563 ## License
564
565 Sunspot is distributed under the MIT License, copyright (c) 2008-2009 Mat Brown
Something went wrong with that request. Please try again.