rsolr 0.11.0 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +7 -0
- data/LICENSE +1 -1
- data/README.rdoc +1 -23
- data/lib/rsolr.rb +4 -25
- data/lib/rsolr/client.rb +5 -5
- data/lib/rsolr/connection.rb +4 -67
- data/lib/rsolr/connection/net_http.rb +1 -41
- data/lib/rsolr/connection/requestable.rb +43 -0
- data/lib/rsolr/connection/utils.rb +73 -0
- data/lib/rsolr/message.rb +4 -150
- data/lib/rsolr/message/document.rb +48 -0
- data/lib/rsolr/message/field.rb +20 -0
- data/lib/rsolr/message/generator.rb +89 -0
- data/rsolr.gemspec +7 -4
- metadata +7 -4
- data/lib/rsolr/connection/direct.rb +0 -69
- data/lib/xout.rb +0 -87
data/CHANGES.txt
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
0.12.0 - February 3, 2010
|
2
|
+
Removed adapters for xml and connections (these will be provided via separate gems)
|
3
|
+
- default xml generator is Builder
|
4
|
+
- default http connection is Net::Http
|
5
|
+
Removed JRuby/Direct connection stuff (this will be in a new library)
|
6
|
+
Updated specs
|
7
|
+
|
1
8
|
0.11.0 - November 17, 2009
|
2
9
|
Removed pagination feature yet again... keeping it in RSolr::Ext until a better API can be thought up.
|
3
10
|
Updated Xout with fixed version - thanks to Mat Brown
|
data/LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -13,6 +13,7 @@ A Ruby client for Apache Solr. RSolr has been developed to be simple and extenda
|
|
13
13
|
* {Sunspot}[http://github.com/outoftime/sunspot] -- an awesome Solr DSL, built with RSolr
|
14
14
|
* {Blacklight}[http://blacklightopac.org] -- a next generation Library OPAC, built with RSolr
|
15
15
|
* {solr-ruby}[http://wiki.apache.org/solr/solr-ruby] -- the original Solr Ruby Gem
|
16
|
+
* {java_bin}[http://github.com/kennyj/java_bin] -- Provides javabin/binary parsing for Ruby
|
16
17
|
|
17
18
|
== Simple usage:
|
18
19
|
require 'rubygems'
|
@@ -27,29 +28,6 @@ A Ruby client for Apache Solr. RSolr has been developed to be simple and extenda
|
|
27
28
|
|
28
29
|
# alternative to above:
|
29
30
|
response = rsolr.catalog :q=>'*:*'
|
30
|
-
|
31
|
-
To use a DirectSolrConnection (no http) in JRuby:
|
32
|
-
|
33
|
-
# "apache-solr" should be a path to a solr build.
|
34
|
-
Dir['apache-solr/dist/*.jar'].each{|jar|require jar}
|
35
|
-
Dir['apache-solr/lib/*.jar'].each{|jar|require jar}
|
36
|
-
|
37
|
-
opts = {:home_dir=>'/path/to/solr/home'}
|
38
|
-
|
39
|
-
# note: you'll have to close the direct connection yourself unless using a block.
|
40
|
-
solr = RSolr.direct_connect(opts)
|
41
|
-
solr.select :q=>'*:*'
|
42
|
-
solr.connection.close
|
43
|
-
|
44
|
-
# OR using a block for automatic connection closing:
|
45
|
-
RSolr.direct_connect opts do |solr|
|
46
|
-
solr.select :q=>'*:*'
|
47
|
-
end
|
48
|
-
|
49
|
-
In general, the direct connection is less than ideal in most applications. You'll be missing out on Http caching, and it'll be impossible to do distributed searches. The direct connection could possibly come in handy though, for quickly indexing large numbers of documents.
|
50
|
-
|
51
|
-
For more information about DirectSolrConnection, see the {API}[http://lucene.apache.org/solr/api/org/apache/solr/servlet/DirectSolrConnection.html].
|
52
|
-
|
53
31
|
|
54
32
|
== Querying
|
55
33
|
Use the #select method to send requests to the /select handler:
|
data/lib/rsolr.rb
CHANGED
@@ -1,38 +1,17 @@
|
|
1
|
-
# add this directory to the load path if it hasn't already been added
|
2
1
|
|
3
2
|
require 'rubygems'
|
4
|
-
|
5
|
-
$: << File.dirname(__FILE__) unless $:.include?(File.dirname(__FILE__))
|
6
|
-
|
7
|
-
require 'xout'
|
3
|
+
$:.unshift File.dirname(__FILE__) unless $:.include?(File.dirname(__FILE__))
|
8
4
|
|
9
5
|
module RSolr
|
10
6
|
|
11
|
-
VERSION = '0.
|
7
|
+
VERSION = '0.12.0'
|
12
8
|
|
13
9
|
autoload :Message, 'rsolr/message'
|
14
10
|
autoload :Client, 'rsolr/client'
|
15
11
|
autoload :Connection, 'rsolr/connection'
|
16
12
|
|
17
|
-
|
18
|
-
|
19
|
-
# RSolr.connect 'http://solr.web100.org'
|
20
|
-
def self.connect *args
|
21
|
-
Client.new(Connection::NetHttp.new(*args))
|
22
|
-
end
|
23
|
-
|
24
|
-
# DirectSolrConnection (jruby only). Example:
|
25
|
-
# RSolr.direct_connect 'path/to/solr/distribution'
|
26
|
-
# RSolr.direct_connect :dist_dir=>'path/to/solr/distribution', :home_dir=>'/path/to/solrhome'
|
27
|
-
# RSolr.direct_connect opts do |rsolr|
|
28
|
-
# ###
|
29
|
-
# end
|
30
|
-
# Note:
|
31
|
-
# if a block is used, the client is yielded and the solr core will be closed for you.
|
32
|
-
# if a block is NOT used, the the client is returned and the core is NOT closed.
|
33
|
-
def self.direct_connect *args, &blk
|
34
|
-
rsolr = Client.new(Connection::Direct.new(*args))
|
35
|
-
block_given? ? (yield rsolr and rsolr.connection.close) : rsolr
|
13
|
+
def self.connect opts={}
|
14
|
+
Client.new Connection::NetHttp.new(opts)
|
36
15
|
end
|
37
16
|
|
38
17
|
# A module that contains string related methods
|
data/lib/rsolr/client.rb
CHANGED
@@ -76,9 +76,9 @@ class RSolr::Client
|
|
76
76
|
update message.delete_by_query(query)
|
77
77
|
end
|
78
78
|
|
79
|
-
# shortcut to RSolr::Message::
|
80
|
-
def message
|
81
|
-
@message ||= RSolr::Message::
|
79
|
+
# shortcut to RSolr::Message::Generator
|
80
|
+
def message *opts
|
81
|
+
@message ||= RSolr::Message::Generator.new
|
82
82
|
end
|
83
83
|
|
84
84
|
protected
|
@@ -95,7 +95,7 @@ class RSolr::Client
|
|
95
95
|
# : body - the raw response body from the solr server
|
96
96
|
# This method will evaluate the :body value if the params[:wt] == :ruby
|
97
97
|
# otherwise, the body is returned
|
98
|
-
# The return object has a special method attached called #
|
98
|
+
# The return object has a special method attached called #raw
|
99
99
|
# This method gives you access to the original response from the connection,
|
100
100
|
# so you can access things like the actual :url sent to solr,
|
101
101
|
# the raw :body, original :params and original :data
|
@@ -105,7 +105,7 @@ class RSolr::Client
|
|
105
105
|
if connection_response[:params][:wt] == :ruby
|
106
106
|
data = Kernel.eval(data)
|
107
107
|
end
|
108
|
-
# attach a method called #
|
108
|
+
# attach a method called #raw that returns the original connection response value
|
109
109
|
def data.raw; @raw end
|
110
110
|
data.send(:instance_variable_set, '@raw', connection_response)
|
111
111
|
data
|
data/lib/rsolr/connection.rb
CHANGED
@@ -1,72 +1,9 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
1
3
|
module RSolr::Connection
|
2
4
|
|
3
|
-
autoload :Direct, 'rsolr/connection/direct'
|
4
5
|
autoload :NetHttp, 'rsolr/connection/net_http'
|
5
|
-
|
6
|
-
|
7
|
-
module Utils
|
8
|
-
|
9
|
-
# Performs URI escaping so that you can construct proper
|
10
|
-
# query strings faster. Use this rather than the cgi.rb
|
11
|
-
# version since it's faster. (Stolen from Rack).
|
12
|
-
def escape(s)
|
13
|
-
s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
|
14
|
-
#'%'+$1.unpack('H2'*$1.size).join('%').upcase
|
15
|
-
'%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
|
16
|
-
}.tr(' ', '+')
|
17
|
-
end
|
18
|
-
|
19
|
-
# Return the bytesize of String; uses String#length under Ruby 1.8 and
|
20
|
-
# String#bytesize under 1.9.
|
21
|
-
if ''.respond_to?(:bytesize)
|
22
|
-
def bytesize(string)
|
23
|
-
string.bytesize
|
24
|
-
end
|
25
|
-
else
|
26
|
-
def bytesize(string)
|
27
|
-
string.size
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
# creates and returns a url as a string
|
32
|
-
# "url" is the base url
|
33
|
-
# "params" is an optional hash of GET style query params
|
34
|
-
# "string_query" is an extra query string that will be appended to the
|
35
|
-
# result of "url" and "params".
|
36
|
-
def build_url url='', params={}, string_query=''
|
37
|
-
queries = [string_query, hash_to_query(params)]
|
38
|
-
queries.delete_if{|i| i.to_s.empty?}
|
39
|
-
url += "?#{queries.join('&')}" unless queries.empty?
|
40
|
-
url
|
41
|
-
end
|
42
|
-
|
43
|
-
# converts a key value pair to an escaped string:
|
44
|
-
# Example:
|
45
|
-
# build_param(:id, 1) == "id=1"
|
46
|
-
def build_param(k,v)
|
47
|
-
"#{escape(k)}=#{escape(v)}"
|
48
|
-
end
|
49
|
-
|
50
|
-
#
|
51
|
-
# converts hash into URL query string, keys get an alpha sort
|
52
|
-
# if a value is an array, the array values get mapped to the same key:
|
53
|
-
# hash_to_query(:q=>'blah', :fq=>['blah', 'blah'], :facet=>{:field=>['location_facet', 'format_facet']})
|
54
|
-
# returns:
|
55
|
-
# ?q=blah&fq=blah&fq=blah&facet.field=location_facet&facet.field=format.facet
|
56
|
-
#
|
57
|
-
# if a value is empty/nil etc., it is not added
|
58
|
-
def hash_to_query(params)
|
59
|
-
mapped = params.map do |k, v|
|
60
|
-
next if v.to_s.empty?
|
61
|
-
if v.class == Array
|
62
|
-
hash_to_query(v.map { |x| [k, x] })
|
63
|
-
else
|
64
|
-
build_param k, v
|
65
|
-
end
|
66
|
-
end
|
67
|
-
mapped.compact.join("&")
|
68
|
-
end
|
69
|
-
|
70
|
-
end
|
6
|
+
autoload :Utils, 'rsolr/connection/utils'
|
7
|
+
autoload :Requestable, 'rsolr/connection/requestable'
|
71
8
|
|
72
9
|
end
|
@@ -5,40 +5,7 @@ require 'net/http'
|
|
5
5
|
#
|
6
6
|
class RSolr::Connection::NetHttp
|
7
7
|
|
8
|
-
include RSolr::Connection::
|
9
|
-
|
10
|
-
attr_reader :opts, :uri
|
11
|
-
|
12
|
-
# opts can have:
|
13
|
-
# :url => 'http://localhost:8080/solr'
|
14
|
-
def initialize opts={}
|
15
|
-
opts[:url] ||= 'http://127.0.0.1:8983/solr'
|
16
|
-
@opts = opts
|
17
|
-
@uri = URI.parse opts[:url]
|
18
|
-
end
|
19
|
-
|
20
|
-
# send a request to the connection
|
21
|
-
# request '/update', :wt=>:xml, '</commit>'
|
22
|
-
def request path, params={}, *extra
|
23
|
-
opts = extra[-1].kind_of?(Hash) ? extra.pop : {}
|
24
|
-
data = extra[0]
|
25
|
-
# force a POST, use the query string as the POST body
|
26
|
-
if opts[:method] == :post and data.to_s.empty?
|
27
|
-
http_context = self.post(path, hash_to_query(params), {}, {'Content-Type' => 'application/x-www-form-urlencoded'})
|
28
|
-
else
|
29
|
-
if data
|
30
|
-
# standard POST, using "data" as the POST body
|
31
|
-
http_context = self.post(path, data, params, {"Content-Type" => 'text/xml; charset=utf-8'})
|
32
|
-
else
|
33
|
-
# standard GET
|
34
|
-
http_context = self.get(path, params)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
raise RSolr::RequestError.new("Solr Response: #{http_context[:message]}") unless http_context[:status_code] == 200
|
38
|
-
http_context
|
39
|
-
end
|
40
|
-
|
41
|
-
protected
|
8
|
+
include RSolr::Connection::Requestable
|
42
9
|
|
43
10
|
def connection
|
44
11
|
@connection ||= Net::HTTP.new(@uri.host, @uri.port)
|
@@ -72,13 +39,6 @@ class RSolr::Connection::NetHttp
|
|
72
39
|
}
|
73
40
|
end
|
74
41
|
|
75
|
-
# encodes the string as utf-8 in Ruby 1.9
|
76
|
-
# returns the unaltered string in Ruby 1.8
|
77
|
-
def encode_utf8 string
|
78
|
-
(string.respond_to?(:force_encoding) and string.respond_to?(:encoding)) ?
|
79
|
-
string.force_encoding(Encoding::UTF_8) : string
|
80
|
-
end
|
81
|
-
|
82
42
|
# accepts a path/string and optional hash of query params
|
83
43
|
def build_url path, params={}
|
84
44
|
full_path = @uri.path + path
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# A module that defines the interface and top-level logic for http based connection classes.
|
2
|
+
module RSolr::Connection::Requestable
|
3
|
+
|
4
|
+
include RSolr::Connection::Utils
|
5
|
+
|
6
|
+
attr_reader :opts, :uri
|
7
|
+
|
8
|
+
# opts can have:
|
9
|
+
# :url => 'http://localhost:8080/solr'
|
10
|
+
def initialize opts={}
|
11
|
+
opts[:url] ||= 'http://127.0.0.1:8983/solr'
|
12
|
+
@opts = opts
|
13
|
+
@uri = URI.parse opts[:url]
|
14
|
+
end
|
15
|
+
|
16
|
+
# send a request to the connection
|
17
|
+
# request '/select', :q=>'*:*'
|
18
|
+
#
|
19
|
+
# request '/update', {:wt=>:xml}, '</commit>'
|
20
|
+
#
|
21
|
+
# force a post where the post body is the param query
|
22
|
+
# request '/update', "<optimize/>", :method=>:post
|
23
|
+
#
|
24
|
+
def request path, params={}, *extra
|
25
|
+
opts = extra[-1].kind_of?(Hash) ? extra.pop : {}
|
26
|
+
data = extra[0]
|
27
|
+
# force a POST, use the query string as the POST body
|
28
|
+
if opts[:method] == :post and data.to_s.empty?
|
29
|
+
http_context = self.post(path, hash_to_query(params), {}, {'Content-Type' => 'application/x-www-form-urlencoded'})
|
30
|
+
else
|
31
|
+
if data
|
32
|
+
# standard POST, using "data" as the POST body
|
33
|
+
http_context = self.post(path, data, params, {"Content-Type" => 'text/xml; charset=utf-8'})
|
34
|
+
else
|
35
|
+
# standard GET
|
36
|
+
http_context = self.get(path, params)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
raise RSolr::RequestError.new("Solr Response: #{http_context[:message]}") unless http_context[:status_code] == 200
|
40
|
+
http_context
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# Helpful utility methods for building queries to a Solr server
|
2
|
+
# This includes helpers that the Direct connection can use.
|
3
|
+
module RSolr::Connection::Utils
|
4
|
+
|
5
|
+
# Performs URI escaping so that you can construct proper
|
6
|
+
# query strings faster. Use this rather than the cgi.rb
|
7
|
+
# version since it's faster. (Stolen from Rack).
|
8
|
+
def escape(s)
|
9
|
+
s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
|
10
|
+
#'%'+$1.unpack('H2'*$1.size).join('%').upcase
|
11
|
+
'%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
|
12
|
+
}.tr(' ', '+')
|
13
|
+
end
|
14
|
+
|
15
|
+
# encodes the string as utf-8 in Ruby 1.9
|
16
|
+
# returns the unaltered string in Ruby 1.8
|
17
|
+
def encode_utf8 string
|
18
|
+
(string.respond_to?(:force_encoding) and string.respond_to?(:encoding)) ?
|
19
|
+
string.force_encoding(Encoding::UTF_8) : string
|
20
|
+
end
|
21
|
+
|
22
|
+
# Return the bytesize of String; uses String#length under Ruby 1.8 and
|
23
|
+
# String#bytesize under 1.9.
|
24
|
+
if ''.respond_to?(:bytesize)
|
25
|
+
def bytesize(string)
|
26
|
+
string.bytesize
|
27
|
+
end
|
28
|
+
else
|
29
|
+
def bytesize(string)
|
30
|
+
string.size
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# creates and returns a url as a string
|
35
|
+
# "url" is the base url
|
36
|
+
# "params" is an optional hash of GET style query params
|
37
|
+
# "string_query" is an extra query string that will be appended to the
|
38
|
+
# result of "url" and "params".
|
39
|
+
def build_url url='', params={}, string_query=''
|
40
|
+
queries = [string_query, hash_to_query(params)]
|
41
|
+
queries.delete_if{|i| i.to_s.empty?}
|
42
|
+
url += "?#{queries.join('&')}" unless queries.empty?
|
43
|
+
url
|
44
|
+
end
|
45
|
+
|
46
|
+
# converts a key value pair to an escaped string:
|
47
|
+
# Example:
|
48
|
+
# build_param(:id, 1) == "id=1"
|
49
|
+
def build_param(k,v)
|
50
|
+
"#{escape(k)}=#{escape(v)}"
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# converts hash into URL query string, keys get an alpha sort
|
55
|
+
# if a value is an array, the array values get mapped to the same key:
|
56
|
+
# hash_to_query(:q=>'blah', :fq=>['blah', 'blah'], :facet=>{:field=>['location_facet', 'format_facet']})
|
57
|
+
# returns:
|
58
|
+
# ?q=blah&fq=blah&fq=blah&facet.field=location_facet&facet.field=format.facet
|
59
|
+
#
|
60
|
+
# if a value is empty/nil etc., it is not added
|
61
|
+
def hash_to_query(params)
|
62
|
+
mapped = params.map do |k, v|
|
63
|
+
next if v.to_s.empty?
|
64
|
+
if v.class == Array
|
65
|
+
hash_to_query(v.map { |x| [k, x] })
|
66
|
+
else
|
67
|
+
build_param k, v
|
68
|
+
end
|
69
|
+
end
|
70
|
+
mapped.compact.join("&")
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
data/lib/rsolr/message.rb
CHANGED
@@ -1,154 +1,8 @@
|
|
1
|
-
# The Solr::Message class is the XML generation module for sending updates to Solr.
|
2
|
-
|
1
|
+
# The Solr::Message::Generator class is the XML generation module for sending updates to Solr.
|
3
2
|
module RSolr::Message
|
4
3
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
# "attrs" is a hash for setting the "doc" xml attributes
|
9
|
-
# "fields" is an array of Field objects
|
10
|
-
attr_accessor :attrs, :fields
|
11
|
-
|
12
|
-
# "doc_hash" must be a Hash/Mash object
|
13
|
-
# If a value in the "doc_hash" is an array,
|
14
|
-
# a field object is created for each value...
|
15
|
-
def initialize(doc_hash = {})
|
16
|
-
@fields = []
|
17
|
-
doc_hash.each_pair do |field,values|
|
18
|
-
# create a new field for each value (multi-valued)
|
19
|
-
# put non-array values into an array
|
20
|
-
values = [values] unless values.is_a?(Array)
|
21
|
-
values.each do |v|
|
22
|
-
next if v.to_s.empty?
|
23
|
-
@fields << Field.new({:name=>field}, v.to_s)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
@attrs={}
|
27
|
-
end
|
28
|
-
|
29
|
-
# returns an array of fields that match the "name" arg
|
30
|
-
def fields_by_name(name)
|
31
|
-
@fields.select{|f|f.name==name}
|
32
|
-
end
|
33
|
-
|
34
|
-
# returns the *first* field that matches the "name" arg
|
35
|
-
def field_by_name(name)
|
36
|
-
@fields.detect{|f|f.name==name}
|
37
|
-
end
|
38
|
-
|
39
|
-
#
|
40
|
-
# Add a field value to the document. Options map directly to
|
41
|
-
# XML attributes in the Solr <field> node.
|
42
|
-
# See http://wiki.apache.org/solr/UpdateXmlMessages#head-8315b8028923d028950ff750a57ee22cbf7977c6
|
43
|
-
#
|
44
|
-
# === Example:
|
45
|
-
#
|
46
|
-
# document.add_field('title', 'A Title', :boost => 2.0)
|
47
|
-
#
|
48
|
-
def add_field(name, value, options = {})
|
49
|
-
@fields << Field.new(options.merge({:name=>name}), value)
|
50
|
-
end
|
51
|
-
|
52
|
-
end
|
53
|
-
|
54
|
-
# A class that represents a "doc"/"field" xml element for a solr update
|
55
|
-
class Field
|
56
|
-
|
57
|
-
# "attrs" is a hash for setting the "doc" xml attributes
|
58
|
-
# "value" is the text value for the node
|
59
|
-
attr_accessor :attrs, :value
|
60
|
-
|
61
|
-
# "attrs" must be a hash
|
62
|
-
# "value" should be something that responds to #_to_s
|
63
|
-
def initialize(attrs, value)
|
64
|
-
@attrs = attrs
|
65
|
-
@value = value
|
66
|
-
end
|
67
|
-
|
68
|
-
# the value of the "name" attribute
|
69
|
-
def name
|
70
|
-
@attrs[:name]
|
71
|
-
end
|
72
|
-
|
73
|
-
end
|
74
|
-
|
75
|
-
class Builder
|
76
|
-
|
77
|
-
# generates "add" xml for updating solr
|
78
|
-
# "data" can be a hash or an array of hashes.
|
79
|
-
# - each hash should be a simple key=>value pair representing a solr doc.
|
80
|
-
# If a value is an array, multiple fields will be created.
|
81
|
-
#
|
82
|
-
# "add_attrs" can be a hash for setting the add xml element attributes.
|
83
|
-
#
|
84
|
-
# This method can also accept a block.
|
85
|
-
# The value yielded to the block is a Message::Document; for each solr doc in "data".
|
86
|
-
# You can set xml element attributes for each "doc" element or individual "field" elements.
|
87
|
-
#
|
88
|
-
# For example:
|
89
|
-
#
|
90
|
-
# solr.add({:id=>1, :nickname=>'Tim'}, {:boost=>5.0, :commitWithin=>1.0}) do |doc_msg|
|
91
|
-
# doc_msg.attrs[:boost] = 10.00 # boost the document
|
92
|
-
# nickname = doc_msg.field_by_name(:nickname)
|
93
|
-
# nickname.attrs[:boost] = 20 if nickname.value=='Tim' # boost a field
|
94
|
-
# end
|
95
|
-
#
|
96
|
-
# would result in an add element having the attributes boost="10.0"
|
97
|
-
# and a commitWithin="1.0".
|
98
|
-
# Each doc element would have a boost="10.0".
|
99
|
-
# The "nickname" field would have a boost="20.0"
|
100
|
-
# if the doc had a "nickname" field with the value of "Tim".
|
101
|
-
#
|
102
|
-
def add(data, add_attrs={})
|
103
|
-
data = [data] unless data.is_a?(Array)
|
104
|
-
add = Xout.new :add, add_attrs
|
105
|
-
data.each do |doc|
|
106
|
-
doc = Document.new(doc) if doc.respond_to?(:each_pair)
|
107
|
-
yield doc if block_given?
|
108
|
-
add.child :doc, doc.attrs do |doc_node|
|
109
|
-
doc.fields.each do |field_obj|
|
110
|
-
doc_node.child :field, field_obj.value, field_obj.attrs
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
add.to_xml
|
115
|
-
end
|
116
|
-
|
117
|
-
# generates a <commit/> message
|
118
|
-
def commit(opts={})
|
119
|
-
Xout.new(:commit, opts).to_xml
|
120
|
-
end
|
121
|
-
|
122
|
-
# generates a <optimize/> message
|
123
|
-
def optimize(opts={})
|
124
|
-
Xout.new(:optimize, opts).to_xml
|
125
|
-
end
|
126
|
-
|
127
|
-
# generates a <rollback/> message
|
128
|
-
def rollback
|
129
|
-
Xout.new(:rollback).to_xml
|
130
|
-
end
|
131
|
-
|
132
|
-
# generates a <delete><id>ID</id></delete> message
|
133
|
-
# "ids" can be a single value or array of values
|
134
|
-
def delete_by_id(ids)
|
135
|
-
ids = [ids] unless ids.is_a?(Array)
|
136
|
-
delete_node = Xout.new(:delete) do |xml|
|
137
|
-
ids.each { |id| xml.child :id, id }
|
138
|
-
end
|
139
|
-
delete_node.to_xml
|
140
|
-
end
|
141
|
-
|
142
|
-
# generates a <delete><query>ID</query></delete> message
|
143
|
-
# "queries" can be a single value or an array of values
|
144
|
-
def delete_by_query(queries)
|
145
|
-
queries = [queries] unless queries.is_a?(Array)
|
146
|
-
delete_node = Xout.new(:delete) do |xml|
|
147
|
-
queries.each { |query| xml.child :query, query }
|
148
|
-
end
|
149
|
-
delete_node.to_xml
|
150
|
-
end
|
151
|
-
|
152
|
-
end
|
4
|
+
autoload :Document, 'rsolr/message/document'
|
5
|
+
autoload :Field, 'rsolr/message/field'
|
6
|
+
autoload :Generator, 'rsolr/message/generator'
|
153
7
|
|
154
8
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# A class that represents a "doc" xml element for a solr update
|
2
|
+
class RSolr::Message::Document
|
3
|
+
|
4
|
+
# "attrs" is a hash for setting the "doc" xml attributes
|
5
|
+
# "fields" is an array of Field objects
|
6
|
+
attr_accessor :attrs, :fields
|
7
|
+
|
8
|
+
# "doc_hash" must be a Hash/Mash object
|
9
|
+
# If a value in the "doc_hash" is an array,
|
10
|
+
# a field object is created for each value...
|
11
|
+
def initialize(doc_hash = {})
|
12
|
+
@fields = []
|
13
|
+
doc_hash.each_pair do |field,values|
|
14
|
+
# create a new field for each value (multi-valued)
|
15
|
+
# put non-array values into an array
|
16
|
+
values = [values] unless values.is_a?(Array)
|
17
|
+
values.each do |v|
|
18
|
+
next if v.to_s.empty?
|
19
|
+
@fields << RSolr::Message::Field.new({:name=>field}, v.to_s)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
@attrs={}
|
23
|
+
end
|
24
|
+
|
25
|
+
# returns an array of fields that match the "name" arg
|
26
|
+
def fields_by_name(name)
|
27
|
+
@fields.select{|f|f.name==name}
|
28
|
+
end
|
29
|
+
|
30
|
+
# returns the *first* field that matches the "name" arg
|
31
|
+
def field_by_name(name)
|
32
|
+
@fields.detect{|f|f.name==name}
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# Add a field value to the document. Options map directly to
|
37
|
+
# XML attributes in the Solr <field> node.
|
38
|
+
# See http://wiki.apache.org/solr/UpdateXmlMessages#head-8315b8028923d028950ff750a57ee22cbf7977c6
|
39
|
+
#
|
40
|
+
# === Example:
|
41
|
+
#
|
42
|
+
# document.add_field('title', 'A Title', :boost => 2.0)
|
43
|
+
#
|
44
|
+
def add_field(name, value, options = {})
|
45
|
+
@fields << RSolr::Message::Field.new(options.merge({:name=>name}), value)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# A class that represents a "doc"/"field" xml element for a solr update
|
2
|
+
class RSolr::Message::Field
|
3
|
+
|
4
|
+
# "attrs" is a hash for setting the "doc" xml attributes
|
5
|
+
# "value" is the text value for the node
|
6
|
+
attr_accessor :attrs, :value
|
7
|
+
|
8
|
+
# "attrs" must be a hash
|
9
|
+
# "value" should be something that responds to #_to_s
|
10
|
+
def initialize(attrs, value)
|
11
|
+
@attrs = attrs
|
12
|
+
@value = value
|
13
|
+
end
|
14
|
+
|
15
|
+
# the value of the "name" attribute
|
16
|
+
def name
|
17
|
+
@attrs[:name]
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
class RSolr::Message::Generator
|
2
|
+
|
3
|
+
def build &block
|
4
|
+
require 'builder'
|
5
|
+
b = ::Builder::XmlMarkup.new(:indent=>0, :margin=>0, :encoding => 'UTF-8')
|
6
|
+
b.instruct!
|
7
|
+
block_given? ? yield(b) : b
|
8
|
+
end
|
9
|
+
|
10
|
+
# generates "add" xml for updating solr
|
11
|
+
# "data" can be a hash or an array of hashes.
|
12
|
+
# - each hash should be a simple key=>value pair representing a solr doc.
|
13
|
+
# If a value is an array, multiple fields will be created.
|
14
|
+
#
|
15
|
+
# "add_attrs" can be a hash for setting the add xml element attributes.
|
16
|
+
#
|
17
|
+
# This method can also accept a block.
|
18
|
+
# The value yielded to the block is a Message::Document; for each solr doc in "data".
|
19
|
+
# You can set xml element attributes for each "doc" element or individual "field" elements.
|
20
|
+
#
|
21
|
+
# For example:
|
22
|
+
#
|
23
|
+
# solr.add({:id=>1, :nickname=>'Tim'}, {:boost=>5.0, :commitWithin=>1.0}) do |doc_msg|
|
24
|
+
# doc_msg.attrs[:boost] = 10.00 # boost the document
|
25
|
+
# nickname = doc_msg.field_by_name(:nickname)
|
26
|
+
# nickname.attrs[:boost] = 20 if nickname.value=='Tim' # boost a field
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# would result in an add element having the attributes boost="10.0"
|
30
|
+
# and a commitWithin="1.0".
|
31
|
+
# Each doc element would have a boost="10.0".
|
32
|
+
# The "nickname" field would have a boost="20.0"
|
33
|
+
# if the doc had a "nickname" field with the value of "Tim".
|
34
|
+
#
|
35
|
+
def add data, add_attrs={}, &block
|
36
|
+
data = [data] unless data.is_a?(Array)
|
37
|
+
build do |xml|
|
38
|
+
xml.add(add_attrs) do |add_node|
|
39
|
+
data.each do |doc|
|
40
|
+
doc = RSolr::Message::Document.new(doc) if doc.respond_to?(:each_pair)
|
41
|
+
yield doc if block_given?
|
42
|
+
add_node.doc(doc.attrs) do |doc_node|
|
43
|
+
doc.fields.each do |field_obj|
|
44
|
+
doc_node.field field_obj.value, field_obj.attrs
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# generates a <commit/> message
|
53
|
+
def commit(opts={})
|
54
|
+
build {|xml| xml.commit opts}
|
55
|
+
end
|
56
|
+
|
57
|
+
# generates a <optimize/> message
|
58
|
+
def optimize(opts={})
|
59
|
+
build {|xml| xml.optimize opts}
|
60
|
+
end
|
61
|
+
|
62
|
+
# generates a <rollback/> message
|
63
|
+
def rollback opts={}
|
64
|
+
build {|xml| xml.rollback opts}
|
65
|
+
end
|
66
|
+
|
67
|
+
# generates a <delete><id>ID</id></delete> message
|
68
|
+
# "ids" can be a single value or array of values
|
69
|
+
def delete_by_id(ids)
|
70
|
+
ids = [ids] unless ids.is_a?(Array)
|
71
|
+
build do |xml|
|
72
|
+
xml.delete do |delete_node|
|
73
|
+
ids.each { |id| delete_node.id(id) }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# generates a <delete><query>ID</query></delete> message
|
79
|
+
# "queries" can be a single value or an array of values
|
80
|
+
def delete_by_query(queries)
|
81
|
+
queries = [queries] unless queries.is_a?(Array)
|
82
|
+
build do |xml|
|
83
|
+
xml.delete do |delete_node|
|
84
|
+
queries.each { |query| delete_node.query query }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
data/rsolr.gemspec
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
|
3
3
|
s.name = "rsolr"
|
4
|
-
s.version = "0.
|
5
|
-
s.date = "
|
4
|
+
s.version = "0.12.0"
|
5
|
+
s.date = "2010-02-03"
|
6
6
|
s.summary = "A Ruby client for Apache Solr"
|
7
7
|
s.email = "goodieboy@gmail.com"
|
8
8
|
s.homepage = "http://github.com/mwmitchell/rsolr"
|
@@ -13,12 +13,15 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.files = [
|
14
14
|
"CHANGES.txt",
|
15
15
|
"lib/rsolr/client.rb",
|
16
|
-
"lib/rsolr/connection/direct.rb",
|
17
16
|
"lib/rsolr/connection/net_http.rb",
|
17
|
+
"lib/rsolr/connection/requestable.rb",
|
18
|
+
"lib/rsolr/connection/utils.rb",
|
18
19
|
"lib/rsolr/connection.rb",
|
20
|
+
"lib/rsolr/message/document.rb",
|
21
|
+
"lib/rsolr/message/field.rb",
|
22
|
+
"lib/rsolr/message/generator.rb",
|
19
23
|
"lib/rsolr/message.rb",
|
20
24
|
"lib/rsolr.rb",
|
21
|
-
"lib/xout.rb",
|
22
25
|
"LICENSE",
|
23
26
|
"README.rdoc",
|
24
27
|
"rsolr.gemspec"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rsolr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.12.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Mitchell
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2010-02-03 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -26,12 +26,15 @@ extra_rdoc_files:
|
|
26
26
|
files:
|
27
27
|
- CHANGES.txt
|
28
28
|
- lib/rsolr/client.rb
|
29
|
-
- lib/rsolr/connection/direct.rb
|
30
29
|
- lib/rsolr/connection/net_http.rb
|
30
|
+
- lib/rsolr/connection/requestable.rb
|
31
|
+
- lib/rsolr/connection/utils.rb
|
31
32
|
- lib/rsolr/connection.rb
|
33
|
+
- lib/rsolr/message/document.rb
|
34
|
+
- lib/rsolr/message/field.rb
|
35
|
+
- lib/rsolr/message/generator.rb
|
32
36
|
- lib/rsolr/message.rb
|
33
37
|
- lib/rsolr.rb
|
34
|
-
- lib/xout.rb
|
35
38
|
- LICENSE
|
36
39
|
- README.rdoc
|
37
40
|
- rsolr.gemspec
|
@@ -1,69 +0,0 @@
|
|
1
|
-
raise "JRuby Required" unless defined?(JRUBY_VERSION)
|
2
|
-
|
3
|
-
require 'java'
|
4
|
-
|
5
|
-
#
|
6
|
-
# Connection for JRuby + DirectSolrConnection
|
7
|
-
#
|
8
|
-
class RSolr::Connection::Direct
|
9
|
-
|
10
|
-
include RSolr::Connection::Utils
|
11
|
-
|
12
|
-
attr_accessor :opts
|
13
|
-
|
14
|
-
# opts can be an instance of org.apache.solr.servlet.DirectSolrConnection
|
15
|
-
# if opts is NOT an instance of org.apache.solr.servlet.DirectSolrConnection
|
16
|
-
# then...
|
17
|
-
# required: opts[:home_dir] is absolute path to solr home (the directory with "data", "config" etc.)
|
18
|
-
#
|
19
|
-
# You can load your own solr java libs by setting :autoload_jars to false.
|
20
|
-
# When set to true (default), RSolr loads its own set of solr java libs.
|
21
|
-
def initialize(opts, &block)
|
22
|
-
if defined?(Java::OrgApacheSolrCore::SolrCore) and opts.is_a?(Java::OrgApacheSolrCore::SolrCore)
|
23
|
-
@connection = org.apache.solr.servlet.DirectSolrConnection.new(opts)
|
24
|
-
elsif defined?(Java::OrgApacheSolrServlet::DirectSolrConnection) and opts.is_a?(Java::OrgApacheSolrServlet::DirectSolrConnection)
|
25
|
-
@connection = opts
|
26
|
-
else
|
27
|
-
opts[:data_dir] ||= File.join(opts[:home_dir].to_s, 'data')
|
28
|
-
@opts = opts
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# sets the @connection instance variable if it has not yet been set
|
33
|
-
def connection
|
34
|
-
@connection ||= (
|
35
|
-
org.apache.solr.servlet.DirectSolrConnection.new(opts[:home_dir], @opts[:data_dir], nil)
|
36
|
-
)
|
37
|
-
end
|
38
|
-
|
39
|
-
def close
|
40
|
-
if @connection
|
41
|
-
@connection.close
|
42
|
-
@connection=nil
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
# send a request to the connection
|
47
|
-
# request '/select', :q=>'something'
|
48
|
-
# request '/update', :wt=>:xml, '</commit>'
|
49
|
-
def request(path, params={}, data=nil, opts={})
|
50
|
-
data = data.to_xml if data.respond_to?(:to_xml)
|
51
|
-
url = build_url(path, params)
|
52
|
-
begin
|
53
|
-
body = connection.request(url, data)
|
54
|
-
rescue
|
55
|
-
raise RSolr::RequestError.new($!.message)
|
56
|
-
end
|
57
|
-
{
|
58
|
-
:status_code => 200,
|
59
|
-
:url=>url,
|
60
|
-
:body=>body,
|
61
|
-
:path=>path,
|
62
|
-
:params=>params,
|
63
|
-
:data=>data,
|
64
|
-
:headers => {},
|
65
|
-
:message => ''
|
66
|
-
}
|
67
|
-
end
|
68
|
-
|
69
|
-
end
|
data/lib/xout.rb
DELETED
@@ -1,87 +0,0 @@
|
|
1
|
-
class Xout
|
2
|
-
|
3
|
-
VERSION = '0.1.0'
|
4
|
-
|
5
|
-
attr_reader :name, :text, :attrs, :children
|
6
|
-
|
7
|
-
def initialize node_name, *args, &block
|
8
|
-
@children = []
|
9
|
-
attrs = args.last.is_a?(Hash) ? args.pop : {}
|
10
|
-
text = args.empty? ? '' : args.pop.to_s
|
11
|
-
@name, @text, @attrs = node_name, text, attrs
|
12
|
-
yield self if block_given?
|
13
|
-
end
|
14
|
-
|
15
|
-
def child name, *args, &block
|
16
|
-
add_child self.class.new(name, *args, &block)
|
17
|
-
end
|
18
|
-
|
19
|
-
def add_child node
|
20
|
-
children << node
|
21
|
-
end
|
22
|
-
|
23
|
-
def to_xml
|
24
|
-
xml = ["<#{name}#{create_attrs(attrs)}"]
|
25
|
-
if not text.empty? or not children.empty?
|
26
|
-
xml << ">#{escape_text text.to_s}"
|
27
|
-
xml += children.map{|child|child.to_xml}
|
28
|
-
xml << "</#{name}>"
|
29
|
-
else
|
30
|
-
xml << '/>'
|
31
|
-
end
|
32
|
-
xml.join
|
33
|
-
end
|
34
|
-
|
35
|
-
alias :to_s :to_xml
|
36
|
-
|
37
|
-
def to_xml_doc
|
38
|
-
'<?xml version="1.0" encoding="UTF-8"?>' + to_xml
|
39
|
-
end
|
40
|
-
|
41
|
-
# builds an XML attribute string.
|
42
|
-
# escapes each attribute value by running it through #escape_attr
|
43
|
-
def create_attrs hash
|
44
|
-
r = hash.map { |k,v| "#{k}=\"#{escape_attr v.to_s}\"" }.join(' ')
|
45
|
-
" #{r}" unless r.empty?
|
46
|
-
end
|
47
|
-
|
48
|
-
module Escapable
|
49
|
-
|
50
|
-
def text_mapping
|
51
|
-
@text_mapping ||= {'&'=>'&', '<'=>'<', '>'=>'>'}
|
52
|
-
end
|
53
|
-
|
54
|
-
def text_regexp
|
55
|
-
@text_regexp ||= /[#{text_mapping.keys.join}]/
|
56
|
-
end
|
57
|
-
|
58
|
-
def attr_mapping
|
59
|
-
@attr_mapping ||= {'&'=>'&', '<'=>'<', '>'=>'>', "'"=>''', '"'=>'"e;'}
|
60
|
-
end
|
61
|
-
|
62
|
-
def attr_regexp
|
63
|
-
@attr_regexp ||= /[#{attr_mapping.keys.join}]/
|
64
|
-
end
|
65
|
-
|
66
|
-
# minimal escaping for attribute values
|
67
|
-
def escape_attr input
|
68
|
-
escape input, attr_regexp, attr_mapping
|
69
|
-
end
|
70
|
-
|
71
|
-
# minimal escaping for text
|
72
|
-
def escape_text input
|
73
|
-
escape input, text_regexp, text_mapping
|
74
|
-
end
|
75
|
-
|
76
|
-
# accepts a string input and a hash mapping of characters => replacement values:
|
77
|
-
# Example:
|
78
|
-
# escape 'My <string>cat</strong>', '<'=>'>', '>'=>'<'
|
79
|
-
def escape input, regexp, map
|
80
|
-
input.gsub(regexp) { | char | map[char] || char }
|
81
|
-
end
|
82
|
-
|
83
|
-
end
|
84
|
-
|
85
|
-
include Escapable
|
86
|
-
|
87
|
-
end
|