mwmitchell-solr 0.5.3 → 0.5.4

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.
Files changed (38) hide show
  1. data/CHANGES.txt +28 -0
  2. data/README.rdoc +50 -13
  3. data/Rakefile +1 -1
  4. data/examples/direct.rb +6 -4
  5. data/examples/http.rb +7 -2
  6. data/lib/solr/{adapter → connection/adapter}/common_methods.rb +1 -44
  7. data/lib/solr/{adapter → connection/adapter}/direct.rb +28 -13
  8. data/lib/solr/connection/adapter/http.rb +51 -0
  9. data/lib/solr/connection/adapter.rb +7 -0
  10. data/lib/solr/connection/search_ext.rb +28 -24
  11. data/lib/solr/connection.rb +1 -1
  12. data/lib/solr/http_client/adapter/curb.rb +51 -0
  13. data/lib/solr/http_client/adapter/net_http.rb +48 -0
  14. data/lib/solr/http_client/adapter.rb +6 -0
  15. data/lib/solr/http_client.rb +115 -0
  16. data/lib/solr/mapper/rss.rb +2 -0
  17. data/lib/solr/message.rb +5 -1
  18. data/lib/solr/response/base.rb +32 -0
  19. data/lib/solr/response/index_info.rb +22 -0
  20. data/lib/solr/response/query.rb +93 -0
  21. data/lib/solr/response/update.rb +4 -0
  22. data/lib/solr.rb +3 -4
  23. data/test/{direct_test.rb → connection/direct_test.rb} +4 -4
  24. data/test/connection/http_test.rb +19 -0
  25. data/test/{connection_test_methods.rb → connection/test_methods.rb} +14 -4
  26. data/test/http_client/curb_test.rb +19 -0
  27. data/test/http_client/net_http_test.rb +13 -0
  28. data/test/http_client/test_methods.rb +40 -0
  29. data/test/{adapter_common_methods_test.rb → http_client/util_test.rb} +3 -12
  30. data/test/message_test.rb +10 -0
  31. data/test/{ext_pagination_test.rb → pagination_test.rb} +8 -8
  32. data/test/test_helpers.rb +8 -0
  33. metadata +29 -17
  34. data/lib/solr/adapter/http.rb +0 -55
  35. data/lib/solr/adapter.rb +0 -7
  36. data/test/ext_search_test.rb +0 -9
  37. data/test/http_test.rb +0 -15
  38. data/test/indexer_test.rb +0 -14
@@ -2,6 +2,6 @@ module Solr::Connection
2
2
 
3
3
  autoload :Base, 'solr/connection/base'
4
4
  autoload :SearchExt, 'solr/connection/search_ext'
5
- autoload :PaginationExt, 'solr/connection/pagination_ext'
5
+ autoload :Adapter, 'solr/connection/adapter'
6
6
 
7
7
  end
