mwmitchell-rsolr 0.9.1 → 0.9.5

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.
@@ -7,8 +7,8 @@ class RSolr::HTTPClient::Adapter::NetHTTP
7
7
  attr :uri
8
8
  attr :connection
9
9
 
10
- def initialize(url)
11
- @uri = URI.parse(url)
10
+ def initialize(opts={})
11
+ @uri = URI.parse(opts[:url])
12
12
  @connection = Net::HTTP.new(@uri.host, @uri.port)
13
13
  end
14
14
 
@@ -12,9 +12,10 @@
12
12
  # :headers
13
13
 
14
14
  # Example:
15
- # connector = RSolr::HTTPClient.Connector.new
16
- # connector.adapter_name = :net_http # switch to Net::HTTP before calling "connect"
17
- # hclient = connector.connect('http://www.google.com')
15
+ # hclient = RSolr::HTTPClient.connect('http://www.google.com')
16
+ # # SAME AS
17
+ # hclient = RSolr::HTTPClient.connect(:net_http, 'http://www.google.com')
18
+ # hclient = RSolr::HTTPClient.connect(:curb, 'http://www.google.com')
18
19
  # response = hclient.get('/search', :hl=>:en, :q=>:ruby, :btnG=>:Search)
19
20
  # puts response[:status_code]
20
21
  # puts response[:body]
@@ -23,53 +24,23 @@ require 'uri'
23
24
 
24
25
  module RSolr::HTTPClient
25
26
 
26
- autoload :Adapter, 'rsolr/http_client/adapter'
27
-
28
- class UnkownAdapterError < RuntimeError; end
27
+ module Adapter
28
+ autoload :Curb, 'rsolr/http_client/adapter/curb'
29
+ autoload :NetHTTP, 'rsolr/http_client/adapter/net_http'
30
+ end
29
31
 
30
- class Connector
31
-
32
- attr_accessor :adapter_name
33
-
34
- def initialize(adapter_name = :curb)
35
- @adapter_name = adapter_name
36
- end
37
-
38
- # Creates and returns an instance of RSolr::HTTPClient::Adapter::*
39
- # The "url" is a full/valid url.
40
- # Example:
41
- # connector = RSolr::HTTPClient::Connector.new
42
- # client = connector.connect('http://google.com')
43
- #
44
- # TODO: this should be less verbose... something like RSolr:HTTPClient.connect(url, adapter=:curb)
45
- def connect(url)
46
- case adapter_name
47
- when :curb
48
- klass = 'Curb'
49
- when :net_http
50
- klass = 'NetHTTP'
51
- else
52
- raise UnkownAdapterError.new("Name: #{adapter_name}")
53
- end
54
- begin
55
- RSolr::HTTPClient::Base.new RSolr::HTTPClient::Adapter.const_get(klass).new(url)
56
- rescue ::URI::InvalidURIError
57
- raise "#{$!} == #{url}"
58
- end
59
- end
60
-
32
+ class UnkownAdapterError < RuntimeError
61
33
  end
62
34
 
63
- # The base class for interacting with one of the HTTP client adapters
64
35
  class Base
65
-
36
+
66
37
  attr_reader :adapter
67
-
38
+
68
39
  # requires an instace of RSolr::HTTPClient::*
69
40
  def initialize(adapter)
70
41
  @adapter = adapter
71
42
  end
72
-
43
+
73
44
  # sends a GET reqest to the "path" variable
74
45
  # an optional hash of "params" can be used,
75
46
  # which is later transformed into a GET query string
@@ -81,7 +52,7 @@ module RSolr::HTTPClient
81
52
  end
82
53
  http_context
83
54
  end
84
-
55
+
85
56
  # sends a POST request to the "path" variable
86
57
  # "data" is required, and must be a string
87
58
  # "params" is an optional hash for query string params...
@@ -97,13 +68,43 @@ module RSolr::HTTPClient
97
68
 
98
69
  end
99
70
 
