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
data/History.txt ADDED
@@ -0,0 +1,39 @@
1
+ == 0.8.0 2009-05-22
2
+ * Access query API directly; instantiate search without running it
3
+ * Dynamic fields
4
+ * Search blocks can be evaluated in calling context
5
+
6
+ == 0.7.3 2009-05-06
7
+ * Better exception handling when class doesn't have adapter/setup
8
+
9
+ == 0.7.2 2009-04-29
10
+ * Dirty sessions
11
+
12
+ == 0.7.1 2009-04-29
13
+ * Removed extlib dependency from gemspec
14
+
15
+ == 0.7.0 2009-04-28
16
+ * Less magic in the DSL
17
+ * Restrict by empty values
18
+ * Negative scoping using without() method
19
+ * Exclusion by object identity using without(instance)
20
+ * Support for faceting
21
+ * Explicit commits
22
+ * Boolean field type
23
+ * Attribute field flexibility
24
+ * Virtual field blocks can be evaluated in calling context
25
+ * Order available by multiple fields
26
+ * New adapter API
27
+ * Got rid of builder API
28
+ * Full documentation
29
+
30
+ == 0.0.2 2009-02-14
31
+ * Run sunspot's built-in Solr instance using
32
+ sunspot-solr executable
33
+ * Search hash interpretation delegated to
34
+ Builder object
35
+
36
+ == 0.0.1 2008-12-11
37
+ * Initial release
38
+ * Define indexing for any class using DSL
39
+ * Search indexed classes using DSL
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Permission is hereby granted, free of charge, to any person obtaining
2
+ a copy of this software and associated documentation files (the
3
+ 'Software'), to deal in the Software without restriction, including
4
+ without limitation the rights to use, copy, modify, merge, publish,
5
+ distribute, sublicense, and/or sell copies of the Software, and to
6
+ permit persons to whom the Software is furnished to do so, subject to
7
+ the following conditions:
8
+
9
+ The above copyright notice and this permission notice shall be
10
+ included in all copies or substantial portions of the Software.
11
+
12
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
13
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
15
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
16
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
17
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
18
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,154 @@
1
+ = Sunspot
2
+
3
+ http://outoftime.github.com/sunspot
4
+
5
+ Sunspot is a Ruby library for expressive, powerful interaction with the Solr search engine.
6
+ Sunspot is built on top of the RSolr gem, which provides a low-level interface for Solr
7
+ interaction; Sunspot provides a simple, intuitive, expressive DSL backed by powerful
8
+ features for indexing objects and searching for them.
9
+
10
+ Sunspot is designed to be easily plugged in to any ORM, or even non-database-backed
11
+ objects such as the filesystem.
12
+
13
+ === Features:
14
+
15
+ * Define indexing strategy for each searchable class using intuitive block-based API
16
+ * Clean separation between keyword-searchable fields and fields for scoping/ordering
17
+ * Define fields based on existing attributes or "virtual fields" for custom indexing
18
+ * Indexes each object's entire superclass hierarchy, for easy searching for all objects inheriting from a parent class
19
+ * Intuitive DSL for scoping searches, with all the usual boolean operators available
20
+ * Intuitive interface for requesting facets on indexed fields
21
+ * Extensible adapter architecture for easy integration of other ORMs or non-model classes
22
+ * Refine search using field facets, date range facets, or ultra-powerful query facets
23
+ * Full compatibility with will_paginate
24
+ * Ordering
25
+
26
+ == Installation
27
+
28
+ gem sources -a http://gems.github.com
29
+ gem install outoftime-sunspot
30
+
31
+ In order to start the packaged Solr installation, run:
32
+
33
+ sunspot-solr start -- [-d /path/to/data/directory] [-p port] [-s path/to/solr/home] [--pid-dir=path/to/pid/dir]
34
+
35
+ If you don't specify a data directory, your Solr index will be stored in your operating system's temporary directory.
36
+
37
+ If you specify a solr home, the directory must contain a <code>conf</code>
38
+ directory, which should contain at least <code>schema.xml</code> and
39
+ <code>solrconfig.xml</code>. Be sure to copy the <code>schema.xml</code> out of
40
+ the Sunspot gem's <code>solr/solr/conf</code> directory. Sunspot relies on the
41
+ field name patterns defined in the packaged <code>schema.xml</code>, so those
42
+ cannot be modified.
43
+
44
+ You can also run your own instance of Solr wherever you'd like; just copy the solr/config/schema.xml file out of the gem's solr into your installation.
45
+ You can change the URL at which Sunspot accesses Solr with:
46
+
47
+ Sunspot.config.solr.url = 'http://solr.my.host:9818/solr'
48
+
49
+ == Rails Integration
50
+
51
+ The {Sunspot::Rails}[http://github.com/outoftime/sunspot_rails] plugin makes
52
+ integrating Sunspot into Rails drop-in easy.
53
+
54
+ == Using Sunspot
55
+
56
+ === Define an index:
57
+
58
+ class Post
59
+ #...
60
+ end
61
+
62
+ Sunspot.setup(Post) do
63
+ text :title, :body
64
+ string :author_name
65
+ integer :blog_id
66
+ integer :category_ids
67
+ float :average_rating, :using => :ratings_average
68
+ time :published_at
69
+ string :sort_title do
70
+ title.downcase.sub(/^(an?|the)\W+/, ''/) if title = self.title
71
+ end
72
+ end
73
+
74
+ See Sunspot.setup for more information.
75
+
76
+ Note that in order for a class to be searchable, it must have an adapter
77
+ registered for itself or one of its subclasses. Adapters allow Sunspot to load
78
+ objects out of persistent storage, and to determine their primary key for
79
+ indexing. {Sunspot::Rails}[http://github.com/outoftime/sunspot_rails] comes with
80
+ an adapter for ActiveRecord objects, but for other types of models you will need
81
+ to define your own. See Sunspot::Adapters for more information.
82
+
83
+ === Search for objects:
84
+
85
+ search = Sunspot.search Post do
86
+ keywords 'great pizza'
87
+ with :author_name, 'Mark Twain'
88
+ with(:blog_id).any_of [2, 14]
89
+ with(:category_ids).all_of [4, 10]
90
+ with(:published_at).less_than Time.now
91
+ any_of do
92
+ with(:expired_at).greater_than(Time.now)
93
+ with(:expired_at, nil)
94
+ end
95
+ without :title, 'Bad Title'
96
+ without bad_instance # specifically exclude this instance from results
97
+
98
+ paginate :page => 3, :per_page => 15
99
+ order_by :average_rating, :desc
100
+
101
+ facet :blog_id
102
+ end
103
+
104
+ See Sunspot.search for more information.
105
+
106
+ === Get data from search:
107
+
108
+ search.results
109
+ search.total
110
+ search.page
111
+ search.per_page
112
+ search.facet(:blog_id)
113
+
114
+ == About the API documentation
115
+
116
+ All of the methods documented in the RDoc are considered part of Sunspot's
117
+ public API. Methods that are not part of the public API are documented in the
118
+ code, but excluded from the RDoc. If you find yourself needing to access methods
119
+ that are not part of the public API in order to do what you need, please contact
120
+ me so I can rectify the situation!
121
+
122
+ == Dependencies
123
+
124
+ 1. RSolr
125
+ 2. Daemons
126
+ 3. OptiFlag
127
+ 4. Haml
128
+ 5. Java
129
+
130
+ Sunspot has been tested with MRI 1.8.6 and 1.8.7, REE 1.8.6, YARV 1.9.1, and
131
+ JRuby 1.2.0
132
+
133
+ == Bugs
134
+
135
+ Please submit bug reports to
136
+ http://outoftime.lighthouseapp.com/projects/20339-sunspot
137
+
138
+ == Further Reading
139
+
140
+ * Sunspot Discussion: http://groups.google.com/group/ruby-sunspot
141
+ * IRC: #sunspot-ruby @ Freenode
142
+ * Posts about Sunspot from my tumblog: http://outofti.me/tagged/sunspot
143
+ * Read about it on Linux Magazine: http://www.linux-mag.com/id/7341
144
+
145
+ == Contributors
146
+
147
+ * Mat Brown (mat@patch.com)
148
+ * Peer Allan (peer.allan@gmail.com)
149
+ * Dmitriy Dzema (dima@dzema.name)
150
+ * Benjamin Krause (bk@benjaminkrause.com)
151
+
152
+ == License
153
+
154
+ Sunspot is distributed under the MIT License, copyright (c) 2008-2009 Mat Brown
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ ENV['RUBYOPT'] = '-W1'
2
+
3
+ task :environment do
4
+ require File.dirname(__FILE__) + '/lib/sunspot'
5
+ end
6
+
7
+ Dir['tasks/**/*.rake'].each { |t| load t }
8
+
9
+ task :default => 'spec:api'
data/TODO ADDED
@@ -0,0 +1,4 @@
1
+ === 0.9 ===
2
+ * Query-based faceting (?)
3
+ === 0.10 ===
4
+ * Intelligently decide whether to instantiate all facet rows at once
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 1
3
+ :major: 0
4
+ :minor: 9
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ using_gems = false
4
+ begin
5
+ require 'fileutils'
6
+ require 'optiflag'
7
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'sunspot', 'schema')
8
+ rescue LoadError => e
9
+ if using_gems
10
+ raise(e)
11
+ else
12
+ using_gems = true
13
+ require 'rubygems'
14
+ retry
15
+ end
16
+ end
17
+
18
+ module ConfigureSolrFlags extend OptiFlagSet
19
+ optional_flag 'tokenizer'
20
+ optional_flag 'extra_filters'
21
+ optional_flag 'dir'
22
+ and_process!
23
+ end
24
+
25
+ solr_directory = ARGV.flags.dir || FileUtils.pwd
26
+ conf_directory = File.join(solr_directory, 'conf')
27
+ schema_file = File.join(conf_directory, 'schema.xml')
28
+ FileUtils.mkdir_p(conf_directory)
29
+
30
+ schema = Sunspot::Schema.new
31
+ schema.tokenizer = ARGV.flags.tokenizer if ARGV.flags.tokenizer
32
+ if ARGV.flags.extra_filters
33
+ for filter in ARGV.flags.extra_filters.split(',')
34
+ schema.add_filter(filter)
35
+ end
36
+ end
37
+
38
+ if File.exist?(schema_file)
39
+ backup_file = File.join(conf_directory, "schema-#{File.mtime(schema_file).strftime('%Y%m%d%H%M%S')}.xml")
40
+ STDERR.puts("Backing up current schema file to #{File.expand_path(backup_file)}")
41
+ FileUtils.mv(schema_file, backup_file)
42
+ end
43
+
44
+ File.open(File.join(conf_directory, 'schema.xml'), 'w') do |file|
45
+ file << schema.to_xml
46
+ end
data/bin/sunspot-solr ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env ruby
2
+ using_gems = false
3
+ begin
4
+ require 'fileutils'
5
+ require 'tmpdir'
6
+ require 'daemons'
7
+ require 'optiflag'
8
+ rescue LoadError => e
9
+ if using_gems
10
+ raise(e)
11
+ else
12
+ using_gems = true
13
+ require 'rubygems'
14
+ retry
15
+ end
16
+ end
17
+
18
+ working_directory = FileUtils.pwd
19
+ solr_home = File.join(File.dirname(__FILE__), '..', 'solr')
20
+
21
+ module SolrFlags extend OptiFlagSet
22
+ optional_flag 'p' do
23
+ description 'Port on which to run Solr (default 8983)'
24
+ long_form 'port'
25
+ end
26
+
27
+ optional_flag 'd' do
28
+ description 'Solr data directory'
29
+ end
30
+
31
+ optional_flag 's' do
32
+ description 'Solr home (should contain conf/ directory)'
33
+ end
34
+
35
+ optional_flag 'pd' do
36
+ long_form 'pid-dir'
37
+ description 'Directory for pid files'
38
+ end
39
+
40
+ and_process!
41
+ end
42
+
43
+ port = ARGV.flags.p || '8983'
44
+ data_dir = File.expand_path(ARGV.flags.d || File.join(Dir.tmpdir, 'solr_data'))
45
+ home = File.expand_path(ARGV.flags.s) if ARGV.flags.s
46
+ pid_dir = File.expand_path(ARGV.flags.pd || working_directory)
47
+
48
+ options = { :dir_mode => :normal, :dir => pid_dir }
49
+
50
+ Daemons.run_proc('sunspot-solr', options) do
51
+ FileUtils.cd(working_directory) do
52
+ FileUtils.cd(solr_home) do
53
+ args = ['java']
54
+ args << "-Djetty.port=#{port}" if port
55
+ args << "-Dsolr.data.dir=#{data_dir}" if data_dir
56
+ args << "-Dsolr.solr.home=#{home}" if home
57
+ args << '-jar' << 'start.jar'
58
+ STDERR.puts(args * ' ')
59
+ Kernel.exec(*args)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,40 @@
1
+ module LightConfig
2
+ class Configuration
3
+ def initialize(&block)
4
+ @properties = {}
5
+ ::LightConfig::Builder.new(self).instance_eval(&block)
6
+ singleton = (class <<self; self; end)
7
+ @properties.keys.each do |property|
8
+ singleton.module_eval do
9
+ define_method property do
10
+ @properties[property]
11
+ end
12
+
13
+ define_method "#{property}=" do |value|
14
+ @properties[property] = value
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ class Builder
22
+ def initialize(configuration)
23
+ @configuration = configuration
24
+ end
25
+
26
+ def method_missing(method, *args, &block)
27
+ raise ArgumentError("wrong number of arguments(#{args.length} for 1)") unless args.length < 2
28
+ value = if block then ::LightConfig::Configuration.new(&block)
29
+ else args.first
30
+ end
31
+ @configuration.instance_variable_get(:@properties)[method] = value
32
+ end
33
+ end
34
+
35
+ class <<self
36
+ def build(&block)
37
+ LightConfig::Configuration.new(&block)
38
+ end
39
+ end
40
+ end
data/lib/sunspot.rb ADDED
@@ -0,0 +1,470 @@
1
+ begin
2
+ require 'time'
3
+ require 'date'
4
+ require 'rsolr'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ require 'rsolr'
8
+ end
9
+
10
+ require File.join(File.dirname(__FILE__), 'light_config')
11
+
12
+ %w(util adapters configuration setup composite_setup field field_factory
13
+ data_extractor indexer query search facet facet_row instantiated_facet
14
+ instantiated_facet_row date_facet date_facet_row query_facet query_facet_row
15
+ session type dsl).each do |filename|
16
+ require File.join(File.dirname(__FILE__), 'sunspot', filename)
17
+ end
18
+
19
+ #
20
+ # The Sunspot module provides class-method entry points to most of the
21
+ # functionality provided by the Sunspot library. Internally, the Sunspot
22
+ # singleton class contains a (non-thread-safe!) instance of Sunspot::Session,
23
+ # to which it delegates most of the class methods it exposes. In the method
24
+ # documentation below, this instance is referred to as the "singleton session".
25
+ #
26
+ # Though the singleton session provides a convenient entry point to Sunspot,
27
+ # it is by no means required to use the Sunspot class methods. Multiple sessions
28
+ # may be instantiated and used (if you need to connect to multiple Solr
29
+ # instances, for example.)
30
+ #
31
+ # Note that the configuration of classes for index/search (the +setup+
32
+ # method) is _not_ session-specific, but rather global.
33
+ #
34
+ module Sunspot
35
+ UnrecognizedFieldError = Class.new(Exception)
36
+ UnrecognizedRestrictionError = Class.new(Exception)
37
+ NoAdapterError = Class.new(Exception)
38
+ NoSetupError = Class.new(Exception)
39
+
40
+ class <<self
41
+ # Configures indexing and search for a given class.
42
+ #
43
+ # ==== Parameters
44
+ #
45
+ # clazz<Class>:: class to configure
46
+ #
47
+ # ==== Example
48
+ #
49
+ # Sunspot.setup(Post) do
50
+ # text :title, :body
51
+ # string :author_name
52
+ # integer :blog_id
53
+ # integer :category_ids
54
+ # float :average_rating, :using => :ratings_average
55
+ # time :published_at
56
+ # string :sort_title do
57
+ # title.downcase.sub(/^(an?|the)\W+/, ''/) if title = self.title
58
+ # end
59
+ # end
60
+ #
61
+ # ====== Attribute Fields vs. Virtual Fields
62
+ #
63
+ # Attribute fields call a method on the indexed object and index the
64
+ # return value. All of the fields defined above except for the last one are
65
+ # attribute fields. By default, the field name will also be the attribute
66
+ # used; this can be overriden with the +:using+ option, as in
67
+ # +:average_rating+ above. In that case, the attribute +:ratings_average+
68
+ # will be indexed with the field name +:average_rating+.
69
+ #
70
+ # +:sort_title+ is a virtual field, which evaluates the block inside the
71
+ # context of the instance being indexed, and indexes the value returned
72
+ # by the block. If the block you pass takes an argument, it will be passed
73
+ # the instance rather than being evaluated inside of it; so, the following
74
+ # example is equivalent to the one above (assuming #title is public):
75
+ #
76
+ # Sunspot.setup(Post) do
77
+ # string :sort_title do |post|
78
+ # post.title.downcase.sub(/^(an?|the)\W+/, ''/) if title = self.title
79
+ # end
80
+ # end
81
+ #
82
+ # ===== Field Types
83
+ #
84
+ # The available types are:
85
+ #
86
+ # * +text+
87
+ # * +string+
88
+ # * +integer+
89
+ # * +float+
90
+ # * +time+
91
+ # * +boolean+
92
+ #
93
+ # Note that the +text+ type behaves quite differently from the others -
94
+ # this is the type that is indexed as fulltext, and is searched using the
95
+ # +keywords+ method inside the search DSL. Text fields cannot have
96
+ # restrictions set on them, nor can they be used in order statements or
97
+ # for facets. All other types are indexed literally, and thus can be used
98
+ # for all of those operations. They will not, however, be searched in
99
+ # fulltext. In this way, Sunspot provides a complete barrier between
100
+ # fulltext fields and value fields.
101
+ #
102
+ # It is fine to specify a field both as a text field and a string field;
103
+ # internally, the fields will have different names so there is no danger
104
+ # of conflict.
105
+ #
106
+ # ===== Dynamic Fields
107
+ #
108
+ # For use cases which have highly dynamic data models (for instance, an
109
+ # open set of key-value pairs attached to a model), it may be useful to
110
+ # defer definition of fields until indexing time. Sunspot exposes dynamic
111
+ # fields, which define a data accessor (either attribute or virtual, see
112
+ # above), which accepts a hash of field names to values. Note that the field
113
+ # names in the hash are internally scoped to the base name of the dynamic
114
+ # field, so any time they are referred to, they are referred to using both
115
+ # the base name and the dynamic (runtime-specified) name.
116
+ #
117
+ # Dynamic fields are speficied in the setup block using the type name
118
+ # prefixed by +dynamic_+. For example:
119
+ #
120
+ # Sunspot.setup(Post) do
121
+ # dynamic_string :custom_values do
122
+ # key_value_pairs.inject({}) do |hash, key_value_pair|
123
+ # hash[key_value_pair.key.to_sym] = key_value_pair.value
124
+ # end
125
+ # end
126
+ # end
127
+ #
128
+ # If you later wanted to facet all of the values for the key "cuisine",
129
+ # you could issue:
130
+ #
131
+ # Sunspot.search(Post) do
132
+ # dynamic :custom_values do
133
+ # facet :cuisine
134
+ # end
135
+ # end
136
+ #
137
+ # In the documentation, +:custom_values+ is referred to as the "base name" -
138
+ # that is, the one specified statically - and +:cuisine+ is referred to as
139
+ # the dynamic name, which is the part that is specified at indexing time.
140
+ #
141
+ def setup(clazz, &block)
142
+ Setup.setup(clazz, &block)
143
+ end
144
+
145
+ # Indexes objects on the singleton session.
146
+ #
147
+ # ==== Parameters
148
+ #
149
+ # objects...<Object>:: objects to index (may pass an array or varargs)
150
+ #
151
+ # ==== Example
152
+ #
153
+ # post1, post2 = Array(2) { Post.create }
154
+ # Sunspot.index(post1, post2)
155
+ #
156
+ # Note that indexed objects won't be reflected in search until a commit is
157
+ # sent - see Sunspot.index! and Sunspot.commit
158
+ #
159
+ def index(*objects)
160
+ session.index(*objects)
161
+ end
162
+
163
+ # Indexes objects on the singleton session and commits immediately.
164
+ #
165
+ # See: Sunspot.index and Sunspot.commit
166
+ #
167
+ # ==== Parameters
168
+ #
169
+ # objects...<Object>:: objects to index (may pass an array or varargs)
170
+ #
171
+ def index!(*objects)
172
+ session.index!(*objects)
173
+ end
174
+
175
+ # Commits the singleton session
176
+ #
177
+ # When documents are added to or removed from Solr, the changes are
178
+ # initially stored in memory, and are not reflected in Solr's existing
179
+ # searcher instance. When a commit message is sent, the changes are written
180
+ # to disk, and a new searcher is spawned. Commits are thus fairly
181
+ # expensive, so if your application needs to index several documents as part
182
+ # of a single operation, it is advisable to index them all and then call
183
+ # commit at the end of the operation.
184
+ #
185
+ # Note that Solr can also be configured to automatically perform a commit
186
+ # after either a specified interval after the last change, or after a
187
+ # specified number of documents are added. See
188
+ # http://wiki.apache.org/solr/SolrConfigXml
189
+ #
190
+ def commit
191
+ session.commit
192
+ end
193
+
194
+ #
195
+ # Create a new Search instance, but do not execute it immediately. Generally
196
+ # you will want to use the #search method to execute searches using the
197
+ # DSL; however, if you are building searches dynamically (using the Builder
198
+ # pattern, for instance), it may be easier to access the Query API directly.
199
+ #
200
+ # ==== Parameters
201
+ #
202
+ # types<Class>...::
203
+ # Zero, one, or more types to search for. If no types are passed, all
204
+ # configured types will be searched for.
205
+ #
206
+ # ==== Returns
207
+ #
208
+ # Sunspot::Search::
209
+ # Search object, not yet executed. Query parameters can be added manually;
210
+ # then #execute! should be called.
211
+ #
212
+ def new_search(*types)
213
+ session.new_search(*types)
214
+ end
215
+
216
+
217
+ # Search for objects in the index.
218
+ #
219
+ # ==== Parameters
220
+ #
221
+ # types<Class>...::
222
+ # Zero, one, or more types to search for. If no types are passed, all
223
+ # configured types will be searched.
224
+ #
225
+ # ==== Options (last argument, optional)
226
+ #
227
+ # :keywords<String>:: Fulltext search string
228
+ # :conditions<Hash>::
229
+ # Hash of key-value pairs to be used as restrictions. Keys are field
230
+ # names. Scalar values are used as equality restrictions; arrays are used
231
+ # as "any of" restrictions; and Ranges are used as range restrictions.
232
+ # :order<String>:: order field and direction (e.g., 'updated_at desc')
233
+ # :page<Integer>:: Page to start on for pagination
234
+ # :per_page<Integer>::
235
+ # Number of results to use per page. Ignored if :page is not specified.
236
+ #
237
+ # ==== Returns
238
+ #
239
+ # Sunspot::Search:: Object containing results, facets, count, etc.
240
+ #
241
+ # The fields available for restriction, ordering, etc. are those that meet
242
+ # the following criteria:
243
+ #
244
+ # * They are not of type +text+.
245
+ # * They are defined for all of the classes being searched
246
+ # * They have the same data type for all of the classes being searched
247
+ # * They have the same multiple flag for all of the classes being searched.
248
+ #
249
+ # The restrictions available are the constants defined in the
250
+ # Sunspot::Restriction class. The standard restrictions are:
251
+ #
252
+ # with(:field_name).equal_to(value)
253
+ # with(:field_name, value) # shorthand for above
254
+ # with(:field_name).less_than(value)
255
+ # with(:field_name).greater_than(value)
256
+ # with(:field_name).between(value1..value2)
257
+ # with(:field_name).any_of([value1, value2, value3])
258
+ # with(:field_name).all_of([value1, value2, value3])
259
+ # without(some_instance) # exclude that particular instance
260
+ #
261
+ # +without+ can be substituted for +with+, causing the restriction to be
262
+ # negated. In the last example above, only +without+ works, as it does not
263
+ # make sense to search only for an instance you already have.
264
+ #
265
+ # Equality restrictions can take +nil+ as a value, which restricts the
266
+ # results to documents that have no value for the given field. Passing +nil+
267
+ # as a value to other restriction types is illegal. Thus:
268
+ #
269
+ # with(:field_name, nil) # ok
270
+ # with(:field_name).equal_to(nil) # ok
271
+ # with(:field_name).less_than(nil) # bad
272
+ #
273
+ # ==== Example
274
+ #
275
+ # Sunspot.search(Post) do
276
+ # keywords 'great pizza'
277
+ # with(:published_at).less_than Time.now
278
+ # with :blog_id, 1
279
+ # without current_post
280
+ # facet :category_ids
281
+ # order_by :published_at, :desc
282
+ # paginate 2, 15
283
+ # end
284
+ #
285
+ # If the block passed to #search takes an argument, that argument will
286
+ # present the DSL, and the block will be evaluated in the calling context.
287
+ # This will come in handy for building searches using instance data or
288
+ # methods, e.g.:
289
+ #
290
+ # Sunspot.search(Post) do |query|
291
+ # query.with(:blog_id, @current_blog.id)
292
+ # end
293
+ #
294
+ # See Sunspot::DSL::Search, Sunspot::DSL::Scope, Sunspot::DSL::FieldQuery
295
+ # and Sunspot::DSL::Query for the full API presented inside the block.
296
+ #
297
+ def search(*types, &block)
298
+ session.search(*types, &block)
299
+ end
300
+
301
+ # Remove objects from the index. Any time an object is destroyed, it must
302
+ # be removed from the index; otherwise, the index will contain broken
303
+ # references to objects that do not exist, which will cause errors when
304
+ # those objects are matched in search results.
305
+ #
306
+ # ==== Parameters
307
+ #
308
+ # objects...<Object>::
309
+ # Objects to remove from the index (may pass an array or varargs)
310
+ #
311
+ # ==== Example
312
+ #
313
+ # post.destroy
314
+ # Sunspot.remove(post)
315
+ #
316
+ def remove(*objects)
317
+ session.remove(*objects)
318
+ end
319
+
320
+ #
321
+ # Remove objects from the index and immediately commit. See Sunspot.remove
322
+ #
323
+ # ==== Parameters
324
+ #
325
+ # objects...<Object>:: Objects to remove from the index
326
+ #
327
+ def remove!
328
+ session.remove!(*objects)
329
+ end
330
+
331
+ #
332
+ # Remove an object from the index using its class name and primary key.
333
+ # Useful if you know this information and want to remove an object without
334
+ # instantiating it from persistent storage
335
+ #
336
+ # ==== Parameters
337
+ #
338
+ # clazz<Class>:: Class of the object, or class name as a string or symbol
339
+ # id::
340
+ # Primary key of the object. This should be the same id that would be
341
+ # returned by the class's instance adapter.
342
+ #
343
+ def remove_by_id(clazz, id)
344
+ session.remove_by_id(clazz, id)
345
+ end
346
+
347
+ #
348
+ # Remove an object by class name and primary key, and immediately commit.
349
+ # See #remove_by_id and #commit
350
+ #
351
+ def remove_by_id!(clazz, id)
352
+ session.remove_by_id!(clazz, id)
353
+ end
354
+
355
+ # Remove all objects of the given classes from the index. There isn't much
356
+ # use for this in general operations but it can be useful for maintenance,
357
+ # testing, etc. If no arguments are passed, remove everything from the
358
+ # index.
359
+ #
360
+ # ==== Parameters
361
+ #
362
+ # classes...<Class>::
363
+ # classes for which to remove all instances from the index (may pass an
364
+ # array or varargs)
365
+ #
366
+ # ==== Example
367
+ #
368
+ # Sunspot.remove_all(Post, Blog)
369
+ #
370
+ def remove_all(*classes)
371
+ session.remove_all(*classes)
372
+ end
373
+
374
+ #
375
+ # Remove all objects of the given classes from the index and immediately
376
+ # commit. See Sunspot.remove_all
377
+ #
378
+ # ==== Parameters
379
+ #
380
+ # classes...<Class>::
381
+ # classes for which to remove all instances from the index
382
+ def remove_all!(*classes)
383
+ session.remove_all!(*classes)
384
+ end
385
+
386
+ #
387
+ # Process all adds in a batch. Any Sunspot adds initiated inside the block
388
+ # will be sent in bulk when the block finishes. Useful if your application
389
+ # initiates index adds from various places in code as part of a single
390
+ # operation; doing a batch add will give better performance.
391
+ #
392
+ # ==== Example
393
+ #
394
+ # Sunspot.batch do
395
+ # post = Post.new
396
+ # Sunspot.add(post)
397
+ # comment = Comment.new
398
+ # Sunspot.add(comment)
399
+ # end
400
+ #
401
+ # Sunspot will send both the post and the comment in a single request.
402
+ #
403
+ def batch(&block)
404
+ session.batch(&block)
405
+ end
406
+
407
+ #
408
+ # True if documents have been added, updated, or removed since the last
409
+ # commit.
410
+ #
411
+ # ==== Returns
412
+ #
413
+ # Boolean:: Whether there have been any updates since the last commit
414
+ #
415
+ def dirty?
416
+ session.dirty?
417
+ end
418
+
419
+ #
420
+ # Sends a commit if the session is dirty (see #dirty?).
421
+ #
422
+ def commit_if_dirty
423
+ session.commit_if_dirty
424
+ end
425
+
426
+ # Returns the configuration associated with the singleton session. See
427
+ # Sunspot::Configuration for details.
428
+ #
429
+ # ==== Returns
430
+ #
431
+ # LightConfig::Configuration:: configuration for singleton session
432
+ #
433
+ def config
434
+ session.config
435
+ end
436
+
437
+ #
438
+ # Resets the singleton session. This is useful for clearing out all
439
+ # static data between tests, but probably nowhere else.
440
+ #
441
+ # ==== Parameters
442
+ #
443
+ # keep_config<Boolean>::
444
+ # Whether to retain the configuration used by the current singleton
445
+ # session. Default false.
446
+ #
447
+ def reset!(keep_config = false)
448
+ config =
449
+ if keep_config
450
+ session.config
451
+ else
452
+ Configuration.build
453
+ end
454
+ @session = Session.new(config)
455
+ end
456
+
457
+ private
458
+
459
+ #
460
+ # Get the singleton session, creating it if none yet exists.
461
+ #
462
+ # ==== Returns
463
+ #
464
+ # Sunspot::Session:: the singleton session
465
+ #
466
+ def session #:nodoc:
467
+ @session ||= Session.new
468
+ end
469
+ end
470
+ end