erichummel-sunspot_rails 1.2.1a

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/History.txt +51 -0
  2. data/LICENSE +18 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +258 -0
  5. data/Rakefile +18 -0
  6. data/TESTING.md +35 -0
  7. data/TODO +8 -0
  8. data/VERSION.yml +4 -0
  9. data/dev_tasks/rdoc.rake +24 -0
  10. data/dev_tasks/release.rake +4 -0
  11. data/dev_tasks/spec.rake +22 -0
  12. data/dev_tasks/todo.rake +4 -0
  13. data/generators/sunspot/sunspot_generator.rb +9 -0
  14. data/generators/sunspot/templates/sunspot.yml +18 -0
  15. data/install.rb +1 -0
  16. data/lib/generators/sunspot_rails/install/install_generator.rb +13 -0
  17. data/lib/generators/sunspot_rails/install/templates/config/sunspot.yml +17 -0
  18. data/lib/generators/sunspot_rails.rb +9 -0
  19. data/lib/sunspot/rails/adapters.rb +83 -0
  20. data/lib/sunspot/rails/configuration.rb +323 -0
  21. data/lib/sunspot/rails/init.rb +5 -0
  22. data/lib/sunspot/rails/log_subscriber.rb +33 -0
  23. data/lib/sunspot/rails/railtie.rb +36 -0
  24. data/lib/sunspot/rails/railties/controller_runtime.rb +36 -0
  25. data/lib/sunspot/rails/request_lifecycle.rb +36 -0
  26. data/lib/sunspot/rails/searchable.rb +412 -0
  27. data/lib/sunspot/rails/server.rb +173 -0
  28. data/lib/sunspot/rails/solr_instrumentation.rb +21 -0
  29. data/lib/sunspot/rails/solr_logging.rb +63 -0
  30. data/lib/sunspot/rails/spec_helper.rb +26 -0
  31. data/lib/sunspot/rails/stub_session_proxy.rb +88 -0
  32. data/lib/sunspot/rails/tasks.rb +62 -0
  33. data/lib/sunspot/rails/version.rb +5 -0
  34. data/lib/sunspot/rails.rb +59 -0
  35. data/lib/sunspot_rails.rb +12 -0
  36. data/spec/configuration_spec.rb +173 -0
  37. data/spec/model_lifecycle_spec.rb +63 -0
  38. data/spec/model_spec.rb +356 -0
  39. data/spec/request_lifecycle_spec.rb +61 -0
  40. data/spec/schema.rb +27 -0
  41. data/spec/server_spec.rb +37 -0
  42. data/spec/session_spec.rb +24 -0
  43. data/spec/spec_helper.rb +46 -0
  44. data/spec/stub_session_proxy_spec.rb +122 -0
  45. metadata +155 -0
