solr4r 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,40 @@
1
+ = solr4r - A Ruby client for Apache Solr
2
+
3
+ == VERSION
4
+
5
+ This documentation refers to solr4r version 0.0.1
6
+
7
+
8
+ == DESCRIPTION
9
+
10
+ TODO
11
+
12
+
13
+ == LINKS
14
+
15
+ Documentation:: http://blackwinter.github.com/solr4r
16
+ Source code:: http://github.com/blackwinter/solr4r
17
+ RubyGem:: http://rubygems.org/gems/solr4r
18
+
19
+
20
+ == AUTHORS
21
+
22
+ * Jens Wille <mailto:jens.wille@gmail.com>
23
+
24
+
25
+ == LICENSE AND COPYRIGHT
26
+
27
+ Copyright (C) 2014 Jens Wille
28
+
29
+ solr4r is free software: you can redistribute it and/or modify it
30
+ under the terms of the GNU Affero General Public License as published by
31
+ the Free Software Foundation, either version 3 of the License, or (at your
32
+ option) any later version.
33
+
34
+ solr4r is distributed in the hope that it will be useful, but
35
+ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
36
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
37
+ License for more details.
38
+
39
+ You should have received a copy of the GNU Affero General Public License
40
+ along with solr4r. If not, see <http://www.gnu.org/licenses/>.
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ require File.expand_path(%q{../lib/solr4r/version}, __FILE__)
2
+
3
+ begin
4
+ require 'hen'
5
+
6
+ Hen.lay! {{
7
+ gem: {
8
+ name: %q{solr4r},
9
+ version: Solr4R::VERSION,
10
+ summary: %q{A Ruby client for Apache Solr},
11
+ description: %q{Access the Apache Solr search server from Ruby},
12
+ author: %q{Jens Wille},
13
+ email: %q{jens.wille@gmail.com},
14
+ license: %q{AGPL-3.0},
15
+ homepage: :blackwinter,
16
+ dependencies: { curb: ['~> 0.8', '> 0.8.5'], nokogiri: '~> 1.6' },
17
+
18
+ required_ruby_version: '>= 1.9.3'
19
+ }
20
+ }}
21
+ rescue LoadError => err
22
+ warn "Please install the `hen' gem. (#{err})"
23
+ end
data/TODO ADDED
@@ -0,0 +1,3 @@
1
+ * request: https://github.com/taf2/curb/pull/196
2
+ * documentation
3
+ * specs
@@ -0,0 +1,291 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ ###############################################################################
5
+ # #
6
+ # solr4r -- A Ruby client for Apache Solr #
7
+ # #
8
+ # Copyright (C) 2014 Jens Wille #
9
+ # #
10
+ # Mir is free software: you can redistribute it and/or modify it under the #
11
+ # terms of the GNU Affero General Public License as published by the Free #
12
+ # Software Foundation, either version 3 of the License, or (at your option) #
13
+ # any later version. #
14
+ # #
15
+ # solr4r is distributed in the hope that it will be useful, but WITHOUT ANY #
16
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
17
+ # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for #
18
+ # more details. #
19
+ # #
20
+ # You should have received a copy of the GNU Affero General Public License #
21
+ # along with solr4r. If not, see <http://www.gnu.org/licenses/>. #
22
+ # #
23
+ ###############################################################################
24
+ #++
25
+
26
+ require 'nokogiri'
27
+
28
+ module Solr4R
29
+
30
+ Document = Nokogiri::XML::Document
31
+
32
+ class Builder < Nokogiri::XML::Builder
33
+
34
+ DEFAULT_OPTIONS = {
35
+ encoding: 'UTF-8'
36
+ }
37
+
38
+ def initialize(options = {})
39
+ if block_given?
40
+ raise ArgumentError,
41
+ 'block argument not supported, use options hash instead'
42
+ end
43
+
44
+ super(@options = DEFAULT_OPTIONS.merge(options))
45
+
46
+ @_solr_doc = @doc
47
+ end
48
+
49
+ # See Schema[http://wiki.apache.org/solr/UpdateXmlMessages#add.2Freplace_documents].
50
+ #
51
+ # Examples:
52
+ #
53
+ # # single document
54
+ # add(employeeId: '05991', office: 'Bridgewater', skills: %w[Perl Java])
55
+ #
56
+ # <?xml version="1.0" encoding="UTF-8"?>
57
+ # <add>
58
+ # <doc>
59
+ # <field name="employeeId">05991</field>
60
+ # <field name="office">Bridgewater</field>
61
+ # <field name="skills">Perl</field>
62
+ # <field name="skills">Java</field>
63
+ # </doc>
64
+ # </add>
65
+ #
66
+ # # multiple documents
67
+ # add([{ employeeId: '05992', office: 'Blackwater' }, { employeeId: '05993', skills: 'Ruby' }])
68
+ #
69
+ # <?xml version="1.0" encoding="UTF-8"?>
70
+ # <add>
71
+ # <doc>
72
+ # <field name="employeeId">05992</field>
73
+ # <field name="office">Blackwater</field>
74
+ # </doc>
75
+ # <doc>
76
+ # <field name="employeeId">05993</field>
77
+ # <field name="skills">Ruby</field>
78
+ # </doc>
79
+ # </add>
80
+ #
81
+ # # add attributes
82
+ # add([id: 42, text: 'blah'], commitWithin: 23)
83
+ #
84
+ # <?xml version="1.0" encoding="UTF-8"?>
85
+ # <add commitWithin="23">
86
+ # <doc>
87
+ # <field name="id">42</field>
88
+ # <field name="text">blah</field>
89
+ # </doc>
90
+ # </add>
91
+ #
92
+ # # document attributes
93
+ # add([[{ id: 42, text: 'blah' }, { boost: 10.0 }]])
94
+ #
95
+ # <?xml version="1.0" encoding="UTF-8"?>
96
+ # <add>
97
+ # <doc boost="10.0">
98
+ # <field name="id">42</field>
99
+ # <field name="text">blah</field>
100
+ # </doc>
101
+ # </add>
102
+ #
103
+ # # field attributes
104
+ # add(id: 42, text: ['blah', boost: 2.0])
105
+ #
106
+ # <?xml version="1.0" encoding="UTF-8"?>
107
+ # <add>
108
+ # <doc>
109
+ # <field name="id">42</field>
110
+ # <field boost="2.0" name="text">blah</field>
111
+ # </doc>
112
+ # </add>
113
+ #
114
+ # # all attributes together
115
+ # add([[{ id: 42, text: ['blah', boost: 2.0] }, { boost: 10.0 }]], commitWithin: 23)
116
+ #
117
+ # <?xml version="1.0" encoding="UTF-8"?>
118
+ # <add commitWithin="23">
119
+ # <doc boost="10.0">
120
+ # <field name="id">42</field>
121
+ # <field boost="2.0" name="text">blah</field>
122
+ # </doc>
123
+ # </add>
124
+ def add(doc, attributes = {})
125
+ to_xml(:add, attributes) { |add_node|
126
+ doc = [doc] unless doc.is_a?(Array)
127
+ doc.each { |hash, doc_attributes|
128
+ doc_(doc_attributes) { |doc_node|
129
+ hash.each { |key, values|
130
+ values = values.is_a?(Array) ? values.dup : [values]
131
+
132
+ field_attributes = values.last.is_a?(Hash) ? values.pop : {}
133
+ field_attributes = field_attributes.merge(name: key)
134
+
135
+ values.each { |value| doc_node.field_(value, field_attributes) }
136
+ }
137
+ }
138
+ }
139
+ }
140
+ end
141
+
142
+ # See Schema[http://wiki.apache.org/solr/UpdateXmlMessages#A.22commit.22_and_.22optimize.22].
143
+ #
144
+ # Examples:
145
+ #
146
+ # commit
147
+ #
148
+ # <?xml version="1.0" encoding="UTF-8"?>
149
+ # <commit/>
150
+ #
151
+ # commit(softCommit: true)
152
+ #
153
+ # <?xml version="1.0" encoding="UTF-8"?>
154
+ # <commit softCommit="true"/>
155
+ def commit(attributes = {})
156
+ to_xml(:commit, attributes)
157
+ end
158
+
159
+ # See Schema[http://wiki.apache.org/solr/UpdateXmlMessages#A.22commit.22_and_.22optimize.22].
160
+ #
161
+ # Examples:
162
+ #
163
+ # optimize
164
+ #
165
+ # <?xml version="1.0" encoding="UTF-8"?>
166
+ # <optimize/>
167
+ #
168
+ # optimize(maxSegments: 42)
169
+ #
170
+ # <?xml version="1.0" encoding="UTF-8"?>
171
+ # <optimize maxSegments="42"/>
172
+ def optimize(attributes = {})
173
+ to_xml(:optimize, attributes)
174
+ end
175
+
176
+ # See Schema[http://wiki.apache.org/solr/UpdateXmlMessages#A.22rollback.22].
177
+ #
178
+ # Example:
179
+ #
180
+ # rollback
181
+ #
182
+ # <?xml version="1.0" encoding="UTF-8"?>
183
+ # <rollback/>
184
+ def rollback
185
+ to_xml(:rollback)
186
+ end
187
+
188
+ # See Schema[http://wiki.apache.org/solr/UpdateXmlMessages#A.22delete.22_documents_by_ID_and_by_Query].
189
+ #
190
+ # Examples:
191
+ #
192
+ # # single ID
193
+ # delete(id: '05991')
194
+ #
195
+ # <?xml version="1.0" encoding="UTF-8"?>
196
+ # <delete>
197
+ # <id>05991</id>
198
+ # </delete>
199
+ #
200
+ # # multiple IDs
201
+ # delete(id: %w[05991 06000])
202
+ #
203
+ # <?xml version="1.0" encoding="UTF-8"?>
204
+ # <delete>
205
+ # <id>05991</id>
206
+ # <id>06000</id>
207
+ # </delete>
208
+ #
209
+ # # single query
210
+ # delete(query: 'office:Bridgewater')
211
+ #
212
+ # <?xml version="1.0" encoding="UTF-8"?>
213
+ # <delete>
214
+ # <query>office:Bridgewater</query>
215
+ # </delete>
216
+ #
217
+ # # multiple queries
218
+ # delete(query: %w[office:Bridgewater office:Osaka])
219
+ #
220
+ # <?xml version="1.0" encoding="UTF-8"?>
221
+ # <delete>
222
+ # <query>office:Bridgewater</query>
223
+ # <query>office:Osaka</query>
224
+ # </delete>
225
+ #
226
+ # # query hash
227
+ # delete(query: { office: 'Bridgewater', skills: 'Perl' })
228
+ #
229
+ # <?xml version="1.0" encoding="UTF-8"?>
230
+ # <delete>
231
+ # <query>office:Bridgewater</query>
232
+ # <query>skills:Perl</query>
233
+ # </delete>
234
+ #
235
+ # # query hash with array
236
+ # delete(query: { office: %w[Bridgewater Osaka] })
237
+ #
238
+ # <?xml version="1.0" encoding="UTF-8"?>
239
+ # <delete>
240
+ # <query>office:Bridgewater</query>
241
+ # <query>office:Osaka</query>
242
+ # </delete>
243
+ #
244
+ # # both IDs and queries
245
+ # delete(id: %w[05991 06000], query: { office: %w[Bridgewater Osaka] })
246
+ #
247
+ # <?xml version="1.0" encoding="UTF-8"?>
248
+ # <delete>
249
+ # <id>05991</id>
250
+ # <id>06000</id>
251
+ # <query>office:Bridgewater</query>
252
+ # <query>office:Osaka</query>
253
+ # </delete>
254
+ def delete(hash)
255
+ to_xml(:delete) { |delete_node|
256
+ hash.each { |key, values|
257
+ values = [values] unless values.is_a?(Array)
258
+ values.each { |value|
259
+ ary = []
260
+
261
+ if value.is_a?(Hash)
262
+ value.each { |k, v| Array(v).each { |w| ary << "#{k}:#{w}" } }
263
+ else
264
+ ary << value
265
+ end
266
+
267
+ ary.each { |v| delete_node.method_missing(key, v) }
268
+ }
269
+ }
270
+ }
271
+ end
272
+
273
+ def inspect
274
+ '#<%s:0x%x @options=%p>' % [
275
+ self.class, object_id, @options
276
+ ]
277
+ end
278
+
279
+ private
280
+
281
+ def to_xml(name, attributes = {}, &block)
282
+ self.parent = self.doc = @_solr_doc.dup
283
+
284
+ method_missing(name, attributes, &block)
285
+
286
+ super(&nil)
287
+ end
288
+
289
+ end
290
+
291
+ end
@@ -0,0 +1,184 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ ###############################################################################
5
+ # #
6
+ # solr4r -- A Ruby client for Apache Solr #
7
+ # #
8
+ # Copyright (C) 2014 Jens Wille #
9
+ # #
10
+ # Mir is free software: you can redistribute it and/or modify it under the #
11
+ # terms of the GNU Affero General Public License as published by the Free #
12
+ # Software Foundation, either version 3 of the License, or (at your option) #
13
+ # any later version. #
14
+ # #
15
+ # solr4r is distributed in the hope that it will be useful, but WITHOUT ANY #
16
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
17
+ # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for #
18
+ # more details. #
19
+ # #
20
+ # You should have received a copy of the GNU Affero General Public License #
21
+ # along with solr4r. If not, see <http://www.gnu.org/licenses/>. #
22
+ # #
23
+ ###############################################################################
24
+ #++
25
+
26
+ require 'uri'
27
+
28
+ module Solr4R
29
+
30
+ class Client
31
+
32
+ DEFAULT_HOST = 'localhost'
33
+ DEFAULT_PATH = 'solr/'
34
+ DEFAULT_PORT = 8983
35
+
36
+ DEFAULT_PARAMS = {
37
+ wt: :json
38
+ }
39
+
40
+ DEFAULT_ENDPOINTS = %w[select query spell suggest terms] <<
41
+ ['ping', path: 'admin/ping', method: :head] <<
42
+ ['dump', path: 'debug/dump']
43
+
44
+ DEFAULT_SELECT_PATH = 'select'
45
+
46
+ DEFAULT_UPDATE_PATH = 'update'
47
+
48
+ MATCH_ALL_QUERY = '*:*'
49
+
50
+ def initialize(options = {})
51
+ if options.is_a?(String)
52
+ url, options = options, {}
53
+ else
54
+ url = options.fetch(:url, default_url(options))
55
+ end
56
+
57
+ self.url, self.options = URI.parse(url), options
58
+
59
+ self.request = options.fetch(:request, Request.new)
60
+ self.builder = options.fetch(:builder, Builder.new)
61
+
62
+ self.default_params = options.fetch(:default_params, DEFAULT_PARAMS)
63
+
64
+ register_endpoints(options.fetch(:endpoints, DEFAULT_ENDPOINTS))
65
+ end
66
+
67
+ attr_accessor :url, :options, :request, :builder, :default_params
68
+
69
+ def register_endpoints(endpoints)
70
+ endpoints.each { |args| register_endpoint(*args) } if endpoints
71
+ self
72
+ end
73
+
74
+ def register_endpoint(path, options = {})
75
+ name, path = path, options.fetch(:path, path)
76
+
77
+ if error = invalid_endpoint?(name.to_s)
78
+ raise ArgumentError, "invalid endpoint: #{name} (#{error})"
79
+ else
80
+ define_singleton_method(name) { |_params = {}, _options = {}, &block|
81
+ send_request(path, options.merge(_options.merge(
82
+ params: options.fetch(:params, {}).merge(_params))), &block)
83
+ }
84
+ end
85
+
86
+ self
87
+ end
88
+
89
+ def get(path, params = {}, options = {}, &block)
90
+ send_request(path, options.merge(method: :get, params: params), &block)
91
+ end
92
+
93
+ def post(path, data = nil, options = {}, &block)
94
+ send_request(path, options.merge(method: :post, data: data), &block)
95
+ end
96
+
97
+ def head(path, params = {}, options = {}, &block)
98
+ send_request(path, options.merge(method: :head, params: params), &block)
99
+ end
100
+
101
+ def update(data, options = {}, path = DEFAULT_UPDATE_PATH, &block)
102
+ options = amend_options(options, :headers, 'Content-Type' => 'text/xml')
103
+ post(path, data, options, &block)
104
+ end
105
+
106
+ # See Builder#add.
107
+ def add(doc, attributes = {}, options = {}, &block)
108
+ update(builder.add(doc, attributes), options, &block)
109
+ end
110
+
111
+ # See Builder#commit.
112
+ def commit(attributes = {}, options = {}, &block)
113
+ update(builder.commit(attributes), options, &block)
114
+ end
115
+
116
+ # See Builder#optimize.
117
+ def optimize(attributes = {}, options = {}, &block)
118
+ update(builder.optimize(attributes), options, &block)
119
+ end
120
+
121
+ # See Builder#rollback.
122
+ def rollback(options = {}, &block)
123
+ update(builder.rollback, options, &block)
124
+ end
125
+
126
+ # See Builder#delete.
127
+ def delete(hash, options = {}, &block)
128
+ update(builder.delete(hash), options, &block)
129
+ end
130
+
131
+ # See #delete.
132
+ def delete_by_id(id, options = {}, &block)
133
+ delete({ id: id }, options, &block)
134
+ end
135
+
136
+ # See #delete.
137
+ def delete_by_query(query, options = {}, &block)
138
+ delete({ query: query }, options, &block)
139
+ end
140
+
141
+ # See #delete_by_query.
142
+ def delete_all!(options = {}, &block)
143
+ delete_by_query(MATCH_ALL_QUERY, options, &block)
144
+ end
145
+
146
+ def count(params = {}, options = {}, path = DEFAULT_SELECT_PATH, &block)
147
+ params = params.merge(rows: 0)
148
+ params[:q] ||= MATCH_ALL_QUERY
149
+ get(path, params, options, &block)
150
+ end
151
+
152
+ def inspect
153
+ '#<%s:0x%x @url=%p, @default_params=%p>' % [
154
+ self.class, object_id, url, default_params
155
+ ]
156
+ end
157
+
158
+ private
159
+
160
+ def default_url(options)
161
+ 'http://%s:%d/%s' % [
162
+ options.fetch(:host, DEFAULT_HOST),
163
+ options.fetch(:port, DEFAULT_PORT),
164
+ options.fetch(:path, DEFAULT_PATH)
165
+ ]
166
+ end
167
+
168
+ def amend_options(options, key, value)
169
+ options.merge(key => value.merge(options.fetch(key, {})))
170
+ end
171
+
172
+ def send_request(path, options, &block)
173
+ options = amend_options(options, :params, default_params)
174
+ request.execute(URI.join(url, path), options, &block)
175
+ end
176
+
177
+ def invalid_endpoint?(name)
178
+ 'method already defined' if respond_to?(name) || (
179
+ respond_to?(name, true) && !DEFAULT_ENDPOINTS.flatten.include?(name))
180
+ end
181
+
182
+ end
183
+
184
+ end
@@ -0,0 +1,122 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ ###############################################################################
5
+ # #
6
+ # solr4r -- A Ruby client for Apache Solr #
7
+ # #
8
+ # Copyright (C) 2014 Jens Wille #
9
+ # #
10
+ # Mir is free software: you can redistribute it and/or modify it under the #
11
+ # terms of the GNU Affero General Public License as published by the Free #
12
+ # Software Foundation, either version 3 of the License, or (at your option) #
13
+ # any later version. #
14
+ # #
15
+ # solr4r is distributed in the hope that it will be useful, but WITHOUT ANY #
16
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
17
+ # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for #
18
+ # more details. #
19
+ # #
20
+ # You should have received a copy of the GNU Affero General Public License #
21
+ # along with solr4r. If not, see <http://www.gnu.org/licenses/>. #
22
+ # #
23
+ ###############################################################################
24
+ #++
25
+
26
+ require 'curl'
27
+
28
+ module Solr4R
29
+
30
+ class Request < Curl::Easy
31
+
32
+ DEFAULT_VERB = :get
33
+
34
+ DEFAULT_USER_AGENT = "Solr4R/#{VERSION}"
35
+
36
+ RESPONSE_ATTRIBUTES = {
37
+ # request
38
+ headers: :request_headers,
39
+ last_effective_url: :request_url,
40
+ post_body: :post_body,
41
+ params: :request_params,
42
+ verb: :request_verb,
43
+
44
+ # response
45
+ body_str: :response_body,
46
+ content_type: :content_type,
47
+ header_str: :response_header,
48
+ response_code: :response_code
49
+ }
50
+
51
+ def initialize(options = {})
52
+ if block_given?
53
+ raise ArgumentError,
54
+ 'block argument not supported, use options hash instead'
55
+ end
56
+
57
+ super()
58
+
59
+ self.options = options.merge(params: nil, verb: nil)
60
+ set_options
61
+ end
62
+
63
+ attr_accessor :options, :params, :verb
64
+
65
+ def execute(request_url, options = {}, &block)
66
+ prepare_request(request_url, options, &block)
67
+
68
+ send("http_#{verb}")
69
+
70
+ Response.new { |response|
71
+ RESPONSE_ATTRIBUTES.each { |attribute, key|
72
+ response.send("#{key}=", send(attribute))
73
+ }
74
+ }
75
+ end
76
+
77
+ def reset
78
+ super.tap {
79
+ set_options
80
+ headers['User-Agent'] ||= DEFAULT_USER_AGENT
81
+ }
82
+ end
83
+
84
+ def inspect
85
+ '#<%s:0x%x @options=%p, @verb=%p, @url=%p, @response_code=%p>' % [
86
+ self.class, object_id, options, verb, url, response_code
87
+ ]
88
+ end
89
+
90
+ private
91
+
92
+ def set_options
93
+ options.each { |key, value| send("#{key}=", value) }
94
+ end
95
+
96
+ def prepare_request(request_url, options)
97
+ reset
98
+
99
+ self.url = Curl.urlalize(request_url.to_s,
100
+ self.params = options.fetch(:params, {}))
101
+
102
+ case self.verb = options.fetch(:method, DEFAULT_VERB)
103
+ when :get, :head
104
+ # ok
105
+ when :post, :delete, :patch
106
+ self.post_body = Curl.postalize(options[:data])
107
+ when :put
108
+ self.put_data = Curl.postalize(options[:data])
109
+ else
110
+ raise ArgumentError, "verb not supported: #{verb}"
111
+ end
112
+
113
+ headers.update(options[:headers]) if options[:headers]
114
+
115
+ yield self if block_given?
116
+
117
+ self
118
+ end
119
+
120
+ end
121
+
122
+ end