pduey-sunspot_rails 1.2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. data/.gitignore +5 -0
  2. data/.rspec +1 -0
  3. data/History.txt +54 -0
  4. data/LICENSE +18 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.rdoc +282 -0
  7. data/Rakefile +18 -0
  8. data/TESTING.md +35 -0
  9. data/TODO +8 -0
  10. data/VERSION.yml +4 -0
  11. data/dev_tasks/rdoc.rake +24 -0
  12. data/dev_tasks/release.rake +4 -0
  13. data/dev_tasks/spec.rake +27 -0
  14. data/dev_tasks/todo.rake +4 -0
  15. data/generators/sunspot/sunspot_generator.rb +9 -0
  16. data/generators/sunspot/templates/sunspot.yml +18 -0
  17. data/install.rb +1 -0
  18. data/lib/generators/sunspot_rails.rb +9 -0
  19. data/lib/generators/sunspot_rails/install/install_generator.rb +13 -0
  20. data/lib/generators/sunspot_rails/install/templates/config/sunspot.yml +17 -0
  21. data/lib/sunspot/rails.rb +63 -0
  22. data/lib/sunspot/rails/adapters.rb +83 -0
  23. data/lib/sunspot/rails/configuration.rb +323 -0
  24. data/lib/sunspot/rails/init.rb +5 -0
  25. data/lib/sunspot/rails/log_subscriber.rb +33 -0
  26. data/lib/sunspot/rails/railtie.rb +36 -0
  27. data/lib/sunspot/rails/railties/controller_runtime.rb +36 -0
  28. data/lib/sunspot/rails/request_lifecycle.rb +36 -0
  29. data/lib/sunspot/rails/searchable.rb +420 -0
  30. data/lib/sunspot/rails/server.rb +173 -0
  31. data/lib/sunspot/rails/solr_instrumentation.rb +20 -0
  32. data/lib/sunspot/rails/solr_logging.rb +62 -0
  33. data/lib/sunspot/rails/spec_helper.rb +26 -0
  34. data/lib/sunspot/rails/stub_session_proxy.rb +142 -0
  35. data/lib/sunspot/rails/tasks.rb +66 -0
  36. data/lib/sunspot_rails.rb +12 -0
  37. data/spec/configuration_spec.rb +173 -0
  38. data/spec/model_lifecycle_spec.rb +63 -0
  39. data/spec/model_spec.rb +478 -0
  40. data/spec/rails2/.gitignore +2 -0
  41. data/spec/rails2/Gemfile +15 -0
  42. data/spec/rails2/Rakefile +17 -0
  43. data/spec/rails2/app/controllers/application.rb +10 -0
  44. data/spec/rails2/app/controllers/application_controller.rb +10 -0
  45. data/spec/rails2/app/controllers/posts_controller.rb +6 -0
  46. data/spec/rails2/app/models/author.rb +8 -0
  47. data/spec/rails2/app/models/blog.rb +12 -0
  48. data/spec/rails2/app/models/location.rb +2 -0
  49. data/spec/rails2/app/models/photo_post.rb +2 -0
  50. data/spec/rails2/app/models/post.rb +11 -0
  51. data/spec/rails2/app/models/post_with_auto.rb +10 -0
  52. data/spec/rails2/app/models/post_with_conditional_index.rb +13 -0
  53. data/spec/rails2/app/models/post_with_default_scope.rb +11 -0
  54. data/spec/rails2/config/boot.rb +116 -0
  55. data/spec/rails2/config/database.yml +4 -0
  56. data/spec/rails2/config/environment.rb +41 -0
  57. data/spec/rails2/config/environments/development.rb +27 -0
  58. data/spec/rails2/config/environments/test.rb +27 -0
  59. data/spec/rails2/config/initializers/new_rails_defaults.rb +19 -0
  60. data/spec/rails2/config/initializers/session_store.rb +15 -0
  61. data/spec/rails2/config/preinitializer.rb +19 -0
  62. data/spec/rails2/config/routes.rb +43 -0
  63. data/spec/rails2/config/sunspot.yml +19 -0
  64. data/spec/rails2/db/schema.rb +27 -0
  65. data/spec/rails2/script/console +3 -0
  66. data/spec/rails3/.gitignore +4 -0
  67. data/spec/rails3/Gemfile +15 -0
  68. data/spec/rails3/README +281 -0
  69. data/spec/rails3/Rakefile +7 -0
  70. data/spec/rails3/app/controllers/application.rb +4 -0
  71. data/spec/rails3/app/controllers/application_controller.rb +4 -0
  72. data/spec/rails3/app/controllers/posts_controller.rb +6 -0
  73. data/spec/rails3/app/models/author.rb +8 -0
  74. data/spec/rails3/app/models/blog.rb +12 -0
  75. data/spec/rails3/app/models/location.rb +2 -0
  76. data/spec/rails3/app/models/photo_post.rb +2 -0
  77. data/spec/rails3/app/models/post.rb +11 -0
  78. data/spec/rails3/app/models/post_with_auto.rb +10 -0
  79. data/spec/rails3/app/models/post_with_conditional_index.rb +13 -0
  80. data/spec/rails3/app/models/post_with_default_scope.rb +11 -0
  81. data/spec/rails3/config.ru +4 -0
  82. data/spec/rails3/config/application.rb +46 -0
  83. data/spec/rails3/config/boot.rb +13 -0
  84. data/spec/rails3/config/database.yml +22 -0
  85. data/spec/rails3/config/environment.rb +5 -0
  86. data/spec/rails3/config/environments/development.rb +19 -0
  87. data/spec/rails3/config/environments/test.rb +19 -0
  88. data/spec/rails3/config/initializers/backtrace_silencers.rb +7 -0
  89. data/spec/rails3/config/initializers/inflections.rb +10 -0
  90. data/spec/rails3/config/initializers/mime_types.rb +5 -0
  91. data/spec/rails3/config/initializers/new_rails_defaults.rb +19 -0
  92. data/spec/rails3/config/initializers/secret_token.rb +7 -0
  93. data/spec/rails3/config/initializers/session_store.rb +8 -0
  94. data/spec/rails3/config/locales/en.yml +5 -0
  95. data/spec/rails3/config/routes.rb +59 -0
  96. data/spec/rails3/config/sunspot.yml +19 -0
  97. data/spec/rails3/db/schema.rb +27 -0
  98. data/spec/rails3/script/console +3 -0
  99. data/spec/rails3/script/rails +6 -0
  100. data/spec/rails3/solr/.gitignore +1 -0
  101. data/spec/rails3/solr/conf/elevate.xml +36 -0
  102. data/spec/rails3/solr/conf/schema.xml +238 -0
  103. data/spec/rails3/solr/conf/solrconfig.xml +938 -0
  104. data/spec/rails3/solr/conf/spellings.txt +2 -0
  105. data/spec/rails3/solr/conf/stopwords.txt +58 -0
  106. data/spec/rails3/solr/conf/synonyms.txt +31 -0
  107. data/spec/rails3/solr/lib/lucene-spatial-2.9.1.jar +0 -0
  108. data/spec/rails3/solr/lib/solr-spatial-light-0.0.6.jar +0 -0
  109. data/spec/request_lifecycle_spec.rb +61 -0
  110. data/spec/schema.rb +27 -0
  111. data/spec/server_spec.rb +37 -0
  112. data/spec/session_spec.rb +24 -0
  113. data/spec/spec_helper.rb +46 -0
  114. data/spec/stub_session_proxy_spec.rb +122 -0
  115. data/sunspot_rails.gemspec +44 -0
  116. metadata +322 -0