@@ -0,0 +1,51 @@
1
+ require 'rubygems'
2
+ require 'curb'
3
+
4
+ class Solr::HTTPClient::Adapter::Curb
5
+
6
+ include Solr::HTTPClient::Util
7
+
8
+ attr :uri
9
+ attr :c
10
+
11
+ def initialize(url)
12
+ @uri = URI.parse(url)
13
+ @c = ::Curl::Easy.new
14
+ end
15
+
16
+ def get(path, params={})
17
+ @c.url = _build_url(path, params)
18
+ @c.multipart_form_post = false
19
+ @c.perform
20
+ create_http_context(path, params)
21
+ end
22
+
23
+ def post(path, data, params={}, headers={})
24
+ @c.url = _build_url(path, params)
25
+ @c.headers = headers
26
+ @c.http_post(data)
27
+ create_http_context(path, params, data, headers)
28
+ end
29
+
30
+ protected
31
+
32
+ def create_http_context(path, params, data=nil, headers={})
33
+ {
34
+ :status_code=>@c.response_code.to_i,
35
+ :url=>@c.url,
36
+ :body=>@c.body_str,
37
+ :path=>path,
38
+ :params=>params,
39
+ :data=>data,
40
+ :headers=>headers
41
+ }
42
+ end
43
+
44
+ def _build_url(path, params={})
45
+ url = @uri.scheme + '://' + @uri.host
46
+ url += ':' + @uri.port.to_s if @uri.port
47
+ url += @uri.path + path
48
+ build_url(url, params, @uri.query)
49
+ end
50
+
51
+ end
@@ -0,0 +1,48 @@
1
+ require 'net/http'
2
+
3
+ class Solr::HTTPClient::Adapter::NetHTTP
4
+
5
+ include Solr::HTTPClient::Util
6
+
7
+ attr :uri
8
+ attr :c
9
+
10
+ def initialize(url)
11
+ @uri = URI.parse(url)
12
+ @c = Net::HTTP.new(@uri.host, @uri.port)
13
+ end
14
+
15
+ def get(path, params={})
16
+ url = _build_url(path, params)
17
+ net_http_response = @c.get(url)
18
+ create_http_context(net_http_response, url, path, params)
19
+ end
20
+
21
+ def post(path, data, params={}, headers={})
22
+ url = _build_url(path, params)
23
+ net_http_response = @c.post(url, data, headers)
24
+ create_http_context(net_http_response, url, path, params, data, headers)
25
+ end
26
+
27
+ protected
28
+
29
+ def create_http_context(net_http_response, url, path, params, data=nil, headers={})
30
+ full_url = "#{@uri.scheme}://#{@uri.host}"
31
+ full_url += @uri.port ? ":#{@uri.port}" : ''
32
+ full_url += url
33
+ {
34
+ :status_code=>net_http_response.code.to_i,
35
+ :body=>net_http_response.body,
36
+ :url=>full_url,
37
+ :path=>path,
38
+ :params=>params,
39
+ :data=>data,
40
+ :headers=>headers
41
+ }
42
+ end
43
+
44
+ def _build_url(path, params={})
45
+ build_url(@uri.path + path, params, @uri.query)
46
+ end
47
+
48
+ end
@@ -0,0 +1,6 @@
1
+ module Solr::HTTPClient::Adapter
2
+
3
+ autoload :Curb, 'solr/http_client/adapter/curb'
4
+ autoload :NetHTTP, 'solr/http_client/adapter/net_http'
5
+
6
+ end
@@ -0,0 +1,115 @@
1
+ require 'uri'
2
+
3
+ # A simple wrapper for different http client implementations
4
+ # that supports #get and #post
5
+ # This was motivated by: http://apocryph.org/2008/11/09/more_indepth_analysis_ruby_http_client_performance/
6
+ # Net::HTTP is the default adapter
7
+
8
+ # Each adapter response should be a hash with the following keys:
9
+ # :status_code
10
+ # :url
11
+ # :body
12
+ # :path
13
+ # :params
14
+ # :data
15
+ # :headers
16
+
17
+ # Example:
18
+ #hclient = Solr::HTTPClient.connect('http://www.google.com', :net_http)
19
+ #response = hclient.get('/search', :hl=>:en, :q=>:ruby, :btnG=>:Search)
20
+ #puts response[:status_code]
21
+ #puts response[:body]
22
+
23
+ module Solr::HTTPClient
24
+
25
+ autoload :Adapter, 'solr/http_client/adapter'
26
+
27
+ class UnkownAdapterError < RuntimeError; end
28
+
29
+ def self.connect(url, adapter_name=:net_http)
30
+ case adapter_name
31
+ when :curb
32
+ klass = 'Curb'
33
+ when :net_http
34
+ klass = 'NetHTTP'
35
+ else
36
+ raise UnkownAdapterError.new("Name: #{adapter_name}")
37
+ end
38
+ Base.new Solr::HTTPClient::Adapter.const_get(klass).new(url)
39
+ end
40
+
41
+ class Base
42
+
43
+ attr_reader :adapter
44
+
45
+ def initialize(adapter)
46
+ @adapter = adapter
47
+ end
48
+
49
+ def get(path, params={})
50
+ begin
51
+ http_context = @adapter.get(path, params)
52
+ rescue
53
+ raise Solr::RequestError.new($!)
54
+ end
55
+ http_context
56
+ end
57
+
58
+ def post(path, data, params={}, headers={})
59
+ begin
60
+ http_context = @adapter.post(path, data, params, headers)
61
+ rescue
62
+ raise Solr::RequestError.new($!)
63
+ end
64
+ http_context
65
+ end
66
+
67
+ end
68
+
69
+ module Util
70
+
71
+ # escapes a query key/value for http
72
+ def escape(s)
73
+ s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
74
+ '%'+$1.unpack('H2'*$1.size).join('%').upcase
75
+ }.tr(' ', '+')
76
+ end
77
+
78
+ def build_url(url='', params={}, string_query='')
79
+ queries = [string_query, hash_to_params(params)]
80
+ queries.delete_if{|i| i.to_s.empty?}
81
+ url += "?#{queries.join('&')}" unless queries.empty?
82
+ url
83
+ end
84
+
85
+ def build_param(k,v)
86
+ "#{escape(k)}=#{escape(v)}"
87
+ end
88
+
89
+ #
90
+ # converts hash into URL query string, keys get an alpha sort
91
+ # if a value is an array, the array values get mapped to the same key:
92
+ # hash_to_params(:q=>'blah', 'facet.field'=>['location_facet', 'format_facet'])
93
+ # returns:
94
+ # ?q=blah&facet.field=location_facet&facet.field=format.facet
95
+ #
96
+ # if a value is empty/nil etc., the key is not added
97
+ def hash_to_params(params)
98
+ return unless params.is_a?(Hash)
99
+ # copy params and convert keys to strings
100
+ params = params.inject({}){|acc,(k,v)| acc.merge({k.to_s, v}) }
101
+ # get sorted keys
102
+ params.keys.sort.inject([]) do |acc,k|
103
+ v = params[k]
104
+ if v.is_a?(Array)
105
+ acc << v.reject{|i|i.to_s.empty?}.collect{|vv|build_param(k, vv)}
106
+ elsif ! v.to_s.empty?
107
+ acc.push(build_param(k, v))
108
+ end
109
+ acc
110
+ end.join('&')
111
+ end
112
+
113
+ end
114
+
115
+ end
@@ -1,3 +1,5 @@
1
+ #TODO - this could use the http wrapper stuff instead of open-uri/net::http
2
+
1
3
  require 'rss'