71
+ # Factory for creating connections.
72
+ # Can specify the connection type by
73
+ # using :net_http or :curb for the first argument.
74
+ # The ending arguments are always used for the connection adapter instance.
75
+ #
76
+ # Examples:
77
+ # # default net_http connection
78
+ # RSolr::HTTPClient.connect :url=>''
79
+ # # SAME AS
80
+ # RSolr::HTTPClient.connect :net_http, :url=>''
81
+ # # curb connection
82
+ # RSolr.connect :curb, :url=>''
83
+ def self.connect(*args)
84
+ type = args.first.is_a?(Symbol) ? args.shift : :net_http
85
+ opts = args
86
+ klass = case type
87
+ when :net_http,nil
88
+ 'NetHTTP'
89
+ when :curb
90
+ 'Curb'
91
+ else
92
+ raise UnkownAdapterError.new("Invalid adapter type: #{type} - use :curb or :net_http or blank for :net_http/default")
93
+ end
94
+ begin
95
+ Base.new Adapter.const_get(klass).new(*args)
96
+ end
97
+ end
98
+
100
99
  module Util
101
100
 
102
- # escapes a query key/value for http
101
+ # Performs URI escaping so that you can construct proper
102
+ # query strings faster. Use this rather than the cgi.rb
103
+ # version since it's faster. (Stolen from Rack).
103
104
  def escape(s)
104
105
  s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
105
106
  '%'+$1.unpack('H2'*$1.size).join('%').upcase
106
- }.tr(' ', '+')
107
+ }.tr(' ', '+')
107
108
  end
108
109
 
109
110
  # creates and returns a url as a string
@@ -112,7 +113,7 @@ module RSolr::HTTPClient
112
113
  # "string_query" is an extra query string that will be appended to the
113
114
  # result of "url" and "params".
114
115
  def build_url(url='', params={}, string_query='')
115
- queries = [string_query, hash_to_params(params)]
116
+ queries = [string_query, hash_to_query(params)]
116
117
  queries.delete_if{|i| i.to_s.empty?}
117
118
  url += "?#{queries.join('&')}" unless queries.empty?
118
119
  url
@@ -124,38 +125,23 @@ module RSolr::HTTPClient
124
125
  def build_param(k,v)
125
126
  "#{escape(k)}=#{escape(v)}"
126
127
  end
127
-
128
+
128
129
  #
129
130
  # converts hash into URL query string, keys get an alpha sort
130
131
  # if a value is an array, the array values get mapped to the same key:
131
- # hash_to_params(:q=>'blah', :fq=>['blah', 'blah'], :facet=>{:field=>['location_facet', 'format_facet']})
132
+ # hash_to_query(:q=>'blah', :fq=>['blah', 'blah'], :facet=>{:field=>['location_facet', 'format_facet']})
132
133
  # returns:
133
134
  # ?q=blah&fq=blah&fq=blah&facet.field=location_facet&facet.field=format.facet
134
135
  #
135
136
  # if a value is empty/nil etc., the key is not added
136
- def hash_to_params(params)
137
- return unless params.is_a?(Hash)
138
- # copy params and convert keys to strings
139
- params = params.inject({}){|acc,(k,v)| acc.merge(k.to_s => v) }
140
- # get sorted keys
141
- params.keys.sort.inject([]) do |acc,k|
142
- v = params[k]
143
- if v.is_a?(Array)
144
- acc << v.reject{|i|i.to_s.empty?}.collect{|vv|build_param(k, vv)}
145
- elsif v.is_a?(Hash)
146
- # NOT USED
147
- # creates dot based params like:
148
- # hash_to_params(:facet=>{:field=>['one', 'two']}) == facet.field=one&facet.field=two
149
- # TODO: should this go into a non-solr based param builder?
150
- # - dotted syntax is special to solr only
151
- #v.each_pair do |field,field_value|
152
- # acc.push(hash_to_params({"#{k}.#{field}"=>field_value}))
153
- #end
154
- elsif ! v.to_s.empty?
155
- acc.push(build_param(k, v))
137
+ def hash_to_query(params)
138
+ params.map { |k, v|
139
+ if v.class == Array
140
+ hash_to_query(v.map { |x| [k, x] })
141
+ else
142
+ build_param k, v
156
143
  end
157
- acc
158
- end.join('&')
144
+ }.join("&")
159
145
  end