@@ -0,0 +1,323 @@
1
+ require 'erb'
2
+
3
+ module Sunspot #:nodoc:
4
+ module Rails #:nodoc:
5
+ #
6
+ # Sunspot::Rails is configured via the config/sunspot.yml file, which
7
+ # contains properties keyed by environment name. A sample sunspot.yml file
8
+ # would look like:
9
+ #
10
+ # development:
11
+ # solr:
12
+ # hostname: localhost
13
+ # port: 8982
14
+ # min_memory: 512M
15
+ # max_memory: 1G
16
+ # solr_jar: /some/path/solr15/start.jar
17
+ # test:
18
+ # solr:
19
+ # hostname: localhost
20
+ # port: 8983
21
+ # log_level: OFF
22
+ # production:
23
+ # solr:
24
+ # hostname: localhost
25
+ # port: 8983
26
+ # path: /solr/myindex
27
+ # log_level: WARNING
28
+ # solr_home: /some/path
29
+ # master_solr:
30
+ # hostname: localhost
31
+ # port: 8982
32
+ # path: /solr
33
+ # auto_commit_after_request: true
34
+ #
35
+ # Sunspot::Rails uses the configuration to set up the Solr connection, as
36
+ # well as for starting Solr with the appropriate port using the
37
+ # <code>rake sunspot:solr:start</code> task.
38
+ #
39
+ # If the <code>master_solr</code> configuration is present, Sunspot will use
40
+ # the Solr instance specified here for all write operations, and the Solr
41
+ # configured under <code>solr</code> for all read operations.
42
+ #
43
+ class Configuration
44
+ attr_writer :user_configuration
45
+ #
46
+ # The host name at which to connect to Solr. Default 'localhost'.
47
+ #
48
+ # ==== Returns
49
+ #
50
+ # String:: host name
51
+ #
52
+ def hostname
53
+ unless defined?(@hostname)
54
+ @hostname = solr_url.host if solr_url
55
+ @hostname ||= user_configuration_from_key('solr', 'hostname')
56
+ @hostname ||= default_hostname
57
+ end
58
+ @hostname
59
+ end
60
+
61
+ #
62
+ # The port at which to connect to Solr.
63
+ # Defaults to 8981 in test, 8982 in development and 8983 in production.
64
+ #
65
+ # ==== Returns
66
+ #
67
+ # Integer:: port
68
+ #
69
+ def port
70
+ unless defined?(@port)
71
+ @port = solr_url.port if solr_url
72
+ @port ||= user_configuration_from_key('solr', 'port')
73
+ @port ||= default_port
74
+ @port = @port.to_i
75
+ end
76
+ @port
77
+ end
78
+
79
+ #
80
+ # The url path to the Solr servlet (useful if you are running multicore).
81
+ # Default '/solr'.
82
+ #
83
+ # ==== Returns
84
+ #
85
+ # String:: path
86
+ #
87
+ def path
88
+ unless defined?(@path)
89
+ @path = solr_url.path if solr_url
90
+ @path ||= user_configuration_from_key('solr', 'path')
91
+ @path ||= default_path
92
+ end
93
+ @path
94
+ end
95
+
96
+ #
97
+ # The host name at which to connect to the master Solr instance. Defaults
98
+ # to the 'hostname' configuration option.
99
+ #
100
+ # ==== Returns
101
+ #
102
+ # String:: host name
103
+ #
104
+ def master_hostname
105
+ @master_hostname ||= (user_configuration_from_key('master_solr', 'hostname') || hostname)
106
+ end
107
+
108
+ #
109
+ # The port at which to connect to the master Solr instance. Defaults to
110
+ # the 'port' configuration option.
111
+ #
112
+ # ==== Returns
113
+ #
114
+ # Integer:: port
115
+ #
116
+ def master_port
117
+ @master_port ||= (user_configuration_from_key('master_solr', 'port') || port).to_i
118
+ end
119
+
120
+ #
121
+ # The path to the master Solr servlet (useful if you are running multicore).
122
+ # Defaults to the value of the 'path' configuration option.
123
+ #
124
+ # ==== Returns
125
+ #
126
+ # String:: path
127
+ #
128
+ def master_path
129
+ @master_path ||= (user_configuration_from_key('master_solr', 'path') || path)
130
+ end
131
+
132
+ #
133
+ # True if there is a master Solr instance configured, otherwise false.
134
+ #
135
+ # ==== Returns
136
+ #
137
+ # Boolean:: bool
138
+ #
139
+ def has_master?
140
+ @has_master = !!user_configuration_from_key('master_solr')
141
+ end
142
+
143
+ #
144
+ # The default log_level that should be passed to solr. You can
145
+ # change the individual log_levels in the solr admin interface.
146
+ # Default 'INFO'.
147
+ #
148
+ # ==== Returns
149
+ #
150
+ # String:: log_level
151
+ #
152
+ def log_level
153
+ @log_level ||= (user_configuration_from_key('solr', 'log_level') || 'INFO')
154
+ end
155
+
156
+ #
157
+ # Should the solr index receive a commit after each http-request.
158
+ # Default true
159
+ #
160
+ # ==== Returns
161
+ #
162
+ # Boolean: auto_commit_after_request?
163
+ #
164
+ def auto_commit_after_request?
165
+ @auto_commit_after_request ||=
166
+ user_configuration_from_key('auto_commit_after_request') != false
167
+ end
168
+
169
+ #
170
+ # As for #auto_commit_after_request? but only for deletes
171
+ # Default false
172
+ #
173
+ # ==== Returns
174
+ #
175
+ # Boolean: auto_commit_after_delete_request?
176
+ #
177
+ def auto_commit_after_delete_request?
178
+ @auto_commit_after_delete_request ||=
179
+ (user_configuration_from_key('auto_commit_after_delete_request') || false)
180
+ end
181
+
182
+
183
+ #
184
+ # The log directory for solr logfiles
185
+ #
186
+ # ==== Returns
187
+ #
188
+ # String:: log_dir
189
+ #
190
+ def log_file
191
+ @log_file ||= (user_configuration_from_key('solr', 'log_file') || default_log_file_location )
192
+ end
193
+
194
+ def data_path
195
+ @data_path ||= user_configuration_from_key('solr', 'data_path') || File.join(::Rails.root, 'solr', 'data', ::Rails.env)
196
+ end
197
+
198
+ def pid_dir
199
+ @pid_dir ||= user_configuration_from_key('solr', 'pid_dir') || File.join(::Rails.root, 'solr', 'pids', ::Rails.env)
200
+ end
201
+
202
+
203
+ #
204
+ # The solr home directory. Sunspot::Rails expects this directory
205
+ # to contain a config, data and pids directory. See
206
+ # Sunspot::Rails::Server.bootstrap for more information.
207
+ #
208
+ # ==== Returns
209
+ #
210
+ # String:: solr_home
211
+ #
212
+ def solr_home
213
+ @solr_home ||=
214
+ if user_configuration_from_key('solr', 'solr_home')
215
+ user_configuration_from_key('solr', 'solr_home')
216
+ else
217
+ File.join(::Rails.root, 'solr')
218
+ end
219
+ end
220
+
221
+ #
222
+ # Solr start jar
223
+ #
224
+ def solr_jar
225
+ @solr_jar ||= user_configuration_from_key('solr', 'solr_jar')
226
+ end
227
+
228
+ #
229
+ # Minimum java heap size for Solr instance
230
+ #
231
+ def min_memory
232
+ @min_memory ||= user_configuration_from_key('solr', 'min_memory')
233
+ end
234
+
235
+ #
236
+ # Maximum java heap size for Solr instance
237
+ #
238
+ def max_memory
239
+ @max_memory ||= user_configuration_from_key('solr', 'max_memory')
240
+ end
241
+
242
+ private
243
+
244
+ #
245
+ # Logging in rails_root/log as solr_<environment>.log as a
246
+ # default.
247
+ #
248
+ # ===== Returns
249
+ #
250
+ # String:: default_log_file_location
251
+ #
252
+ def default_log_file_location
253
+ File.join(::Rails.root, 'log', "solr_" + ::Rails.env + ".log")
254
+ end
255
+
256
+ #
257
+ # return a specific key from the user configuration in config/sunspot.yml
258
+ #
259
+ # ==== Returns
260
+ #
261
+ # Mixed:: requested_key or nil
262
+ #
263
+ def user_configuration_from_key( *keys )
264
+ keys.inject(user_configuration) do |hash, key|
265
+ hash[key] if hash
266
+ end
267
+ end
268
+
269
+ #
270
+ # Memoized hash of configuration options for the current Rails environment
271
+ # as specified in config/sunspot.yml
272
+ #
273
+ # ==== Returns
274
+ #
275
+ # Hash:: configuration options for current environment
276
+ #
277
+ def user_configuration
278
+ @user_configuration ||=
279
+ begin
280
+ path = File.join(::Rails.root, 'config', 'sunspot.yml')
281
+ if File.exist?(path)
282
+ File.open(path) do |file|
283
+ processed = ERB.new(file.read).result
284
+ YAML.load(processed)[::Rails.env]
285
+ end
286
+ else
287
+ {}
288
+ end
289
+ end
290
+ end
291
+
292
+ protected
293
+
294
+ #
295
+ # When a specific hostname, port and path aren't provided in the
296
+ # sunspot.yml file, look for a key named 'url', then check the
297
+ # environment, then fall back to a sensible localhost default.
298
+ #
299
+
300
+ def solr_url
301
+ if ENV['SOLR_URL'] || ENV['WEBSOLR_URL']
302
+ URI.parse(ENV['SOLR_URL'] || ENV['WEBSOLR_URL'])
303
+ end
304
+ end
305
+
306
+ def default_hostname
307
+ 'localhost'
308
+ end
309
+
310
+ def default_port
311
+ { 'test' => 8981,
312
+ 'development' => 8982,
313
+ 'production' => 8983
314
+ }[::Rails.env] || 8983
315
+ end
316
+
317
+ def default_path
318
+ '/solr'
319
+ end
320
+
321
+ end
322
+ end
323
+ end
@@ -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::Client.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