2
4
  require 'open-uri'
3
5
 
data/lib/solr/message.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  require 'rubygems'
4
4
  require 'builder'
5
5
 
6
+ # The Solr::Message class is the XML generation module for sending updates to Solr.
7
+
6
8
  class Solr::Message
7
9
 
8
10
  class << self
@@ -27,7 +29,9 @@ class Solr::Message
27
29
  sorted_items.keys.sort.each do |k|
28
30
  doc_attrs = {:name=>k}
29
31
  yield doc_attrs if block_given?
30
- doc_xml.field(sorted_items[k], doc_attrs)
32
+ [sorted_items[k]].flatten.each do |v| # multiValued attributes
33
+ doc_xml.field(v, doc_attrs)
34
+ end
31
35
  end
32
36
  end
33
37
  end
@@ -0,0 +1,32 @@
1
+ # default/base response object
2
+ # This is where the ruby "eval" happens
3
+ # So far, all response classes extend this
4
+ class Solr::Response::Base
5
+
6
+ attr_reader :source
7
+
8
+ attr_reader :raw_response, :data, :header, :params, :status, :query_time
9
+
10
+ def initialize(data)
11
+ if data.is_a?(Hash) and data.has_key?(:body)
12
+ @data = Kernel.eval(data[:body])
13
+ @source = data
14
+ else
15
+ if data.is_a?(String)
16
+ @raw_response = data
17
+ @data = Kernel.eval(@raw_response)
18
+ elsif data.is_a?(Hash)
19
+ @data = data
20
+ end
21
+ end
22
+ @header = @data['responseHeader']
23
+ @params = @header['params']
24
+ @status = @header['status']
25
+ @query_time = @header['QTime']
26
+ end
27
+
28
+ def ok?
29
+ self.status==0
30
+ end
31
+
32
+ end
@@ -0,0 +1,22 @@
1
+ # response for /admin/luke
2
+ class Solr::Response::IndexInfo < Solr::Response::Base
3
+
4
+ attr_reader :index, :directory, :has_deletions, :optimized, :current, :max_doc, :num_docs, :version
5
+
6
+ alias :has_deletions? :has_deletions
7
+ alias :optimized? :optimized
8
+ alias :current? :current
9
+
10
+ def initialize(data)
11
+ super(data)
12
+ @index = @data['index']
13
+ @directory = @data['directory']
14
+ @has_deletions = @index['hasDeletions']
15
+ @optimized = @index['optimized']
16
+ @current = @index['current']
17
+ @max_doc = @index['maxDoc']
18
+ @num_docs = @index['numDocs']
19
+ @version = @index['version']
20
+ end
21
+
22
+ end
@@ -0,0 +1,93 @@
1
+ # response module for queries
2
+ module Solr::Response::Query
3
+
4
+ # module for adding some helper methods for each document
5
+ module DocExt
6
+
7
+ # Provide "method accessors" to the data.
8
+ # This might be better implemented using instance_eval
9
+ # to create the methods?
10
+ def method_missing(k, *args)
11
+ has_key?(k) ? self[k] : super(k, *args)
12
+ end
13
+
14
+ # Helper method to check if value/values exist for a given key.
15
+ # The value can be a string, or a RegExp
16
+ # Example:
17
+ # doc.has?(:location_facet, 'Clemons')
18
+ # doc.has?(:id, 'h009', /^u/i)
19
+ def has?(k, *values)
20
+ return if self[k].nil?
21
+ target = self[k]
22
+ if target.is_a?(Array)
23
+ values.each do |val|
24
+ return target.any?{|tv| val.is_a?(Regexp) ? (tv =~ val) : (tv==val)}
25
+ end
26
+ else
27
+ return values.any? {|val| val.is_a?(Regexp) ? (target =~ val) : (target == val)}
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ module Pagination
34
+
35
+ # alias to the Solr param, 'rows'
36
+ def per_page
37
+ @per_page = params['rows'].to_s.to_i
38
+ end
39
+
40
+ # Returns the current page calculated from 'rows' and 'start'
41
+ # WillPaginate hook
42
+ def current_page
43
+ @current_page = self.per_page > 0 ? ((self.start / self.per_page).ceil) : 1
44
+ @current_page == 0 ? 1 : @current_page
45
+ end
46
+
47
+ # Calcuates the total pages from 'numFound' and 'rows'
48
+ # WillPaginate hook
49
+ def total_pages
50
+ self.per_page > 0 ? (self.total / self.per_page.to_f).ceil : 1
51
+ end
52
+
53
+ # returns the previous page number or 1
54
+ # WillPaginate hook
55
+ def previous_page
56
+ (current_page > 1) ? current_page - 1 : 1
57
+ end
58
+
59
+ # returns the next page number or the last
60
+ # WillPaginate hook
61
+ def next_page
62
+ (current_page < total_pages) ? current_page + 1 : total_pages
63
+ end
64
+
65
+ end
66
+
67
+ # The base query response class
68
+ # adds to the Solr::Response::Base class by defining a few more attributes,
69
+ # includes the Pagination module, and extends each of the doc hashes
70
+ # with Solr::Response::Query::DocExt
71
+ class Base < Solr::Response::Base
72
+
73
+ include Solr::Response::Query::Pagination
74
+
75
+ attr_reader :response, :docs, :num_found, :start
76
+
77
+ alias :total :num_found
78
+ alias :offset :start
79
+
80
+ def initialize(data)
81
+ super(data)
82
+ @response = @data['response']
83
+ @docs = @response['docs']
84
+ @docs.each do |d|
85
+ d.extend Solr::Response::Query::DocExt
86
+ end
87
+ @num_found = @response['numFound']
88
+ @start = @response['start']
89
+ end
90
+
91
+ end
92
+
93
+ end
@@ -0,0 +1,4 @@
1
+ # response class for update requests - not sure if this is needed yet?
2
+ class Solr::Response::Update < Solr::Response::Base
3
+
4
+ end
data/lib/solr.rb CHANGED
@@ -7,15 +7,15 @@ proc {|base, files|
7
7
 
8
8
  module Solr
9
9
 
10
- VERSION = '0.5.3'
10
+ VERSION = '0.5.4'
11
11
 
12
- autoload :Adapter, 'solr/adapter'
13
12
  autoload :Message, 'solr/message'
14
13
  autoload :Response, 'solr/response'
15
14
  autoload :Connection, 'solr/connection'
16
15
  autoload :Ext, 'solr/ext'
17
16
  autoload :Mapper, 'solr/mapper'
18
17
  autoload :Indexer, 'solr/indexer'
18
+ autoload :HTTPClient, 'solr/http_client'
19
19
 
20
20
  # factory for creating connections
21
21
  # adapter name is either :http or :direct
@@ -26,8 +26,7 @@ module Solr
26
26
  :http=>'HTTP',
27
27
  :direct=>'Direct'
28
28
  }
29
- adapter_class_name = "Solr::Adapter::#{types[adapter_name]}"
30
- adapter_class = Kernel.eval adapter_class_name
29
+ adapter_class = Solr::Connection::Adapter.const_get(types[adapter_name])
31
30
  Solr::Connection::Base.new(adapter_class.new(opts), opts)
32
31
  end
33
32