160
146
 
161
147
  end
@@ -1,6 +1,6 @@
1
1
  require 'builder'
2
2
 
3
- class RSolr::Message::Builders::Builder
3
+ class RSolr::Message::Adapter::Builder
4
4
 
5
5
  # shortcut method -> xml = RSolr::Message.xml
6
6
  def xml
@@ -1,6 +1,6 @@
1
1
  require 'libxml'
2
2
 
3
- class RSolr::Message::Builders::Libxml
3
+ class RSolr::Message::Adapter::Libxml
4
4
 
5
5
  def add(documents, attributes = {})
6
6
  add_node = new_node('add', attributes)
data/lib/rsolr/message.rb CHANGED
@@ -1,8 +1,11 @@
1
1
  # The Solr::Message class is the XML generation module for sending updates to Solr.
2
2
 
3
- class RSolr::Message
3
+ module RSolr::Message
4
4
 
5
- autoload :Builders, 'rsolr/message/builders'
5
+ module Adapter
6
+ autoload :Builder, 'rsolr/message/adapter/builder'
7
+ autoload :Libxml, 'rsolr/message/adapter/libxml'
8
+ end
6
9
 
7
10
  # A class that represents a "doc" xml element for a solr update
8
11
  class Document
@@ -74,11 +77,15 @@ class RSolr::Message
74
77
 
75
78
  end
76
79
 
77
- class << self
78
- attr_writer :builder
79
-
80
- def builder
81
- @builder ||= Builders::Builder.new
80
+ class Builder
81
+
82
+ attr_writer :adapter
83
+
84
+ # b = Builder.new
85
+ # b.adapter = RSolr::Message::Adapter::LibXML.new
86
+ # b.optimize == '<optimize/>'
87
+ def adapter
88
+ @adapter ||= RSolr::Message::Adapter::Builder.new
82
89
  end
83
90
 
84
91
  # generates "add" xml for updating solr
@@ -113,34 +120,34 @@ class RSolr::Message
113
120
  yield doc if block_given?
114
121
  doc
115
122
  end
116
- builder.add(documents, add_attrs)
123
+ adapter.add(documents, add_attrs)
117
124
  end
118
125
 
119
126
  # generates a <commit/> message
120
127
  def commit(opts={})
121
- builder.commit(opts)
128
+ adapter.commit(opts)
122
129
  end
123
130
 
124
131
  # generates a <optimize/> message
125
132
  def optimize(opts={})
126
- builder.optimize(opts)
133
+ adapter.optimize(opts)
127
134
  end
128
135
 
129
136
  # generates a <rollback/> message
130
137
  def rollback
131
- builder.rollback
138
+ adapter.rollback
132
139
  end
133
140
 
134
141
  # generates a <delete><id>ID</id></delete> message
135
142
  # "ids" can be a single value or array of values
136
143
  def delete_by_id(ids)
137
- builder.delete_by_id(ids)
144
+ adapter.delete_by_id(ids)
138
145
  end
139
146
 
140
147
  # generates a <delete><query>ID</query></delete> message
141
148
  # "queries" can be a single value or an array of values
142
149
  def delete_by_query(queries)
143
- builder.delete_by_query(queries)
150
+ adapter.delete_by_query(queries)
144
151
  end
145
152
  end
146
153
  end
data/lib/rsolr.rb CHANGED
@@ -1,36 +1,43 @@
1
1
  # add this directory to the load path if it hasn't already been added
2
2
 
3
- $: << File.dirname(__FILE__) unless $:.include?(File.dirname(__FILE__))
3
+ require 'rubygems'
4
4
 
