mwmitchell-rsolr 0.9.1 → 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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