sunspot_rails 1.2.1 → 1.3.0.rc6
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/.rspec +1 -0
- data/History.txt +15 -0
- data/README.rdoc +14 -7
- data/Rakefile +0 -6
- data/TESTING.md +32 -18
- data/dev_tasks/spec.rake +103 -18
- data/gemfiles/rails-2.3.14 +15 -0
- data/gemfiles/rails-3.0.10 +15 -0
- data/gemfiles/rails-3.1.1 +15 -0
- data/lib/sunspot/rails.rb +13 -6
- data/lib/sunspot/rails/configuration.rb +25 -4
- data/lib/sunspot/rails/log_subscriber.rb +33 -0
- data/lib/sunspot/rails/railtie.rb +10 -0
- data/lib/sunspot/rails/railties/controller_runtime.rb +36 -0
- data/lib/sunspot/rails/searchable.rb +88 -20
- data/lib/sunspot/rails/server.rb +10 -77
- data/lib/sunspot/rails/solr_instrumentation.rb +20 -0
- data/lib/sunspot/rails/solr_logging.rb +16 -17
- data/lib/sunspot/rails/stub_session_proxy.rb +56 -2
- data/lib/sunspot/rails/tasks.rb +56 -33
- data/lib/sunspot_rails.rb +5 -0
- data/spec/configuration_spec.rb +27 -5
- data/spec/model_lifecycle_spec.rb +1 -1
- data/spec/model_spec.rb +240 -1
- data/spec/rails_template/app/controllers/application_controller.rb +10 -0
- data/spec/rails_template/app/controllers/posts_controller.rb +6 -0
- data/spec/rails_template/app/models/author.rb +8 -0
- data/spec/rails_template/app/models/blog.rb +12 -0
- data/spec/rails_template/app/models/location.rb +2 -0
- data/spec/rails_template/app/models/photo_post.rb +2 -0
- data/spec/rails_template/app/models/post.rb +11 -0
- data/spec/rails_template/app/models/post_with_auto.rb +10 -0
- data/spec/rails_template/app/models/post_with_default_scope.rb +11 -0
- data/spec/rails_template/config/boot.rb +127 -0
- data/spec/rails_template/config/preinitializer.rb +22 -0
- data/spec/rails_template/config/routes.rb +9 -0
- data/spec/rails_template/config/sunspot.yml +22 -0
- data/spec/rails_template/db/schema.rb +27 -0
- data/spec/request_lifecycle_spec.rb +1 -1
- data/spec/searchable_spec.rb +12 -0
- data/spec/server_spec.rb +2 -6
- data/spec/session_spec.rb +35 -2
- data/spec/shared_examples/indexed_after_save.rb +8 -0
- data/spec/shared_examples/not_indexed_after_save.rb +8 -0
- data/spec/spec_helper.rb +4 -2
- data/spec/stub_session_proxy_spec.rb +2 -2
- data/sunspot_rails.gemspec +43 -0
- metadata +114 -44
- data/lib/sunspot/rails/version.rb +0 -5
@@ -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
|
@@ -11,6 +11,16 @@ module Sunspot
|
|
11
11
|
ActiveSupport.on_load(:action_controller) do
|
12
12
|
include(Sunspot::Rails::RequestLifecycle)
|
13
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
|
14
24
|
end
|
15
25
|
|
16
26
|
rake_tasks do
|
@@ -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
|
@@ -36,6 +36,13 @@ module Sunspot #:nodoc:
|
|
36
36
|
# Automatically remove models from the Solr index when they are
|
37
37
|
# destroyed. <b>Setting this option to +false+ is not recommended
|
38
38
|
# </b>(see the README).
|
39
|
+
# :if<Mixed>::
|
40
|
+
# Only index models in Solr if the method, proc or string evaluates
|
41
|
+
# to true (e.g. <code>:if => :should_index?</code> or <code>:if =>
|
42
|
+
# proc { |model| model.foo > 2 }</code>). Models that do not match
|
43
|
+
# the constraint will be removed from the index upon save. Multiple
|
44
|
+
# constraints can be specified by passing an array (e.g. <code>:if =>
|
45
|
+
# [:method1, :method2]</code>).
|
39
46
|
# :ignore_attribute_changes_of<Array>::
|
40
47
|
# Define attributes, that should not trigger a reindex of that
|
41
48
|
# object. Usual suspects are updated_at or counters.
|
@@ -44,6 +51,13 @@ module Sunspot #:nodoc:
|
|
44
51
|
# to load required associations when indexing. See ActiveRecord's
|
45
52
|
# documentation on eager-loading for examples on how to set this
|
46
53
|
# Default: []
|
54
|
+
# :unless<Mixed>::
|
55
|
+
# Only index models in Solr if the method, proc or string evaluates
|
56
|
+
# to false (e.g. <code>:unless => :should_not_index?</code> or <code>
|
57
|
+
# :unless => proc { |model| model.foo <= 2 }</code>). Models that do
|
58
|
+
# not match the constraint will be removed from the index upon save.
|
59
|
+
# Multiple constraints can be specified by passing an array (e.g.
|
60
|
+
# <code>:unless => [:method1, :method2]</code>).
|
47
61
|
#
|
48
62
|
# ==== Example
|
49
63
|
#
|
@@ -67,11 +81,11 @@ module Sunspot #:nodoc:
|
|
67
81
|
extend ClassMethods
|
68
82
|
include InstanceMethods
|
69
83
|
|
70
|
-
|
71
|
-
|
84
|
+
class_attribute :sunspot_options
|
85
|
+
|
72
86
|
unless options[:auto_index] == false
|
73
|
-
before_save :
|
74
|
-
after_save :
|
87
|
+
before_save :mark_for_auto_indexing_or_removal
|
88
|
+
after_save :perform_index_tasks
|
75
89
|
end
|
76
90
|
|
77
91
|
unless options[:auto_remove] == false
|
@@ -227,22 +241,31 @@ module Sunspot #:nodoc:
|
|
227
241
|
:batch_size => 50,
|
228
242
|
:batch_commit => true,
|
229
243
|
:include => self.sunspot_options[:include],
|
230
|
-
:
|
244
|
+
:start => opts.delete(:first_id) || 0
|
231
245
|
}.merge(opts)
|
232
|
-
|
246
|
+
find_in_batch_options = {
|
247
|
+
:include => options[:include],
|
248
|
+
:batch_size => options[:batch_size],
|
249
|
+
:start => options[:first_id]
|
250
|
+
}
|
251
|
+
progress_bar = options[:progress_bar]
|
233
252
|
if options[:batch_size]
|
234
|
-
|
235
|
-
find_in_batches(
|
236
|
-
solr_benchmark options[:batch_size],
|
237
|
-
Sunspot.index(records)
|
253
|
+
batch_counter = 0
|
254
|
+
find_in_batches(find_in_batch_options) do |records|
|
255
|
+
solr_benchmark options[:batch_size], batch_counter do
|
256
|
+
Sunspot.index(records.select { |model| model.indexable? })
|
257
|
+
Sunspot.commit if options[:batch_commit]
|
238
258
|
end
|
239
|
-
|
240
|
-
|
259
|
+
# track progress
|
260
|
+
progress_bar.increment!(records.length) if progress_bar
|
261
|
+
batch_counter += 1
|
241
262
|
end
|
242
|
-
Sunspot.commit unless options[:batch_commit]
|
243
263
|
else
|
244
|
-
|
264
|
+
records = all(:include => options[:include]).select { |model| model.indexable? }
|
265
|
+
Sunspot.index!(records)
|
245
266
|
end
|
267
|
+
# perform a final commit if not committing in batches
|
268
|
+
Sunspot.commit unless options[:batch_commit]
|
246
269
|
end
|
247
270
|
|
248
271
|
#
|
@@ -388,22 +411,67 @@ module Sunspot #:nodoc:
|
|
388
411
|
end
|
389
412
|
end
|
390
413
|
|
414
|
+
def indexable?
|
415
|
+
# options[:if] is not specified or they successfully pass
|
416
|
+
if_passes = self.class.sunspot_options[:if].nil? ||
|
417
|
+
constraint_passes?(self.class.sunspot_options[:if])
|
418
|
+
|
419
|
+
# options[:unless] is not specified or they successfully pass
|
420
|
+
unless_passes = self.class.sunspot_options[:unless].nil? ||
|
421
|
+
!constraint_passes?(self.class.sunspot_options[:unless])
|
422
|
+
|
423
|
+
if_passes and unless_passes
|
424
|
+
end
|
425
|
+
|
391
426
|
private
|
392
427
|
|
393
|
-
def
|
394
|
-
|
395
|
-
|
396
|
-
|
428
|
+
def constraint_passes?(constraint)
|
429
|
+
case constraint
|
430
|
+
when Symbol
|
431
|
+
self.__send__(constraint)
|
432
|
+
when String
|
433
|
+
self.__send__(constraint.to_sym)
|
434
|
+
when Enumerable
|
435
|
+
# All constraints must pass
|
436
|
+
constraint.all? { |inner_constraint| constraint_passes?(inner_constraint) }
|
437
|
+
else
|
438
|
+
if constraint.respond_to?(:call) # could be a Proc or anything else that responds to call
|
439
|
+
constraint.call(self)
|
397
440
|
else
|
398
|
-
|
441
|
+
raise ArgumentError, "Unknown constraint type: #{constraint} (#{constraint.class})"
|
399
442
|
end
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
def mark_for_auto_indexing_or_removal
|
447
|
+
if indexable?
|
448
|
+
# :if/:unless constraints pass or were not present
|
449
|
+
|
450
|
+
@marked_for_auto_indexing =
|
451
|
+
if !new_record? && ignore_attributes = self.class.sunspot_options[:ignore_attribute_changes_of]
|
452
|
+
!(changed.map { |attr| attr.to_sym } - ignore_attributes).blank?
|
453
|
+
else
|
454
|
+
true
|
455
|
+
end
|
456
|
+
|
457
|
+
@marked_for_auto_removal = false
|
458
|
+
else
|
459
|
+
# :if/:unless constraints did not pass; do not auto index and
|
460
|
+
# actually go one step further by removing it from the index
|
461
|
+
@marked_for_auto_indexing = false
|
462
|
+
@marked_for_auto_removal = true
|
463
|
+
end
|
464
|
+
|
400
465
|
true
|
401
466
|
end
|
402
467
|
|
403
|
-
def
|
468
|
+
def perform_index_tasks
|
404
469
|
if @marked_for_auto_indexing
|
405
470
|
solr_index
|
406
471
|
remove_instance_variable(:@marked_for_auto_indexing)
|
472
|
+
elsif @marked_for_auto_removal
|
473
|
+
solr_remove_from_index
|
474
|
+
remove_instance_variable(:@marked_for_auto_removal)
|
407
475
|
end
|
408
476
|
end
|
409
477
|
end
|
data/lib/sunspot/rails/server.rb
CHANGED
@@ -1,47 +1,15 @@
|
|
1
1
|
module Sunspot
|
2
2
|
module Rails
|
3
|
-
class Server < Sunspot::Server
|
3
|
+
class Server < Sunspot::Solr::Server
|
4
4
|
# ActiveSupport log levels are integers; this array maps them to the
|
5
5
|
# appropriate java.util.logging.Level constant
|
6
6
|
LOG_LEVELS = %w(FINE INFO WARNING SEVERE SEVERE INFO)
|
7
7
|
|
8
|
-
def start
|
9
|
-
bootstrap
|
10
|
-
super
|
11
|
-
end
|
12
|
-
|
13
|
-
def run
|
14
|
-
bootstrap
|
15
|
-
super
|
16
|
-
end
|
17
|
-
|
18
|
-
#
|
19
|
-
# Bootstrap a new solr_home by creating all required
|
20
|
-
# directories.
|
21
|
-
#
|
22
|
-
# ==== Returns
|
23
|
-
#
|
24
|
-
# Boolean:: success
|
25
|
-
#
|
26
|
-
def bootstrap
|
27
|
-
unless @bootstrapped
|
28
|
-
install_solr_home
|
29
|
-
@bootstrapped = true
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
#
|
34
|
-
# Directory to store custom libraries for solr
|
35
|
-
#
|
36
|
-
def lib_path
|
37
|
-
File.join( solr_home, 'lib' )
|
38
|
-
end
|
39
|
-
|
40
8
|
#
|
41
9
|
# Directory in which to store PID files
|
42
10
|
#
|
43
11
|
def pid_dir
|
44
|
-
File.join(::Rails.root, 'tmp', 'pids')
|
12
|
+
configuration.pid_dir || File.join(::Rails.root, 'tmp', 'pids')
|
45
13
|
end
|
46
14
|
|
47
15
|
#
|
@@ -59,7 +27,7 @@ module Sunspot
|
|
59
27
|
# String:: data_path
|
60
28
|
#
|
61
29
|
def solr_data_dir
|
62
|
-
|
30
|
+
configuration.data_path
|
63
31
|
end
|
64
32
|
|
65
33
|
#
|
@@ -76,6 +44,13 @@ module Sunspot
|
|
76
44
|
configuration.solr_jar || super
|
77
45
|
end
|
78
46
|
|
47
|
+
#
|
48
|
+
# Address on which to run Solr
|
49
|
+
#
|
50
|
+
def bind_address
|
51
|
+
configuration.bind_address
|
52
|
+
end
|
53
|
+
|
79
54
|
#
|
80
55
|
# Port on which to run Solr
|
81
56
|
#
|
@@ -126,48 +101,6 @@ module Sunspot
|
|
126
101
|
def configuration
|
127
102
|
Sunspot::Rails.configuration
|
128
103
|
end
|
129
|
-
|
130
|
-
#
|
131
|
-
# Directory to store solr config files
|
132
|
-
#
|
133
|
-
# ==== Returns
|
134
|
-
#
|
135
|
-
# String:: config_path
|
136
|
-
#
|
137
|
-
def config_path
|
138
|
-
File.join(solr_home, 'conf')
|
139
|
-
end
|
140
|
-
|
141
|
-
#
|
142
|
-
# Copy default solr configuration files from sunspot
|
143
|
-
# gem to the new solr_home/config directory
|
144
|
-
#
|
145
|
-
# ==== Returns
|
146
|
-
#
|
147
|
-
# Boolean:: success
|
148
|
-
#
|
149
|
-
def install_solr_home
|
150
|
-
unless File.exists?(solr_home)
|
151
|
-
Sunspot::Installer.execute(
|
152
|
-
solr_home,
|
153
|
-
:force => true,
|
154
|
-
:verbose => true
|
155
|
-
)
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
#
|
160
|
-
# Create new solr_home, config, log and pid directories
|
161
|
-
#
|
162
|
-
# ==== Returns
|
163
|
-
#
|
164
|
-
# Boolean:: success
|
165
|
-
#
|
166
|
-
def create_solr_directories
|
167
|
-
[solr_data_dir, pid_dir].each do |path|
|
168
|
-
FileUtils.mkdir_p( path )
|
169
|
-
end
|
170
|
-
end
|
171
104
|
end
|
172
105
|
end
|
173
106
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Sunspot
|
2
|
+
module Rails
|
3
|
+
module SolrInstrumentation
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
alias_method_chain :execute, :as_instrumentation
|
8
|
+
end
|
9
|
+
|
10
|
+
module InstanceMethods
|
11
|
+
def execute_with_as_instrumentation(path, params={}, *extra)
|
12
|
+
ActiveSupport::Notifications.instrument("request.rsolr",
|
13
|
+
{:path => path, :parameters => params}) do
|
14
|
+
execute_without_as_instrumentation(path, params, *extra)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -1,20 +1,21 @@
|
|
1
1
|
module Sunspot
|
2
2
|
module Rails
|
3
3
|
module SolrLogging
|
4
|
+
|
4
5
|
class <<self
|
5
6
|
def included(base)
|
6
|
-
base.
|
7
|
+
base.alias_method_chain :execute, :rails_logging
|
7
8
|
end
|
8
9
|
end
|
9
10
|
|
10
|
-
|
11
|
+
COMMIT = %r{<commit/>}
|
11
12
|
|
12
|
-
|
13
|
-
body = (
|
14
|
-
action = path
|
15
|
-
if body
|
16
|
-
action =
|
17
|
-
body =
|
13
|
+
def execute_with_rails_logging(client, request_context)
|
14
|
+
body = (request_context[:data]||"").dup
|
15
|
+
action = request_context[:path].capitalize
|
16
|
+
if body =~ COMMIT
|
17
|
+
action = "Commit"
|
18
|
+
body = ""
|
18
19
|
end
|
19
20
|
body = body[0, 800] + '...' if body.length > 800
|
20
21
|
|
@@ -22,7 +23,7 @@ module Sunspot
|
|
22
23
|
response = nil
|
23
24
|
begin
|
24
25
|
ms = Benchmark.ms do
|
25
|
-
response =
|
26
|
+
response = execute_without_rails_logging(client, request_context)
|
26
27
|
end
|
27
28
|
log_name = 'Solr %s (%.1fms)' % [action, ms]
|
28
29
|
::Rails.logger.debug(format_log_entry(log_name, body))
|
@@ -39,10 +40,10 @@ module Sunspot
|
|
39
40
|
|
40
41
|
def format_log_entry(message, dump = nil)
|
41
42
|
@colorize_logging ||= begin
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
43
|
+
::Rails.application.config.colorize_logging # Rails 3
|
44
|
+
rescue NoMethodError
|
45
|
+
ActiveRecord::Base.colorize_logging # Rails 2
|
46
|
+
end
|
46
47
|
if @colorize_logging
|
47
48
|
message_color, dump_color = "4;32;1", "0;1"
|
48
49
|
log_entry = " \e[#{message_color}m#{message}\e[0m "
|
@@ -56,8 +57,6 @@ module Sunspot
|
|
56
57
|
end
|
57
58
|
end
|
58
59
|
|
59
|
-
|
60
|
-
|
61
|
-
rescue NameError # RSolr 0.9.6
|
62
|
-
RSolr::Connection::Base.module_eval { include(Sunspot::Rails::SolrLogging) }
|
60
|
+
RSolr::Connection.module_eval do
|
61
|
+
include Sunspot::Rails::SolrLogging
|
63
62
|
end
|