5
- unless Array.respond_to?(:extract_options!)
6
- class Array
7
- def extract_options!
8
- last.is_a?(::Hash) ? pop : {}
9
- end
10
- end
11
- end
5
+ $: << File.dirname(__FILE__) unless $:.include?(File.dirname(__FILE__))
12
6
 
13
7
  module RSolr
14
8
 
15
- VERSION = '0.8.8'
9
+ VERSION = '0.9.5'
16
10
 
17
11
  autoload :Message, 'rsolr/message'
18
12
  autoload :Connection, 'rsolr/connection'
19
- autoload :Adapter, 'rsolr/adapter'
20
13
  autoload :HTTPClient, 'rsolr/http_client'
21
14
 
22
- # factory for creating connections
23
- # "options" is a hash that gets used by the Connection
24
- # object AND the adapter object.
25
- def self.connect(options={})
26
- adapter_name = options[:adapter] ||= :http
27
- types = {
28
- :http=>'HTTP',
29
- :direct=>'Direct'
30
- }
31
- adapter_class = RSolr::Adapter.const_get(types[adapter_name])
32
- adapter = adapter_class.new(options)
33
- RSolr::Connection.new(adapter, options)
15
+ # Factory for creating connections.
16
+ # 2 modes of argument operations:
17
+ # 1. first argument is solr-adapter type, second arg is options hash for solr-adapter instance.
18
+ # 2. options hash for solr-adapter only (no adapter type as first arg)
19
+ #
20
+ # Examples:
21
+ # # default http connection
22
+ # RSolr.connect
23
+ # # http connection with custom url
24
+ # RSolr.connect :url=>'http://solr.web100.org'
25
+ # # direct connection
26
+ # RSolr.connect :direct, :home_dir=>'solr', :dist_dir=>'solr-nightly'
27
+ def self.connect(*args)
28
+ type = args.first.is_a?(Symbol) ? args.shift : :http
29
+ opts = args
30
+ type_class = case type
31
+ when :http,nil
32
+ 'HTTP'
33
+ when :direct
34
+ 'Direct'
35
+ else
36
+ raise "Invalid connection type: #{type} - use :http, :direct or leave nil for :http/default"
37
+ end
38
+ adapter_class = RSolr::Connection::Adapter.const_get(type_class)
39
+ adapter = adapter_class.new(*opts)
40
+ RSolr::Connection::Base.new(adapter)
34
41
  end
35
42
 
36
43
  # A module that contains string related methods
@@ -48,7 +55,7 @@ module RSolr
48
55
  # send the escape method into the Connection class ->
49
56
  # solr = RSolr.connect
50
57
  # solr.escape('asdf')
51
- RSolr::Connection.send(:include, Char)
58
+ RSolr::Connection::Base.send(:include, Char)
52
59
 
53
60
  # bring escape into this module (RSolr) -> RSolr.escape('asdf')
54
61
  extend Char
data/rsolr.gemspec CHANGED
@@ -1,28 +1,27 @@
1
1
  Gem::Specification.new do |s|
2
+
2
3
  s.name = "rsolr"
3
- s.version = "0.9.1"
4
- s.date = "2009-07-22"
4
+ s.version = "0.9.5"
5
+ s.date = "2009-09-05"
5
6
  s.summary = "A Ruby client for Apache Solr"
6
7
  s.email = "goodieboy@gmail.com"
7
8
  s.homepage = "http://github.com/mwmitchell/rsolr"
8
9
  s.description = "RSolr is a Ruby gem for working with Apache Solr!"
9
10
  s.has_rdoc = true
10
11
  s.authors = ["Matt Mitchell"]
