muck-solr 0.4.0
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.
- data/CHANGE_LOG +239 -0
- data/LICENSE +19 -0
- data/README.markdown +118 -0
- data/README.rdoc +107 -0
- data/Rakefile +99 -0
- data/TESTING_THE_PLUGIN +25 -0
- data/VERSION.yml +4 -0
- data/config/solr.yml +15 -0
- data/config/solr_environment.rb +32 -0
- data/lib/acts_as_solr.rb +65 -0
- data/lib/acts_as_solr/acts_methods.rb +352 -0
- data/lib/acts_as_solr/class_methods.rb +236 -0
- data/lib/acts_as_solr/common_methods.rb +89 -0
- data/lib/acts_as_solr/deprecation.rb +61 -0
- data/lib/acts_as_solr/instance_methods.rb +165 -0
- data/lib/acts_as_solr/lazy_document.rb +18 -0
- data/lib/acts_as_solr/parser_methods.rb +203 -0
- data/lib/acts_as_solr/search_results.rb +68 -0
- data/lib/acts_as_solr/solr_fixtures.rb +13 -0
- data/lib/acts_as_solr/tasks.rb +10 -0
- data/lib/acts_as_solr/tasks/database.rake +16 -0
- data/lib/acts_as_solr/tasks/solr.rake +135 -0
- data/lib/acts_as_solr/tasks/test.rake +5 -0
- data/lib/solr.rb +26 -0
- data/lib/solr/connection.rb +177 -0
- data/lib/solr/document.rb +75 -0
- data/lib/solr/exception.rb +13 -0
- data/lib/solr/field.rb +36 -0
- data/lib/solr/importer.rb +19 -0
- data/lib/solr/importer/array_mapper.rb +26 -0
- data/lib/solr/importer/delimited_file_source.rb +38 -0
- data/lib/solr/importer/hpricot_mapper.rb +27 -0
- data/lib/solr/importer/mapper.rb +51 -0
- data/lib/solr/importer/solr_source.rb +41 -0
- data/lib/solr/importer/xpath_mapper.rb +35 -0
- data/lib/solr/indexer.rb +52 -0
- data/lib/solr/request.rb +26 -0
- data/lib/solr/request/add_document.rb +58 -0
- data/lib/solr/request/base.rb +36 -0
- data/lib/solr/request/commit.rb +29 -0
- data/lib/solr/request/delete.rb +48 -0
- data/lib/solr/request/dismax.rb +46 -0
- data/lib/solr/request/index_info.rb +22 -0
- data/lib/solr/request/modify_document.rb +46 -0
- data/lib/solr/request/optimize.rb +19 -0
- data/lib/solr/request/ping.rb +36 -0
- data/lib/solr/request/select.rb +54 -0
- data/lib/solr/request/spellcheck.rb +30 -0
- data/lib/solr/request/standard.rb +402 -0
- data/lib/solr/request/update.rb +23 -0
- data/lib/solr/response.rb +27 -0
- data/lib/solr/response/add_document.rb +17 -0
- data/lib/solr/response/base.rb +42 -0
- data/lib/solr/response/commit.rb +15 -0
- data/lib/solr/response/delete.rb +13 -0
- data/lib/solr/response/dismax.rb +8 -0
- data/lib/solr/response/index_info.rb +26 -0
- data/lib/solr/response/modify_document.rb +17 -0
- data/lib/solr/response/optimize.rb +14 -0
- data/lib/solr/response/ping.rb +26 -0
- data/lib/solr/response/ruby.rb +42 -0
- data/lib/solr/response/select.rb +17 -0
- data/lib/solr/response/spellcheck.rb +20 -0
- data/lib/solr/response/standard.rb +60 -0
- data/lib/solr/response/xml.rb +39 -0
- data/lib/solr/solrtasks.rb +27 -0
- data/lib/solr/util.rb +32 -0
- data/lib/solr/xml.rb +44 -0
- data/solr/CHANGES.txt +1207 -0
- data/solr/LICENSE.txt +712 -0
- data/solr/NOTICE.txt +90 -0
- data/solr/etc/jetty.xml +205 -0
- data/solr/etc/webdefault.xml +379 -0
- data/solr/lib/easymock.jar +0 -0
- data/solr/lib/jetty-6.1.3.jar +0 -0
- data/solr/lib/jetty-util-6.1.3.jar +0 -0
- data/solr/lib/jsp-2.1/ant-1.6.5.jar +0 -0
- data/solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
- data/solr/lib/jsp-2.1/jsp-2.1.jar +0 -0
- data/solr/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
- data/solr/lib/servlet-api-2.4.jar +0 -0
- data/solr/lib/servlet-api-2.5-6.1.3.jar +0 -0
- data/solr/lib/xpp3-1.1.3.4.O.jar +0 -0
- data/solr/solr/README.txt +52 -0
- data/solr/solr/bin/abc +176 -0
- data/solr/solr/bin/abo +176 -0
- data/solr/solr/bin/backup +108 -0
- data/solr/solr/bin/backupcleaner +142 -0
- data/solr/solr/bin/commit +128 -0
- data/solr/solr/bin/optimize +129 -0
- data/solr/solr/bin/readercycle +129 -0
- data/solr/solr/bin/rsyncd-disable +77 -0
- data/solr/solr/bin/rsyncd-enable +76 -0
- data/solr/solr/bin/rsyncd-start +145 -0
- data/solr/solr/bin/rsyncd-stop +105 -0
- data/solr/solr/bin/scripts-util +83 -0
- data/solr/solr/bin/snapcleaner +148 -0
- data/solr/solr/bin/snapinstaller +168 -0
- data/solr/solr/bin/snappuller +248 -0
- data/solr/solr/bin/snappuller-disable +77 -0
- data/solr/solr/bin/snappuller-enable +77 -0
- data/solr/solr/bin/snapshooter +109 -0
- data/solr/solr/conf/admin-extra.html +31 -0
- data/solr/solr/conf/protwords.txt +21 -0
- data/solr/solr/conf/schema.xml +126 -0
- data/solr/solr/conf/scripts.conf +24 -0
- data/solr/solr/conf/solrconfig.xml +458 -0
- data/solr/solr/conf/stopwords.txt +57 -0
- data/solr/solr/conf/synonyms.txt +31 -0
- data/solr/solr/conf/xslt/example.xsl +132 -0
- data/solr/solr/conf/xslt/example_atom.xsl +63 -0
- data/solr/solr/conf/xslt/example_rss.xsl +62 -0
- data/solr/start.jar +0 -0
- data/solr/webapps/solr.war +0 -0
- data/test/config/solr.yml +2 -0
- data/test/db/connections/mysql/connection.rb +10 -0
- data/test/db/connections/sqlite/connection.rb +8 -0
- data/test/db/migrate/001_create_books.rb +15 -0
- data/test/db/migrate/002_create_movies.rb +12 -0
- data/test/db/migrate/003_create_categories.rb +11 -0
- data/test/db/migrate/004_create_electronics.rb +16 -0
- data/test/db/migrate/005_create_authors.rb +12 -0
- data/test/db/migrate/006_create_postings.rb +9 -0
- data/test/db/migrate/007_create_posts.rb +13 -0
- data/test/db/migrate/008_create_gadgets.rb +11 -0
- data/test/fixtures/authors.yml +9 -0
- data/test/fixtures/books.yml +13 -0
- data/test/fixtures/categories.yml +7 -0
- data/test/fixtures/db_definitions/mysql.sql +41 -0
- data/test/fixtures/electronics.yml +49 -0
- data/test/fixtures/movies.yml +9 -0
- data/test/fixtures/postings.yml +10 -0
- data/test/functional/acts_as_solr_test.rb +413 -0
- data/test/functional/association_indexing_test.rb +37 -0
- data/test/functional/faceted_search_test.rb +163 -0
- data/test/functional/multi_solr_search_test.rb +57 -0
- data/test/models/author.rb +10 -0
- data/test/models/book.rb +10 -0
- data/test/models/category.rb +8 -0
- data/test/models/electronic.rb +25 -0
- data/test/models/gadget.rb +9 -0
- data/test/models/movie.rb +17 -0
- data/test/models/novel.rb +2 -0
- data/test/models/post.rb +3 -0
- data/test/models/posting.rb +11 -0
- data/test/test_helper.rb +54 -0
- data/test/unit/acts_methods_shoulda.rb +68 -0
- data/test/unit/class_methods_shoulda.rb +85 -0
- data/test/unit/common_methods_shoulda.rb +111 -0
- data/test/unit/instance_methods_shoulda.rb +318 -0
- data/test/unit/lazy_document_shoulda.rb +34 -0
- data/test/unit/parser_instance.rb +19 -0
- data/test/unit/parser_methods_shoulda.rb +268 -0
- data/test/unit/solr_instance.rb +49 -0
- data/test/unit/test_helper.rb +24 -0
- metadata +241 -0
data/Rakefile
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'rake'
|
|
3
|
+
require 'rake/testtask'
|
|
4
|
+
require 'rake/rdoctask'
|
|
5
|
+
|
|
6
|
+
Dir["#{File.dirname(__FILE__)}/lib/acts_as_solr/tasks/**/*.rake"].sort.each { |ext| load ext }
|
|
7
|
+
|
|
8
|
+
desc "Default Task"
|
|
9
|
+
task :default => [:test]
|
|
10
|
+
|
|
11
|
+
desc "Runs the unit tests"
|
|
12
|
+
task :test => "test:unit"
|
|
13
|
+
|
|
14
|
+
namespace :test do
|
|
15
|
+
task :setup do
|
|
16
|
+
RAILS_ROOT = File.expand_path("#{File.dirname(__FILE__)}/test") unless defined? RAILS_ROOT
|
|
17
|
+
ENV['RAILS_ENV'] = "test"
|
|
18
|
+
ENV["ACTS_AS_SOLR_TEST"] = "true"
|
|
19
|
+
require File.expand_path("#{File.dirname(__FILE__)}/config/solr_environment")
|
|
20
|
+
puts "Using " + DB
|
|
21
|
+
%x(mysql -u#{MYSQL_USER} < #{File.dirname(__FILE__) + "/test/fixtures/db_definitions/mysql.sql"}) if DB == 'mysql'
|
|
22
|
+
|
|
23
|
+
Rake::Task["test:migrate"].invoke
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
desc 'Measures test coverage using rcov'
|
|
27
|
+
task :rcov => :setup do
|
|
28
|
+
rm_f "coverage"
|
|
29
|
+
rm_f "coverage.data"
|
|
30
|
+
rcov = "rcov --rails --aggregate coverage.data --text-summary -Ilib"
|
|
31
|
+
|
|
32
|
+
system("#{rcov} --html #{Dir.glob('test/**/*_test.rb').join(' ')}")
|
|
33
|
+
system("open coverage/index.html") if PLATFORM['darwin']
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
desc 'Runs the functional tests, testing integration with Solr'
|
|
37
|
+
Rake::TestTask.new('functional' => :setup) do |t|
|
|
38
|
+
t.pattern = "test/functional/*_test.rb"
|
|
39
|
+
t.verbose = true
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
desc "Unit tests"
|
|
43
|
+
Rake::TestTask.new(:unit) do |t|
|
|
44
|
+
t.libs << 'test/unit'
|
|
45
|
+
t.pattern = "test/unit/*_shoulda.rb"
|
|
46
|
+
t.verbose = true
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
Rake::RDocTask.new do |rd|
|
|
51
|
+
rd.main = "README.rdoc"
|
|
52
|
+
rd.rdoc_dir = "rdoc"
|
|
53
|
+
rd.rdoc_files.exclude("lib/solr/**/*.rb", "lib/solr.rb")
|
|
54
|
+
rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
begin
|
|
58
|
+
require 'jeweler'
|
|
59
|
+
Jeweler::Tasks.new do |s|
|
|
60
|
+
s.name = "muck-solr"
|
|
61
|
+
s.summary = "This gem adds full text search capabilities and many other nifty features from Apache�s Solr to any Rails model. I'm currently rearranging the test suite to include a real unit test suite, and adding a few features I need myself."
|
|
62
|
+
s.email = "meyer@paperplanes.de"
|
|
63
|
+
s.homepage = "http://github.com/mattmatt/acts_as_solr"
|
|
64
|
+
s.description = "This gem adds full text search capabilities and many other nifty features from Apache�s Solr to any Rails model. I'm currently rearranging the test suite to include a real unit test suite, and adding a few features I need myself."
|
|
65
|
+
s.authors = ["Mathias Meyer, Joel Duffin, Justin Ball"]
|
|
66
|
+
s.rubyforge_project = 'muck-solr'
|
|
67
|
+
s.files = FileList["[A-Z]*", "{bin,generators,config,lib,solr}/**/*"] +
|
|
68
|
+
FileList["test/**/*"].reject {|f| f.include?("test/log")}.reject {|f| f.include?("test/tmp")}
|
|
69
|
+
end
|
|
70
|
+
rescue LoadError
|
|
71
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# rubyforge tasks
|
|
75
|
+
begin
|
|
76
|
+
require 'rake/contrib/sshpublisher'
|
|
77
|
+
namespace :rubyforge do
|
|
78
|
+
|
|
79
|
+
desc "Release gem and RDoc documentation to RubyForge"
|
|
80
|
+
task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
|
|
81
|
+
|
|
82
|
+
namespace :release do
|
|
83
|
+
desc "Publish RDoc to RubyForge."
|
|
84
|
+
task :docs => [:rdoc] do
|
|
85
|
+
config = YAML.load(
|
|
86
|
+
File.read(File.expand_path('~/.rubyforge/user-config.yml'))
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
host = "#{config['username']}@rubyforge.org"
|
|
90
|
+
remote_dir = "/var/www/gforge-projects/muck-solr/"
|
|
91
|
+
local_dir = 'rdoc'
|
|
92
|
+
|
|
93
|
+
Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
rescue LoadError
|
|
98
|
+
puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
|
|
99
|
+
end
|
data/TESTING_THE_PLUGIN
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
acts_as_solr comes with a quick and fast unit test suite, and with a longer-running
|
|
2
|
+
functional test suite, the latter testing the actual integration with Solr.
|
|
3
|
+
|
|
4
|
+
The unit test suite is written using Shoulda, so make sure you have a recent version
|
|
5
|
+
installed.
|
|
6
|
+
|
|
7
|
+
Running `rake test` or just `rake` will run both test suites. Use `rake test:unit` to
|
|
8
|
+
just run the unit test suite.
|
|
9
|
+
|
|
10
|
+
== How to run functional tests for this plugin:
|
|
11
|
+
To run the acts_as_solr's plugin tests run the following steps:
|
|
12
|
+
|
|
13
|
+
- create a MySQL database called "actsassolr_test" (if you want to use MySQL)
|
|
14
|
+
|
|
15
|
+
- create a new Rails project, if needed (the plugin can only be tested from within a Rails project); move/checkout acts_as_solr into its vendor/plugins/, as usual
|
|
16
|
+
|
|
17
|
+
- copy vendor/plugins/acts_as_solr/config/solr.yml to config/ (the Rails config folder)
|
|
18
|
+
|
|
19
|
+
- rake solr:start RAILS_ENV=test
|
|
20
|
+
|
|
21
|
+
- rake test:functional (Accepts the following arguments: DB=sqlite|mysql and MYSQL_USER=user)
|
|
22
|
+
|
|
23
|
+
== Troubleshooting:
|
|
24
|
+
If for some reason the tests don't run and you get MySQL errors, make sure you edit the MYSQL_USER entry under
|
|
25
|
+
config/environment.rb. It's recommended to create or use a MySQL user with no password.
|
data/VERSION.yml
ADDED
data/config/solr.yml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Config file for the acts_as_solr plugin.
|
|
2
|
+
#
|
|
3
|
+
# If you change the host or port number here, make sure you update
|
|
4
|
+
# them in your Solr config file
|
|
5
|
+
|
|
6
|
+
development:
|
|
7
|
+
url: http://127.0.0.1:8982/solr
|
|
8
|
+
|
|
9
|
+
production:
|
|
10
|
+
url: http://127.0.0.1:8983/solr
|
|
11
|
+
jvm_options: -server -d64 -Xmx1024M -Xms64M
|
|
12
|
+
|
|
13
|
+
test:
|
|
14
|
+
url: http://127.0.0.1:8981/solr
|
|
15
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
ENV['RAILS_ENV'] = (ENV['RAILS_ENV'] || 'development').dup
|
|
2
|
+
# RAILS_ROOT isn't defined yet, so figure it out.
|
|
3
|
+
require "uri"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
dir = File.dirname(__FILE__)
|
|
6
|
+
SOLR_PATH = File.expand_path("#{dir}/../solr") unless defined? SOLR_PATH
|
|
7
|
+
|
|
8
|
+
RAILS_ROOT = File.expand_path("#{File.dirname(__FILE__)}/../test") unless defined? RAILS_ROOT
|
|
9
|
+
unless defined? SOLR_LOGS_PATH
|
|
10
|
+
SOLR_LOGS_PATH = ENV["SOLR_LOGS_PATH"] || "#{RAILS_ROOT}/log"
|
|
11
|
+
end
|
|
12
|
+
unless defined? SOLR_PIDS_PATH
|
|
13
|
+
SOLR_PIDS_PATH = ENV["SOLR_PIDS_PATH"] || "#{RAILS_ROOT}/tmp/pids"
|
|
14
|
+
end
|
|
15
|
+
unless defined? SOLR_DATA_PATH
|
|
16
|
+
SOLR_DATA_PATH = ENV["SOLR_DATA_PATH"] || "#{RAILS_ROOT}/solr/#{ENV['RAILS_ENV']}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
unless defined? SOLR_PORT
|
|
20
|
+
config = YAML::load_file(RAILS_ROOT+'/config/solr.yml')
|
|
21
|
+
raise("No solr environment defined for RAILS_ENV the #{ENV['RAILS_ENV'].inspect}") unless config[ENV['RAILS_ENV']]
|
|
22
|
+
SOLR_PORT = ENV['PORT'] || URI.parse(config[ENV['RAILS_ENV']]['url']).port
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
SOLR_JVM_OPTIONS = config[ENV['RAILS_ENV']]['jvm_options'] unless defined? SOLR_JVM_OPTIONS
|
|
26
|
+
|
|
27
|
+
if ENV["ACTS_AS_SOLR_TEST"]
|
|
28
|
+
require "activerecord"
|
|
29
|
+
DB = (ENV['DB'] ? ENV['DB'] : 'sqlite') unless defined?(DB)
|
|
30
|
+
MYSQL_USER = (ENV['MYSQL_USER'].nil? ? 'root' : ENV['MYSQL_USER']) unless defined? MYSQL_USER
|
|
31
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), '..', 'test', 'db', 'connections', DB, 'connection.rb')
|
|
32
|
+
end
|
data/lib/acts_as_solr.rb
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Copyright (c) 2006 Erik Hatcher, Thiago Jackiw
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
# copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
# SOFTWARE.
|
|
20
|
+
|
|
21
|
+
require 'active_record'
|
|
22
|
+
require 'rexml/document'
|
|
23
|
+
require 'net/http'
|
|
24
|
+
require 'yaml'
|
|
25
|
+
require 'time'
|
|
26
|
+
require 'erb'
|
|
27
|
+
require 'rexml/xpath'
|
|
28
|
+
|
|
29
|
+
require File.dirname(__FILE__) + '/solr'
|
|
30
|
+
require File.dirname(__FILE__) + '/acts_as_solr/acts_methods'
|
|
31
|
+
require File.dirname(__FILE__) + '/acts_as_solr/common_methods'
|
|
32
|
+
require File.dirname(__FILE__) + '/acts_as_solr/parser_methods'
|
|
33
|
+
require File.dirname(__FILE__) + '/acts_as_solr/class_methods'
|
|
34
|
+
require File.dirname(__FILE__) + '/acts_as_solr/instance_methods'
|
|
35
|
+
require File.dirname(__FILE__) + '/acts_as_solr/common_methods'
|
|
36
|
+
require File.dirname(__FILE__) + '/acts_as_solr/deprecation'
|
|
37
|
+
require File.dirname(__FILE__) + '/acts_as_solr/search_results'
|
|
38
|
+
require File.dirname(__FILE__) + '/acts_as_solr/lazy_document'
|
|
39
|
+
module ActsAsSolr
|
|
40
|
+
|
|
41
|
+
class Post
|
|
42
|
+
def self.execute(request, core = nil)
|
|
43
|
+
begin
|
|
44
|
+
if File.exists?(RAILS_ROOT+'/config/solr.yml')
|
|
45
|
+
config = YAML::load_file(RAILS_ROOT+'/config/solr.yml')
|
|
46
|
+
url = config[ENV['RAILS_ENV']]['url']
|
|
47
|
+
# for backwards compatibility
|
|
48
|
+
url ||= "http://#{config[ENV['RAILS_ENV']]['host']}:#{config[ENV['RAILS_ENV']]['port']}/#{config[ENV['RAILS_ENV']]['servlet_path']}"
|
|
49
|
+
else
|
|
50
|
+
url = 'http://localhost:8982/solr'
|
|
51
|
+
end
|
|
52
|
+
url += "/" + core if !core.nil?
|
|
53
|
+
connection = Solr::Connection.new(url)
|
|
54
|
+
return connection.send(request)
|
|
55
|
+
rescue
|
|
56
|
+
raise "Couldn't connect to the Solr server at #{url}. #{$!}"
|
|
57
|
+
false
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# reopen ActiveRecord and include the acts_as_solr method
|
|
65
|
+
ActiveRecord::Base.extend ActsAsSolr::ActsMethods
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
module ActsAsSolr #:nodoc:
|
|
2
|
+
|
|
3
|
+
module ActsMethods
|
|
4
|
+
|
|
5
|
+
# declares a class as solr-searchable
|
|
6
|
+
#
|
|
7
|
+
# ==== options:
|
|
8
|
+
# fields:: This option can be used to specify only the fields you'd
|
|
9
|
+
# like to index. If not given, all the attributes from the
|
|
10
|
+
# class will be indexed. You can also use this option to
|
|
11
|
+
# include methods that should be indexed as fields
|
|
12
|
+
#
|
|
13
|
+
# class Movie < ActiveRecord::Base
|
|
14
|
+
# acts_as_solr :fields => [:name, :description, :current_time]
|
|
15
|
+
# def current_time
|
|
16
|
+
# Time.now.to_s
|
|
17
|
+
# end
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# Each field passed can also be a hash with the value being a field type
|
|
21
|
+
#
|
|
22
|
+
# class Electronic < ActiveRecord::Base
|
|
23
|
+
# acts_as_solr :fields => [{:price => :range_float}]
|
|
24
|
+
# def current_time
|
|
25
|
+
# Time.now
|
|
26
|
+
# end
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# The field types accepted are:
|
|
30
|
+
#
|
|
31
|
+
# :float:: Index the field value as a float (ie.: 12.87)
|
|
32
|
+
# :integer:: Index the field value as an integer (ie.: 31)
|
|
33
|
+
# :boolean:: Index the field value as a boolean (ie.: true/false)
|
|
34
|
+
# :date:: Index the field value as a date (ie.: Wed Nov 15 23:13:03 PST 2006)
|
|
35
|
+
# :string:: Index the field value as a text string, not applying the same indexing
|
|
36
|
+
# filters as a regular text field
|
|
37
|
+
# :range_integer:: Index the field value for integer range queries (ie.:[5 TO 20])
|
|
38
|
+
# :range_float:: Index the field value for float range queries (ie.:[14.56 TO 19.99])
|
|
39
|
+
#
|
|
40
|
+
# Setting the field type preserves its original type when indexed
|
|
41
|
+
#
|
|
42
|
+
# The field may also be passed with a hash value containing options
|
|
43
|
+
#
|
|
44
|
+
# class Author < ActiveRecord::Base
|
|
45
|
+
# acts_as_solr :fields => [{:full_name => {:type => :text, :as => :name}}]
|
|
46
|
+
# def full_name
|
|
47
|
+
# self.first_name + ' ' + self.last_name
|
|
48
|
+
# end
|
|
49
|
+
# end
|
|
50
|
+
#
|
|
51
|
+
# The options accepted are:
|
|
52
|
+
#
|
|
53
|
+
# :type:: Index the field using the specified type
|
|
54
|
+
# :as:: Index the field using the specified field name
|
|
55
|
+
#
|
|
56
|
+
# additional_fields:: This option takes fields to be include in the index
|
|
57
|
+
# in addition to those derived from the database. You
|
|
58
|
+
# can also use this option to include custom fields
|
|
59
|
+
# derived from methods you define. This option will be
|
|
60
|
+
# ignored if the :fields option is given. It also accepts
|
|
61
|
+
# the same field types as the option above
|
|
62
|
+
#
|
|
63
|
+
# class Movie < ActiveRecord::Base
|
|
64
|
+
# acts_as_solr :additional_fields => [:current_time]
|
|
65
|
+
# def current_time
|
|
66
|
+
# Time.now.to_s
|
|
67
|
+
# end
|
|
68
|
+
# end
|
|
69
|
+
#
|
|
70
|
+
# exclude_fields:: This option taks an array of fields that should be ignored from indexing:
|
|
71
|
+
#
|
|
72
|
+
# class User < ActiveRecord::Base
|
|
73
|
+
# acts_as_solr :exclude_fields => [:password, :login, :credit_card_number]
|
|
74
|
+
# end
|
|
75
|
+
#
|
|
76
|
+
# include:: This option can be used for association indexing, which
|
|
77
|
+
# means you can include any :has_one, :has_many, :belongs_to
|
|
78
|
+
# and :has_and_belongs_to_many association to be indexed:
|
|
79
|
+
#
|
|
80
|
+
# class Category < ActiveRecord::Base
|
|
81
|
+
# has_many :books
|
|
82
|
+
# acts_as_solr :include => [:books]
|
|
83
|
+
# end
|
|
84
|
+
#
|
|
85
|
+
# Each association may also be specified as a hash with an option hash as a value
|
|
86
|
+
#
|
|
87
|
+
# class Book < ActiveRecord::Base
|
|
88
|
+
# belongs_to :author
|
|
89
|
+
# has_many :distribution_companies
|
|
90
|
+
# has_many :copyright_dates
|
|
91
|
+
# has_many :media_types
|
|
92
|
+
# acts_as_solr(
|
|
93
|
+
# :fields => [:name, :description],
|
|
94
|
+
# :include => [
|
|
95
|
+
# {:author => {:using => :fullname, :as => :name}},
|
|
96
|
+
# {:media_types => {:using => lambda{|media| type_lookup(media.id)}}}
|
|
97
|
+
# {:distribution_companies => {:as => :distributor, :multivalued => true}},
|
|
98
|
+
# {:copyright_dates => {:as => :copyright, :type => :date}}
|
|
99
|
+
# ]
|
|
100
|
+
# ]
|
|
101
|
+
#
|
|
102
|
+
# The options accepted are:
|
|
103
|
+
#
|
|
104
|
+
# :type:: Index the associated objects using the specified type
|
|
105
|
+
# :as:: Index the associated objects using the specified field name
|
|
106
|
+
# :using:: Index the associated objects using the value returned by the specified method or proc. If a method
|
|
107
|
+
# symbol is supplied, it will be sent to each object to look up the value to index; if a proc is
|
|
108
|
+
# supplied, it will be called once for each object with the object as the only argument
|
|
109
|
+
# :multivalued:: Index the associated objects using one field for each object rather than joining them
|
|
110
|
+
# all into a single field
|
|
111
|
+
#
|
|
112
|
+
# facets:: This option can be used to specify the fields you'd like to
|
|
113
|
+
# index as facet fields
|
|
114
|
+
#
|
|
115
|
+
# class Electronic < ActiveRecord::Base
|
|
116
|
+
# acts_as_solr :facets => [:category, :manufacturer]
|
|
117
|
+
# end
|
|
118
|
+
#
|
|
119
|
+
# boost:: You can pass a boost (float) value that will be used to boost the document and/or a field. To specify a more
|
|
120
|
+
# boost for the document, you can either pass a block or a symbol. The block will be called with the record
|
|
121
|
+
# as an argument, a symbol will result in the according method being called:
|
|
122
|
+
#
|
|
123
|
+
# class Electronic < ActiveRecord::Base
|
|
124
|
+
# acts_as_solr :fields => [{:price => {:boost => 5.0}}], :boost => 10.0
|
|
125
|
+
# end
|
|
126
|
+
#
|
|
127
|
+
# class Electronic < ActiveRecord::Base
|
|
128
|
+
# acts_as_solr :fields => [{:price => {:boost => 5.0}}], :boost => proc {|record| record.id + 120*37}
|
|
129
|
+
# end
|
|
130
|
+
#
|
|
131
|
+
# class Electronic < ActiveRecord::Base
|
|
132
|
+
# acts_as_solr :fields => [{:price => {:boost => :price_rating}}], :boost => 10.0
|
|
133
|
+
# end
|
|
134
|
+
#
|
|
135
|
+
# if:: Only indexes the record if the condition evaluated is true. The argument has to be
|
|
136
|
+
# either a symbol, string (to be eval'ed), proc/method, or class implementing a static
|
|
137
|
+
# validation method. It behaves the same way as ActiveRecord's :if option.
|
|
138
|
+
#
|
|
139
|
+
# class Electronic < ActiveRecord::Base
|
|
140
|
+
# acts_as_solr :if => proc{|record| record.is_active?}
|
|
141
|
+
# end
|
|
142
|
+
#
|
|
143
|
+
# offline:: Assumes that your using an outside mechanism to explicitly trigger indexing records, e.g. you only
|
|
144
|
+
# want to update your index through some asynchronous mechanism. Will accept either a boolean or a block
|
|
145
|
+
# that will be evaluated before actually contacting the index for saving or destroying a document. Defaults
|
|
146
|
+
# to false. It doesn't refer to the mechanism of an offline index in general, but just to get a centralized point
|
|
147
|
+
# where you can control indexing. Note: This is only enabled for saving records. acts_as_solr doesn't always like
|
|
148
|
+
# it, if you have a different number of results coming from the database and the index. This might be rectified in
|
|
149
|
+
# another patch to support lazy loading.
|
|
150
|
+
#
|
|
151
|
+
# class Electronic < ActiveRecord::Base
|
|
152
|
+
# acts_as_solr :offline => proc {|record| record.automatic_indexing_disabled?}
|
|
153
|
+
# end
|
|
154
|
+
#
|
|
155
|
+
# auto_commit:: The commit command will be sent to Solr only if its value is set to true:
|
|
156
|
+
#
|
|
157
|
+
# class Author < ActiveRecord::Base
|
|
158
|
+
# acts_as_solr :auto_commit => false
|
|
159
|
+
# end
|
|
160
|
+
#
|
|
161
|
+
def acts_as_solr(options={}, solr_options={}, &deferred_solr_configuration)
|
|
162
|
+
|
|
163
|
+
extend ClassMethods
|
|
164
|
+
include InstanceMethods
|
|
165
|
+
include CommonMethods
|
|
166
|
+
include ParserMethods
|
|
167
|
+
|
|
168
|
+
define_solr_configuration_methods
|
|
169
|
+
|
|
170
|
+
after_save :solr_save
|
|
171
|
+
after_destroy :solr_destroy
|
|
172
|
+
|
|
173
|
+
if deferred_solr_configuration
|
|
174
|
+
self.deferred_solr_configuration = deferred_solr_configuration
|
|
175
|
+
else
|
|
176
|
+
process_acts_as_solr(options, solr_options)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def process_acts_as_solr(options, solr_options)
|
|
181
|
+
process_solr_options(options, solr_options)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def define_solr_configuration_methods
|
|
185
|
+
# I'd like to use cattr_accessor, but it does not support lazy loaders and delegation to the class in the instance methods.
|
|
186
|
+
# TODO: Reconcile with cattr_accessor, or a more appropriate method.
|
|
187
|
+
class_eval(<<-EOS, __FILE__, __LINE__)
|
|
188
|
+
@@configuration = nil unless defined?(@@configuration)
|
|
189
|
+
@@solr_configuration = nil unless defined?(@@solr_configuration)
|
|
190
|
+
@@deferred_solr_configuration = nil unless defined?(@@deferred_solr_configuration)
|
|
191
|
+
|
|
192
|
+
def self.configuration
|
|
193
|
+
return @@configuration if @@configuration
|
|
194
|
+
process_deferred_solr_configuration
|
|
195
|
+
@@configuration
|
|
196
|
+
end
|
|
197
|
+
def configuration
|
|
198
|
+
self.class.configuration
|
|
199
|
+
end
|
|
200
|
+
def self.configuration=(value)
|
|
201
|
+
@@configuration = value
|
|
202
|
+
end
|
|
203
|
+
def configuration=(value)
|
|
204
|
+
self.class.configuration = value
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def self.solr_configuration
|
|
208
|
+
return @@solr_configuration if @@solr_configuration
|
|
209
|
+
process_deferred_solr_configuration
|
|
210
|
+
@@solr_configuration
|
|
211
|
+
end
|
|
212
|
+
def solr_configuration
|
|
213
|
+
self.class.solr_configuration
|
|
214
|
+
end
|
|
215
|
+
def self.solr_configuration=(value)
|
|
216
|
+
@@solr_configuration = value
|
|
217
|
+
end
|
|
218
|
+
def solr_configuration=(value)
|
|
219
|
+
self.class.solr_configuration = value
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def self.deferred_solr_configuration
|
|
223
|
+
return @@deferred_solr_configuration if @@deferred_solr_configuration
|
|
224
|
+
@@deferred_solr_configuration
|
|
225
|
+
end
|
|
226
|
+
def deferred_solr_configuration
|
|
227
|
+
self.class.deferred_solr_configuration
|
|
228
|
+
end
|
|
229
|
+
def self.deferred_solr_configuration=(value)
|
|
230
|
+
@@deferred_solr_configuration = value
|
|
231
|
+
end
|
|
232
|
+
def deferred_solr_configuration=(value)
|
|
233
|
+
self.class.deferred_solr_configuration = value
|
|
234
|
+
end
|
|
235
|
+
EOS
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def process_deferred_solr_configuration
|
|
239
|
+
return unless deferred_solr_configuration
|
|
240
|
+
options, solr_options = deferred_solr_configuration.call
|
|
241
|
+
self.deferred_solr_configuration = nil
|
|
242
|
+
self.process_solr_options(options, solr_options)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def process_solr_options(options={}, solr_options={})
|
|
246
|
+
self.configuration = {
|
|
247
|
+
:fields => nil,
|
|
248
|
+
:additional_fields => nil,
|
|
249
|
+
:exclude_fields => [],
|
|
250
|
+
:auto_commit => true,
|
|
251
|
+
:include => nil,
|
|
252
|
+
:facets => nil,
|
|
253
|
+
:boost => nil,
|
|
254
|
+
:if => "true",
|
|
255
|
+
:offline => false
|
|
256
|
+
}
|
|
257
|
+
self.solr_configuration = {
|
|
258
|
+
:type_field => "type_s",
|
|
259
|
+
:primary_key_field => "pk_i",
|
|
260
|
+
:default_boost => 1.0
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
configuration.update(options) if options.is_a?(Hash)
|
|
264
|
+
solr_configuration.update(solr_options) if solr_options.is_a?(Hash)
|
|
265
|
+
Deprecation.validate_index(configuration)
|
|
266
|
+
|
|
267
|
+
configuration[:solr_fields] = {}
|
|
268
|
+
configuration[:solr_includes] = {}
|
|
269
|
+
|
|
270
|
+
if configuration[:fields].respond_to?(:each)
|
|
271
|
+
process_fields(configuration[:fields])
|
|
272
|
+
else
|
|
273
|
+
process_fields(self.new.attributes.keys.map { |k| k.to_sym })
|
|
274
|
+
process_fields(configuration[:additional_fields])
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
if configuration[:include].respond_to?(:each)
|
|
278
|
+
process_includes(configuration[:include])
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
private
|
|
283
|
+
|
|
284
|
+
def get_field_value(field)
|
|
285
|
+
field_name, options = determine_field_name_and_options(field)
|
|
286
|
+
configuration[:solr_fields][field_name] = options
|
|
287
|
+
|
|
288
|
+
define_method("#{field_name}_for_solr".to_sym) do
|
|
289
|
+
begin
|
|
290
|
+
value = self[field_name] || self.instance_variable_get("@#{field_name.to_s}".to_sym) || self.send(field_name.to_sym)
|
|
291
|
+
case options[:type]
|
|
292
|
+
# format dates properly; return nil for nil dates
|
|
293
|
+
when :date
|
|
294
|
+
value ? (value.respond_to?(:utc) ? value.utc : value).strftime("%Y-%m-%dT%H:%M:%SZ") : nil
|
|
295
|
+
else value
|
|
296
|
+
end
|
|
297
|
+
rescue
|
|
298
|
+
puts $!
|
|
299
|
+
logger.debug "There was a problem getting the value for the field '#{field_name}': #{$!}"
|
|
300
|
+
value = ''
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def process_fields(raw_field)
|
|
306
|
+
if raw_field.respond_to?(:each)
|
|
307
|
+
raw_field.each do |field|
|
|
308
|
+
next if configuration[:exclude_fields].include?(field)
|
|
309
|
+
get_field_value(field)
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def process_includes(includes)
|
|
315
|
+
if includes.respond_to?(:each)
|
|
316
|
+
includes.each do |assoc|
|
|
317
|
+
field_name, options = determine_field_name_and_options(assoc)
|
|
318
|
+
configuration[:solr_includes][field_name] = options
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def determine_field_name_and_options(field)
|
|
324
|
+
if field.is_a?(Hash)
|
|
325
|
+
name = field.keys.first
|
|
326
|
+
options = field.values.first
|
|
327
|
+
if options.is_a?(Hash)
|
|
328
|
+
[name, {:type => type_for_field(field)}.merge(options)]
|
|
329
|
+
else
|
|
330
|
+
[name, {:type => options}]
|
|
331
|
+
end
|
|
332
|
+
else
|
|
333
|
+
[field, {:type => type_for_field(field)}]
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def type_for_field(field)
|
|
338
|
+
if configuration[:facets] && configuration[:facets].include?(field)
|
|
339
|
+
:facet
|
|
340
|
+
elsif column = columns_hash[field.to_s]
|
|
341
|
+
case column.type
|
|
342
|
+
when :string then :text
|
|
343
|
+
when :datetime then :date
|
|
344
|
+
when :time then :date
|
|
345
|
+
else column.type
|
|
346
|
+
end
|
|
347
|
+
else
|
|
348
|
+
:text
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|