@@ -0,0 +1,5 @@
1
+ Sunspot.session = Sunspot::Rails.build_session
2
+ Sunspot::Adapters::InstanceAdapter.register(Sunspot::Rails::Adapters::ActiveRecordInstanceAdapter, ActiveRecord::Base)
3
+ Sunspot::Adapters::DataAccessor.register(Sunspot::Rails::Adapters::ActiveRecordDataAccessor, ActiveRecord::Base)
4
+ ActiveRecord::Base.module_eval { include(Sunspot::Rails::Searchable) }
5
+ ActionController::Base.module_eval { include(Sunspot::Rails::RequestLifecycle) }
@@ -0,0 +1,33 @@
1
+ module Sunspot
2
+ module Rails
3
+ class LogSubscriber < ActiveSupport::LogSubscriber
4
+ def self.runtime=(value)
5
+ Thread.current["sorl_runtime"] = value
6
+ end
7
+
8
+ def self.runtime
9
+ Thread.current["sorl_runtime"] ||= 0
10
+ end
11
+
12
+ def self.reset_runtime
13
+ rt, self.runtime = runtime, 0
14
+ rt
15
+ end
16
+
17
+ def request(event)
18
+ self.class.runtime += event.duration
19
+ return unless logger.debug?
20
+
21
+ name = '%s (%.1fms)' % ["SOLR Request", event.duration]
22
+
23
+ # produces: path=/select parameters={fq: ["type:Tag"], q: rossi, fl: * score, qf: tag_name_text, defType: dismax, start: 0, rows: 20}
24
+ parameters = event.payload[:parameters].map { |k, v| "#{k}: #{color(v, BOLD, true)}" }.join(', ')
25
+ request = "path=#{event.payload[:path]} parameters={#{parameters}}"
26
+
27
+ debug " #{color(name, GREEN, true)} [ #{request} ]"
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ Sunspot::Rails::LogSubscriber.attach_to :rsolr
@@ -0,0 +1,36 @@
1
+ module Sunspot
2
+ module Rails
3
+ class Railtie < ::Rails::Railtie
4
+ initializer 'sunspot_rails.init' do
5
+ Sunspot.session = Sunspot::Rails.build_session
6
+ ActiveSupport.on_load(:active_record) do
7
+ Sunspot::Adapters::InstanceAdapter.register(Sunspot::Rails::Adapters::ActiveRecordInstanceAdapter, ActiveRecord::Base)
8
+ Sunspot::Adapters::DataAccessor.register(Sunspot::Rails::Adapters::ActiveRecordDataAccessor, ActiveRecord::Base)
9
+ include(Sunspot::Rails::Searchable)
10
+ end
11
+ ActiveSupport.on_load(:action_controller) do
12
+ include(Sunspot::Rails::RequestLifecycle)
13
+ end
14
+ require 'sunspot/rails/log_subscriber'
15
+ RSolr::Connection.module_eval{ include Sunspot::Rails::SolrInstrumentation }
16
+ end
17
+
18
+ # Expose database runtime to controller for logging.
19
+ initializer "sunspot_rails.log_runtime" do |app|
20
+ require "sunspot/rails/railties/controller_runtime"
21
+ ActiveSupport.on_load(:action_controller) do
22
+ include Sunspot::Rails::Railties::ControllerRuntime
23
+ end
24
+ end
25
+
26
+ rake_tasks do
27
+ load 'sunspot/rails/tasks.rb'
28
+ end
29
+
30
+ generators do
31
+ load "generators/sunspot_rails.rb"
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ module Sunspot
2
+ module Rails
3
+ module Railties
4
+ module ControllerRuntime
5
+ extend ActiveSupport::Concern
6
+
7
+ protected
8
+
9
+ attr_internal :solr_runtime
10
+
11
+ def cleanup_view_runtime
12
+ # TODO only if solr is connected? if not call to super
13
+
14
+ solr_rt_before_render = Sunspot::Rails::LogSubscriber.reset_runtime
15
+ runtime = super
16
+ solr_rt_after_render = Sunspot::Rails::LogSubscriber.reset_runtime
17
+ self.solr_runtime = solr_rt_before_render + solr_rt_after_render
18
+ runtime - solr_rt_after_render
19
+ end
20
+
21
+ def append_info_to_payload(payload)
22
+ super
23
+ payload[:solr_runtime] = solr_runtime
24
+ end
25
+
26
+ module ClassMethods
27
+ def log_process_action(payload)
28
+ messages, solr_runtime = super, payload[:solr_runtime]
29
+ messages << ("Solr: %.1fms" % solr_runtime.to_f) if solr_runtime
30
+ messages
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ module Sunspot #:nodoc:
2
+ module Rails #:nodoc:
3
+ #
4
+ # This module adds an after_filter to ActionController::Base that commits
5
+ # the Sunspot session if any documents have been added, changed, or removed
6
+ # in the course of the request.
7
+ #
8
+ module RequestLifecycle
9
+ class <<self
10
+ def included(base) #:nodoc:
11
+ subclasses = base.subclasses.map do |subclass|
12
+ begin
13
+ subclass.constantize
14
+ rescue NameError
15
+ end
16
+ end.compact
17
+ loaded_controllers = [base].concat(subclasses)
18
+ # Depending on how Sunspot::Rails is loaded, there may already be
19
+ # controllers loaded into memory that subclass this controller. In
20
+ # this case, since after_filter uses the inheritable_attribute
21
+ # structure, the already-loaded subclasses don't get the filters. So,
22
+ # the below ensures that all loaded controllers have the filter.
23
+ loaded_controllers.each do |controller|
24
+ controller.after_filter do
25
+ if Sunspot::Rails.configuration.auto_commit_after_request?
26
+ Sunspot.commit_if_dirty
27
+ elsif Sunspot::Rails.configuration.auto_commit_after_delete_request?
28
+ Sunspot.commit_if_delete_dirty
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,420 @@
1
+ module Sunspot #:nodoc:
2
+ module Rails #:nodoc:
3
+ #
4
+ # This module adds Sunspot functionality to ActiveRecord models. As well as
5
+ # providing class and instance methods, it optionally adds lifecycle hooks
6
+ # to automatically add and remove models from the Solr index as they are
7
+ # created and destroyed.
8
+ #
9
+ module Searchable
10
+ class <<self
11
+ def included(base) #:nodoc:
12
+ base.module_eval do
13
+ extend(ActsAsMethods)
14
+ end
15
+ end
16
+ end
17
+
18
+ module ActsAsMethods
19
+ #
20
+ # Makes a class searchable if it is not already, or adds search
21
+ # configuration if it is. Note that the options passed in are only used
22
+ # the first time this method is called for a particular class; so,
23
+ # search should be defined before activating any mixins that extend
24
+ # search configuration.
25
+ #
26
+ # The block passed into this method is evaluated by the
27
+ # <code>Sunspot.setup</code> method. See the Sunspot documentation for
28
+ # complete information on the functionality provided by that method.
29
+ #
30
+ # ==== Options (+options+)
31
+ #
32
+ # :auto_index<Boolean>::
33
+ # Automatically index models in Solr when they are saved.
34
+ # Default: true
35
+ # :auto_remove<Boolean>::
36
+ # Automatically remove models from the Solr index when they are
37
+ # destroyed. <b>Setting this option to +false+ is not recommended
38
+ # </b>(see the README).
39
+ # :ignore_attribute_changes_of<Array>::
40
+ # Define attributes, that should not trigger a reindex of that
41
+ # object. Usual suspects are updated_at or counters.
42
+ # :include<Mixed>::
43
+ # Define default ActiveRecord includes, set this to allow ActiveRecord
44
+ # to load required associations when indexing. See ActiveRecord's
45
+ # documentation on eager-loading for examples on how to set this
46
+ # Default: []
47
+ #
48
+ # ==== Example
49
+ #
50
+ # class Post < ActiveRecord::Base
51
+ # searchable do
52
+ # text :title, :body
53
+ # string :sort_title do
54
+ # title.downcase.sub(/^(an?|the)/, '')
55
+ # end
56
+ # integer :blog_id
57
+ # time :updated_at
58
+ # end
59
+ # end
60
+ #
61
+ def searchable(options = {}, &block)
62
+ Sunspot.setup(self, &block)
63
+
64
+ if searchable?
65
+ sunspot_options[:include].concat(Util::Array(options[:include]))
66
+ else
67
+ extend ClassMethods
68
+ include InstanceMethods
69
+
70
+ class_attribute :sunspot_options
71
+
72
+ unless options[:auto_index] == false
73
+ before_save :maybe_mark_for_auto_indexing
74
+ after_save :maybe_auto_index
75
+ end
76
+
77
+ unless options[:auto_remove] == false
78
+ after_destroy do |searchable|
79
+ searchable.remove_from_index
80
+ end
81
+ end
82
+ options[:include] = Util::Array(options[:include])
83
+
84
+ self.sunspot_options = options
85
+ end
86
+ end
87
+
88
+ #
89
+ # This method is defined on all ActiveRecord::Base subclasses. It
90
+ # is false for classes on which #searchable has not been called, and
91
+ # true for classes on which #searchable has been called.
92
+ #
93
+ # ==== Returns
94
+ #
95
+ # +false+
96
+ #
97
+ def searchable?
98
+ false
99
+ end
100
+ end
101
+
102
+ module ClassMethods
103
+ def self.extended(base) #:nodoc:
104
+ class <<base
105
+ alias_method :search, :solr_search unless method_defined? :search
106
+ alias_method :search_ids, :solr_search_ids unless method_defined? :search_ids
107
+ alias_method :remove_all_from_index, :solr_remove_all_from_index unless method_defined? :remove_all_from_index
108
+ alias_method :remove_all_from_index!, :solr_remove_all_from_index! unless method_defined? :remove_all_from_index!
109
+ alias_method :reindex, :solr_reindex unless method_defined? :reindex
110
+ alias_method :index, :solr_index unless method_defined? :index
111
+ alias_method :index_orphans, :solr_index_orphans unless method_defined? :index_orphans
112
+ alias_method :clean_index_orphans, :solr_clean_index_orphans unless method_defined? :clean_index_orphans
113
+ end
114
+ end
115
+ #
116
+ # Search for instances of this class in Solr. The block is delegated to
117
+ # the Sunspot.search method - see the Sunspot documentation for the full
118
+ # API.
119
+ #
120
+ # ==== Example
121
+ #
122
+ # Post.search(:include => [:blog]) do
123
+ # keywords 'best pizza'
124
+ # with :blog_id, 1
125
+ # order :updated_at, :desc
126
+ # facet :category_ids
127
+ # end
128
+ #
129
+ # ==== Options
130
+ #
131
+ # :include:: Specify associations to eager load
132
+ # :select:: Specify columns to select from database when loading results
133
+ #
134
+ # ==== Returns
135
+ #
136
+ # Sunspot::Search:: Object containing results, totals, facets, etc.
137
+ #
138
+ def solr_search(options = {}, &block)
139
+ solr_execute_search(options) do
140
+ Sunspot.new_search(self, &block)
141
+ end
142
+ end
143
+
144
+ #
145
+ # Get IDs of matching results without loading the result objects from
146
+ # the database. This method may be useful if search is used as an
147
+ # intermediate step in a larger find operation. The block is the same
148
+ # as the block provided to the #search method.
149
+ #
150
+ # ==== Returns
151
+ #
152
+ # Array:: Array of IDs, in the order returned by the search
153
+ #
154
+ def solr_search_ids(&block)
155
+ solr_execute_search_ids do
156
+ solr_search(&block)
157
+ end
158
+ end
159
+
160
+ #
161
+ # Remove instances of this class from the Solr index.
162
+ #
163
+ def solr_remove_all_from_index
164
+ Sunspot.remove_all(self)
165
+ end
166
+
167
+ #
168
+ # Remove all instances of this class from the Solr index and immediately
169
+ # commit.
170
+ #
171
+ #
172
+ def solr_remove_all_from_index!
173
+ Sunspot.remove_all!(self)
174
+ end
175
+
176
+ #
177
+ # Completely rebuild the index for this class. First removes all
178
+ # instances from the index, then loads records and indexes them.
179
+ #
180
+ # See #index for information on options, etc.
181
+ #
182
+ def solr_reindex(options = {})
183
+ solr_remove_all_from_index
184
+ solr_index(options)
185
+ end
186
+
187
+ #
188
+ # Add/update all existing records in the Solr index. The
189
+ # +batch_size+ argument specifies how many records to load out of the
190
+ # database at a time. The default batch size is 50; if nil is passed,
191
+ # records will not be indexed in batches. By default, a commit is issued
192
+ # after each batch; passing +false+ for +batch_commit+ will disable
193
+ # this, and only issue a commit at the end of the process. If associated
194
+ # objects need to indexed also, you can specify +include+ in format
195
+ # accepted by ActiveRecord to improve your sql select performance
196
+ #
197
+ # ==== Options (passed as a hash)
198
+ #
199
+ # batch_size<Integer>:: Batch size with which to load records. Passing
200
+ # 'nil' will skip batches. Default is 50.
201
+ # batch_commit<Boolean>:: Flag signalling if a commit should be done after
202
+ # after each batch is indexed, default is 'true'
203
+ # include<Mixed>:: include option to be passed to the ActiveRecord find,
204
+ # used for including associated objects that need to be
205
+ # indexed with the parent object, accepts all formats
206
+ # ActiveRecord::Base.find does
207
+ # first_id:: The lowest possible ID for this class. Defaults to 0, which
208
+ # is fine for integer IDs; string primary keys will need to
209
+ # specify something reasonable here.
210
+ #
211
+ # ==== Examples
212
+ #
213
+ # # index in batches of 50, commit after each
214
+ # Post.index
215
+ #
216
+ # # index all rows at once, then commit
217
+ # Post.index(:batch_size => nil)
218
+ #
219
+ # # index in batches of 50, commit when all batches complete
220
+ # Post.index(:batch_commit => false)
221
+ #
222
+ # # include the associated +author+ object when loading to index
223
+ # Post.index(:include => :author)
224
+ #
225
+ def solr_index(opts={})
226
+ options = {
227
+ :batch_size => 50,
228
+ :batch_commit => true,
229
+ :include => self.sunspot_options[:include],
230
+ :first_id => 0
231
+ }.merge(opts)
232
+ progress_bar = options[:progress_bar]
233
+ if options[:batch_size]
234
+ counter = 0
235
+ find_in_batches(:include => options[:include], :batch_size => options[:batch_size], :start => options[:first_id]) do |records|
236
+ solr_benchmark options[:batch_size], counter do
237
+ records = records.select{ |r| r.indexable? }
238
+ Sunspot.index(records)
239
+ end
240
+ Sunspot.commit if options[:batch_commit]
241
+ counter += 1
242
+ progress_bar.increment! records.size unless progress_bar.nil?
243
+ end
244
+ Sunspot.commit unless options[:batch_commit]
245
+ else
246
+ records = all(:include => options[:include]).select{ |r| r.indexable? }
247
+ Sunspot.index!(records)
248
+ end
249
+ end
250
+
251
+ #
252
+ # Return the IDs of records of this class that are indexed in Solr but
253
+ # do not exist in the database. Under normal circumstances, this should
254
+ # never happen, but this method is provided in case something goes
255
+ # wrong. Usually you will want to rectify the situation by calling
256
+ # #clean_index_orphans or #reindex
257
+ #
258
+ # ==== Returns
259
+ #
260
+ # Array:: Collection of IDs that exist in Solr but not in the database
261
+ def solr_index_orphans
262
+ count = self.count
263
+ indexed_ids = solr_search_ids { paginate(:page => 1, :per_page => count) }.to_set
264
+ all(:select => 'id').each do |object|
265
+ indexed_ids.delete(object.id)
266
+ end
267
+ indexed_ids.to_a
268
+ end
269
+
270
+ #
271
+ # Find IDs of records of this class that are indexed in Solr but do not
272
+ # exist in the database, and remove them from Solr. Under normal
273
+ # circumstances, this should not be necessary; this method is provided
274
+ # in case something goes wrong.
275
+ #
276
+ def solr_clean_index_orphans
277
+ solr_index_orphans.each do |id|
278
+ new do |fake_instance|
279
+ fake_instance.id = id
280
+ end.solr_remove_from_index
281
+ end
282
+ end
283
+
284
+ #
285
+ # Classes that have been defined as searchable return +true+ for this
286
+ # method.
287
+ #
288
+ # ==== Returns
289
+ #
290
+ # +true+
291
+ #
292
+ def searchable?
293
+ true
294
+ end
295
+
296
+ def solr_execute_search(options = {})
297
+ options.assert_valid_keys(:include, :select)
298
+ search = yield
299
+ unless options.empty?
300
+ search.build do |query|
301
+ if options[:include]
302
+ query.data_accessor_for(self).include = options[:include]
303
+ end
304
+ if options[:select]
305
+ query.data_accessor_for(self).select = options[:select]
306
+ end
307
+ end
308
+ end
309
+ search.execute
310
+ end
311
+
312
+ def solr_execute_search_ids(options = {})
313
+ search = yield
314
+ search.raw_results.map { |raw_result| raw_result.primary_key.to_i }
315
+ end
316
+
317
+ protected
318
+
319
+ #
320
+ # Does some logging for benchmarking indexing performance
321
+ #
322
+ def solr_benchmark(batch_size, counter, &block)
323
+ start = Time.now
324
+ logger.info("[#{Time.now}] Start Indexing")
325
+ yield
326
+ elapsed = Time.now-start
327
+ logger.info("[#{Time.now}] Completed Indexing. Rows indexed #{counter * batch_size}. Rows/sec: #{batch_size/elapsed.to_f} (Elapsed: #{elapsed} sec.)")
328
+ end
329
+
330
+ end
331
+
332
+ module InstanceMethods
333
+ def self.included(base) #:nodoc:
334
+ base.module_eval do
335
+ alias_method :index, :solr_index unless method_defined? :index
336
+ alias_method :index!, :solr_index! unless method_defined? :index!
337
+ alias_method :remove_from_index, :solr_remove_from_index unless method_defined? :remove_from_index
338
+ alias_method :remove_from_index!, :solr_remove_from_index! unless method_defined? :remove_from_index!
339
+ alias_method :more_like_this, :solr_more_like_this unless method_defined? :more_like_this
340
+ alias_method :more_like_this_ids, :solr_more_like_this_ids unless method_defined? :more_like_this_ids
341
+ end
342
+ end
343
+ #
344
+ # Index the model in Solr. If the model is already indexed, it will be
345
+ # updated. Using the defaults, you will usually not need to call this
346
+ # method, as models are indexed automatically when they are created or
347
+ # updated. If you have disabled automatic indexing (see
348
+ # ClassMethods#searchable), this method allows you to manage indexing
349
+ # manually.
350
+ #
351
+ def solr_index
352
+ Sunspot.index(self) if indexable?
353
+ end
354
+
355
+ #
356
+ # Index the model in Solr and immediately commit. See #index
357
+ #
358
+ def solr_index!
359
+ Sunspot.index!(self) if indexable?
360
+ end
361
+
362
+ #
363
+ # Remove the model from the Solr index. Using the defaults, this should
364
+ # not be necessary, as models will automatically be removed from the
365
+ # index when they are destroyed. If you disable automatic removal
366
+ # (which is not recommended!), you can use this method to manage removal
367
+ # manually.
368
+ #
369
+ def solr_remove_from_index
370
+ Sunspot.remove(self) if indexable?
371
+ end
372
+
373
+ #
374
+ # Remove the model from the Solr index and commit immediately. See
375
+ # #remove_from_index
376
+ #
377
+ def solr_remove_from_index!
378
+ Sunspot.remove!(self) if indexable?
379
+ end
380
+
381
+ def solr_more_like_this(*args, &block)
382
+ options = args.extract_options!
383
+ self.class.solr_execute_search(options) do
384
+ Sunspot.new_more_like_this(self, *args, &block)
385
+ end
386
+ end
387
+
388
+ def solr_more_like_this_ids(&block)
389
+ self.class.solr_execute_search_ids do
390
+ solr_more_like_this(&block)
391
+ end
392
+ end
393
+
394
+ def indexable?
395
+ return true unless sunspot_options.has_key?(:if)
396
+ send(sunspot_options[:if])
397
+ end
398
+
399
+ private
400
+
401
+ def maybe_mark_for_auto_indexing
402
+ @marked_for_auto_indexing =
403
+ if !new_record? && ignore_attributes = self.class.sunspot_options[:ignore_attribute_changes_of]
404
+ @marked_for_auto_indexing = !(changed.map { |attr| attr.to_sym } - ignore_attributes).blank?
405
+ else
406
+ true
407
+ end
408
+ true
409
+ end
410
+
411
+ def maybe_auto_index
412
+ if @marked_for_auto_indexing
413
+ solr_index
414
+ remove_instance_variable(:@marked_for_auto_indexing)
415
+ end
416
+ end
417
+ end
418
+ end
419
+ end
420
+ end