12
+
11
13
  s.files = [
12
14
  "examples/http.rb",
13
15
  "examples/direct.rb",
14
16
  "lib/rsolr.rb",
15
- "lib/rsolr/adapter/direct.rb",
16
- "lib/rsolr/adapter/http.rb",
17
- "lib/rsolr/adapter.rb",
17
+ "lib/rsolr/connection/adapter/direct.rb",
18
+ "lib/rsolr/connection/adapter/http.rb",
18
19
  "lib/rsolr/connection.rb",
19
20
  "lib/rsolr/http_client/adapter/curb.rb",
20
21
  "lib/rsolr/http_client/adapter/net_http.rb",
21
- "lib/rsolr/http_client/adapter.rb",
22
22
  "lib/rsolr/http_client.rb",
23
- "lib/rsolr/message/builders/builder.rb",
24
- "lib/rsolr/message/builders/libxml.rb",
25
- "lib/rsolr/message/builders.rb",
23
+ "lib/rsolr/message/adapter/builder.rb",
24
+ "lib/rsolr/message/adapter/libxml.rb",
26
25
  "lib/rsolr/message.rb",
27
26
  "LICENSE",
28
27
  "Rakefile",
@@ -14,7 +14,7 @@ if defined?(JRUBY_VERSION)
14
14
  base = File.expand_path( File.dirname(__FILE__) )
15
15
  @dist = File.join(base, '..', '..', 'apache-solr')
16
16
  @home = File.join(dist, 'example', 'solr')
17
- @solr = RSolr.connect(:adapter=>:direct, :home_dir=>@home, :dist_dir=>@dist)
17
+ @solr = RSolr.connect(:direct, :home_dir=>@home, :dist_dir=>@dist)
18
18
  @solr.delete_by_query('*:*')
19
19
  @solr.commit
20
20
  end
@@ -26,8 +26,8 @@ if defined?(JRUBY_VERSION)
26
26
  def test_new_connection_with_existing_core
27
27
  Dir["#{@dist}/dist/*.jar"].each { |p| require p }
28
28
  dc = org.apache.solr.servlet.DirectSolrConnection.new(@home, "#{@home}/data", nil)
29
- adapter = RSolr::Adapter::Direct.new dc
30
- s = RSolr::Connection.new(adapter)
29
+ adapter = RSolr::Connection::Adapter::Direct.new dc
30
+ s = RSolr::Connection::Base.new(adapter)
31
31
  assert_equal Hash, s.request('/admin/ping').class
32
32
  adapter.close
33
33
  end
@@ -8,7 +8,7 @@ unless defined?(JRUBY_VERSION)
8
8
  include ConnectionTestMethods
9
9
 
10
10
  def setup
11
- @solr = RSolr.connect
11
+ @solr = RSolr.connect(:http)
12
12
  @solr.delete_by_query('*:*')
13
13
  @solr.commit
14
14
  end
@@ -86,7 +86,7 @@ module ConnectionTestMethods
86
86
  end
87
87
 
88
88
  def test_admin_luke_index_info
89
- response = @solr.send_request('/admin/luke', :numTerms=>0)
89
+ response = @solr.request('/admin/luke', :numTerms=>0)
90
90
  assert response.is_a?(Hash)
91
91
  # make sure the ? methods are true/false
92
92
  assert [true, false].include?(response['index']['current'])
@@ -8,7 +8,7 @@ unless defined?(JRUBY_VERSION)
8
8
  class CurbTest < RSolrBaseTest
9
9
 
10
10
  def setup
11
- @c ||= RSolr::HTTPClient::Connector.new(:curb).connect(URL)
11
+ @c ||= RSolr::HTTPClient::connect(:curb, :url=>URL)
12
12
  end
13
13
 
14
14
  include HTTPClientTestMethods
@@ -4,7 +4,7 @@ require 'http_client/test_methods'
4
4
  class NetHTTPTest < RSolrBaseTest
5
5
 
6
6
  def setup
7
- @c ||= RSolr::HTTPClient::Connector.new(:net_http).connect(URL)
7
+ @c ||= RSolr::HTTPClient::connect(:url=>URL)
8
8
  end
9
9
 
10
10
  include HTTPClientTestMethods
@@ -4,7 +4,7 @@ module HTTPClientTestMethods
4
4
 
5
5
  def test_raise_unknown_adapter
6
6
  assert_raise RSolr::HTTPClient::UnkownAdapterError do
7
- c = RSolr::HTTPClient::Connector.new(:blah).connect(URL)
7
+ c = RSolr::HTTPClient.connect(:blah, URL)
8
8
  end
9
9
  end
10
10
 
@@ -11,11 +11,15 @@ class HTTPUtilTest < RSolrBaseTest
11
11
  end
12
12
 
13
13
  def test_build_url
14
- m = @c.method(:build_url)
15
- assert_equal '/something', m.call('/something')
16
- assert_equal '/something?q=Testing', m.call('/something', :q=>'Testing')
17
- assert_equal '/something?array=1&array=2&array=3', m.call('/something', :array=>[1, 2, 3])
18
- assert_equal '/something?array=1&array=2&array=3&q=A', m.call('/something', :q=>'A', :array=>[1, 2, 3])
14
+ assert_equal '/something', @c.build_url('/something')
15
+ assert_equal '/something?q=Testing', @c.build_url('/something', :q=>'Testing')
16
+ assert_equal '/something?array=1&array=2&array=3', @c.build_url('/something', :array=>[1, 2, 3])
17
+ result = @c.build_url('/something', :q=>'A', :array=>[1, 2, 3])
18
+ assert result=~/^\/something\?/
19
+ assert result=~/q=A/
20
+ assert result=~/array=1/
21
+ assert result=~/array=2/
22
+ assert result=~/array=3/
19
23
  end
20
24
 
21
25
  def test_escape
@@ -26,15 +30,54 @@ class HTTPUtilTest < RSolrBaseTest
26
30
  assert_equal '%3A', @c.escape(':')
27
31
  end
28
32
 
29
- def test_hash_to_params
33
+ def test_hash_to_query
30
34
  my_params = {
31
- :z=>'should be last',
35
+ :z=>'should be whatever',
32
36
  :q=>'test',
33
37
  :d=>[1, 2, 3, 4],
34
38
  :b=>:zxcv,
35
39
  :x=>['!', '*', nil]
36
40
  }
37
- assert_equal 'b=zxcv&d=1&d=2&d=3&d=4&q=test&x=%21&x=%2A&z=should+be+last', @c.hash_to_params(my_params)
41
+ result = @c.hash_to_query(my_params)
42
+ assert result=~/z=should\+be\+whatever/
43
+ assert result=~/q=test/
44
+ assert result=~/d=1/
45
+ assert result=~/d=2/
46
+ assert result=~/d=3/
47
+ assert result=~/d=4/
48
+ assert result=~/b=zxcv/
49
+ assert result=~/x=%21/
50
+ assert result=~/x=*/
51
+ assert result=~/x=&?/
52
+ end
53
+
54
+ def test_ampersand_within_query_value
55
+ my_params = {
56
+ "fq" => "building_facet:\"Green (Humanities & Social Sciences)\""
57
+ }
58
+ expected = 'fq=building_facet%3A%22Green+%28Humanities+%26+Social+Sciences%29%22'
59
+ assert_equal expected, @c.hash_to_query(my_params)
60
+ end
61
+
62
+ def test_brackets
63
+ assert_equal '%7B', @c.escape('{')
64
+ assert_equal '%7D', @c.escape('}')
65
+ end
66
+
67
+ def test_exclamation
68
+ assert_equal '%21', @c.escape('!')
69
+ end
70
+
71
+ def test_complex_solr_query1
72
+ my_params = {'fq' => '{!raw f=field_name}crazy+\"field+value'}
73
+ expected = 'fq=%7B%21raw+f%3Dfield_name%7Dcrazy%2B%5C%22field%2Bvalue'
74
+ assert_equal expected, @c.hash_to_query(my_params)
75
+ end
76
+
77
+ def test_complex_solr_query2
78
+ my_params = {'q' => '+popularity:[10 TO *] +section:0'}
79
+ expected = 'q=%2Bpopularity%3A%5B10+TO+%2A%5D+%2Bsection%3A0'
80
+ assert_equal expected, @c.hash_to_query(my_params)
38
81
  end
39
82
 
40
83
  end