mwmitchell-rsolr 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +9 -0
- data/README.rdoc +28 -43
- data/Rakefile +1 -2
- data/examples/direct.rb +8 -9
- data/examples/http.rb +9 -8
- data/lib/rsolr.rb +10 -14
- data/lib/rsolr/adapter.rb +6 -0
- data/lib/rsolr/{connection/adapter → adapter}/direct.rb +3 -4
- data/lib/rsolr/{connection/adapter → adapter}/http.rb +4 -11
- data/lib/rsolr/connection.rb +104 -3
- data/lib/rsolr/http_client.rb +28 -14
- data/lib/rsolr/http_client/adapter/net_http.rb +1 -1
- data/lib/rsolr/message.rb +5 -6
- data/test/connection/direct_test.rb +19 -20
- data/test/connection/http_test.rb +3 -4
- data/test/connection/test_methods.rb +40 -50
- data/test/http_client/curb_test.rb +3 -4
- data/test/http_client/net_http_test.rb +3 -4
- data/test/http_client/test_methods.rb +1 -1
- data/test/http_client/util_test.rb +1 -1
- data/test/message_test.rb +8 -7
- metadata +6 -20
- data/lib/rsolr/connection/adapter.rb +0 -7
- data/lib/rsolr/connection/adapter/common_methods.rb +0 -50
- data/lib/rsolr/connection/base.rb +0 -101
- data/lib/rsolr/query.rb +0 -58
- data/lib/rsolr/response.rb +0 -8
- data/lib/rsolr/response/base.rb +0 -25
- data/lib/rsolr/response/index_info.rb +0 -33
- data/lib/rsolr/response/query.rb +0 -163
- data/lib/rsolr/response/update.rb +0 -4
- data/test/query_helper_test.rb +0 -30
- data/test/response/base_test.rb +0 -38
- data/test/response/pagination_test.rb +0 -47
- data/test/response/query_test.rb +0 -44
- data/test/test_helpers.rb +0 -61
data/CHANGES.txt
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
0.8.0 - March 6, 2009
|
2
|
+
Removed all response wrapper classes (now returning a simple hash for ruby responses)
|
3
|
+
Removed RSolr::Query - this library needs an external partner lib, RSolrExt etc..
|
4
|
+
changed query method to select
|
5
|
+
added send_request method to Connection for custom requests:
|
6
|
+
send_request '/my-handler', {:start=>0}, post_data=nil
|
7
|
+
moved Connection::Base to Connection
|
8
|
+
moved Connection::Adapter::* to Adapter::*
|
9
|
+
|
1
10
|
0.7.1 - February 27, 2009
|
2
11
|
Added simple query helper module -> RSolr::Query
|
3
12
|
Added tests for RSolr::Query
|
data/README.rdoc
CHANGED
@@ -2,17 +2,25 @@
|
|
2
2
|
|
3
3
|
A Ruby client for Apache Solr. Has transparent JRuby support by using "org.apache.solr.servlet.DirectSolrConnection" as a connection adapter.
|
4
4
|
|
5
|
+
=NOTE
|
6
|
+
Please look at the latest code/branch here: http://github.com/mwmitchell/rsolr/tree/no-response-wrap
|
7
|
+
The mapping/response helper stuff in master, will be extracted out into a separate Gem.
|
8
|
+
|
5
9
|
==Installation:
|
6
10
|
gem sources -a http://gems.github.com
|
7
11
|
sudo gem install mwmitchell-rsolr
|
8
12
|
|
9
|
-
|
13
|
+
==Community
|
14
|
+
http://groups.google.com/group/rsolr
|
15
|
+
|
16
|
+
==Simple usage:
|
10
17
|
require 'rubygems'
|
11
18
|
require 'rsolr'
|
12
19
|
rsolr = RSolr.connect
|
13
|
-
response = rsolr.
|
20
|
+
response = rsolr.select(:q=>'*:*') # becomes /solr/select?q=*:*
|
21
|
+
|
14
22
|
# can also set the request handler path like:
|
15
|
-
response = rsolr.
|
23
|
+
response = rsolr.send_request('/catalog', :q=>'*:*') # becomes /solr/catalog?q=*:*
|
16
24
|
|
17
25
|
To run tests:
|
18
26
|
|
@@ -27,46 +35,15 @@ To get a connection in MRI/standard Ruby:
|
|
27
35
|
|
28
36
|
To get a direct connection (no http) in jRuby using DirectSolrConnection:
|
29
37
|
|
30
|
-
solr = RSolr.connect(:adapter=>:direct, :home_dir=>'/path/to/solr/home', :dist_dir=>'/path/to/solr/distribution')
|
31
|
-
|
32
|
-
You can set the request handler paths for every request:
|
33
|
-
|
34
|
-
solr = RSolr.connect(:select_path=>'select', :update_path=>'update', :luke_path=>'admin/luke')
|
38
|
+
solr = RSolr.connect({:adapter=>:direct}, {:home_dir=>'/path/to/solr/home', :dist_dir=>'/path/to/solr/distribution'})
|
35
39
|
|
36
40
|
|
37
41
|
== Requests
|
38
42
|
Once you have a connection, you can execute queries, updates etc..
|
39
43
|
|
40
|
-
You can optionally specify the request handler path by sending it in as the first argument:
|
41
|
-
solr.query 'catalog', :q=>'object_type:"book"'
|
42
|
-
solr.update 'my/update', '<xml/>'
|
43
|
-
|
44
|
-
The default request handler path value for each of the different methods are as follows:
|
45
|
-
find_by_id, query == 'select'
|
46
|
-
add, update, commit, optimize, rollback, delete_by_id, delete_by_query == 'update'
|
47
|
-
index_info == 'admin/luke'
|
48
|
-
|
49
|
-
Please note that the path you specify should be relative.
|
50
|
-
|
51
|
-
|
52
44
|
=== Querying
|
53
|
-
Use the #
|
54
|
-
response = solr.
|
55
|
-
response = solr.find_by_id(1)
|
56
|
-
|
57
|
-
==== Pagination
|
58
|
-
Pagination is simplified by having a few helpful response methods:
|
59
|
-
|
60
|
-
response = solr.query(:start=>0, :rows=>10, :q=>'*:*')
|
61
|
-
response.per_page
|
62
|
-
response.total_pages
|
63
|
-
response.current_page
|
64
|
-
response.previous_page
|
65
|
-
response.next_page
|
66
|
-
|
67
|
-
If you use WillPaginate, just pass-in the response to the #will_paginate view helper:
|
68
|
-
|
69
|
-
<%= will_paginate(@response) %>
|
45
|
+
Use the #select method to send requests to the /select handler:
|
46
|
+
response = solr.select(:q=>'washington', :facet=>true, 'facet.limit'=>-1, 'facet.field'=>'cat', 'facet.field'=>'inStock', :start=>0, :rows=>10)
|
70
47
|
|
71
48
|
|
72
49
|
=== Updating Solr
|
@@ -102,24 +79,32 @@ Commit & Optimize
|
|
102
79
|
|
103
80
|
|
104
81
|
== Response Formats
|
105
|
-
The default response format is Ruby. When the :wt param is set to :ruby, the response is eval'd and wrapped up in a nice
|
82
|
+
The default response format is Ruby. When the :wt param is set to :ruby, the response is eval'd and wrapped up in a nice Mash (Hash) class. You can get a raw response by setting the :wt to "ruby" - notice, the string -- not a symbol. All other response formats are available as expected, :wt=>'xml' etc..
|
83
|
+
|
84
|
+
You can access the original request context (path, params, url etc.) by using a block:
|
85
|
+
solr.select(:q=>'*:*') do |solr_response, adapter_response|
|
86
|
+
adapter_response[:status_code]
|
87
|
+
adapter_response[:body]
|
88
|
+
adapter_response[:url]
|
89
|
+
end
|
106
90
|
|
107
|
-
|
91
|
+
The adapter_response is a hash that contains the generated params, url, path, post data, headers etc., very useful for debugging and testing.
|
108
92
|
|
109
93
|
|
110
94
|
== HTTP Client Adapter
|
111
|
-
You can specify the http client adapter to use by setting
|
95
|
+
You can specify the http client adapter to use by setting solr.adapter.connector.adapter_name to one of:
|
112
96
|
:net_http uses the standard Net::HTTP library
|
113
97
|
:curb uses the Ruby "curl" bindings
|
114
98
|
|
115
99
|
Example:
|
116
100
|
|
117
|
-
|
101
|
+
solr.adapter.connector.adapter_name = :curb
|
118
102
|
|
119
103
|
Example of using the HTTP client only:
|
120
104
|
|
121
|
-
hclient = RSolr::HTTPClient.
|
122
|
-
hclient = RSolr::HTTPClient.
|
105
|
+
hclient = RSolr::HTTPClient::Connector.new(:curb).connect(url)
|
106
|
+
hclient = RSolr::HTTPClient::Connector.new(:net_http).connect(url)
|
107
|
+
hclient.get('/')
|
123
108
|
|
124
109
|
After reading this http://apocryph.org/2008/11/09/more_indepth_analysis_ruby_http_client_performance - I would recommend using the :curb adapter. NOTE: You can't use the :curb adapter under jRuby. To install curb:
|
125
110
|
|
data/Rakefile
CHANGED
@@ -2,8 +2,6 @@ require 'rake'
|
|
2
2
|
require 'rake/testtask'
|
3
3
|
require 'rake/rdoctask'
|
4
4
|
|
5
|
-
require File.join(File.dirname(__FILE__), 'lib', 'rsolr')
|
6
|
-
|
7
5
|
namespace :rsolr do
|
8
6
|
|
9
7
|
desc "Starts the HTTP server used for running HTTP connection tests"
|
@@ -20,6 +18,7 @@ Rake::TestTask.new("test_units") { |t|
|
|
20
18
|
t.pattern = 'test/**/*_test.rb'
|
21
19
|
t.verbose = true
|
22
20
|
t.warning = true
|
21
|
+
t.libs << "test"
|
23
22
|
}
|
24
23
|
|
25
24
|
# Clean house
|
data/examples/direct.rb
CHANGED
@@ -5,17 +5,16 @@ base = File.expand_path( File.dirname(__FILE__) )
|
|
5
5
|
dist = File.join(base, '..', 'apache-solr')
|
6
6
|
home = File.join(dist, 'example', 'solr')
|
7
7
|
|
8
|
-
solr = RSolr.connect(:adapter=>:direct, :home_dir=>home, :dist_dir=>dist)
|
8
|
+
solr = RSolr.connect({:adapter=>:direct}, {:home_dir=>home, :dist_dir=>dist})
|
9
9
|
|
10
10
|
`cd ../apache-solr/example/exampledocs && ./post.sh ./*.xml`
|
11
11
|
|
12
|
-
|
13
|
-
response = solr.query 'select', :q=>'ipod', :fq=>'price:[0 TO 50]', :rows=>2, :start=>0
|
12
|
+
response = solr.select :q=>'ipod', :fq=>'price:[0 TO 50]', :rows=>2, :start=>0
|
14
13
|
|
15
|
-
|
14
|
+
docs = response[:response][:docs]
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
16
|
+
docs.each do |doc|
|
17
|
+
puts doc[:timestamp]
|
18
|
+
end
|
19
|
+
|
20
|
+
solr.delete_by_query('*:*') and solr.commit
|
data/examples/http.rb
CHANGED
@@ -1,16 +1,17 @@
|
|
1
|
-
# Must be executed using jruby
|
2
1
|
require File.join(File.dirname(__FILE__), '..', 'lib', 'rsolr')
|
3
2
|
|
4
3
|
solr = RSolr.connect
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
response = solr.query :q=>'ipod', :fq=>'price:[0 TO 50]', :rows=>2, :start=>0
|
5
|
+
# switch out the http adapter from curb to net_http (just for an example)
|
6
|
+
solr.adapter.connector.adapter_name = :net_http
|
9
7
|
|
10
|
-
solr.
|
8
|
+
`cd ../apache-solr/example/exampledocs && ./post.sh ./*.xml`
|
11
9
|
|
12
|
-
|
13
|
-
|
10
|
+
solr.select(:q=>'ipod', :fq=>'price:[0 TO 50]', :rows=>2, :start=>0) do |solr_response,adapter_response|
|
11
|
+
puts "URL : #{adapter_response[:url]}"
|
12
|
+
solr_response[:response][:docs].each do |doc|
|
14
13
|
puts doc[:timestamp]
|
15
14
|
end
|
16
|
-
end
|
15
|
+
end
|
16
|
+
|
17
|
+
solr.delete_by_query('*:*') and solr.commit
|
data/lib/rsolr.rb
CHANGED
@@ -7,30 +7,26 @@ proc {|base, files|
|
|
7
7
|
|
8
8
|
module RSolr
|
9
9
|
|
10
|
-
VERSION = '0.
|
10
|
+
VERSION = '0.8.0'
|
11
11
|
|
12
12
|
autoload :Message, 'rsolr/message'
|
13
|
-
autoload :Response, 'rsolr/response'
|
14
13
|
autoload :Connection, 'rsolr/connection'
|
15
|
-
autoload :
|
14
|
+
autoload :Adapter, 'rsolr/adapter'
|
16
15
|
autoload :HTTPClient, 'rsolr/http_client'
|
17
|
-
autoload :Query, 'rsolr/query'
|
18
16
|
|
19
17
|
# factory for creating connections
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
def self.connect(
|
24
|
-
adapter_name =
|
18
|
+
# connection_opts[:adapter] is either :http or :direct
|
19
|
+
# connection_opts are sent to the connection instance
|
20
|
+
# adapter_opts are passed to the actually adapter instance
|
21
|
+
def self.connect(connection_opts={}, adapter_opts={})
|
22
|
+
adapter_name = connection_opts[:adapter] ||= :http
|
25
23
|
types = {
|
26
24
|
:http=>'HTTP',
|
27
25
|
:direct=>'Direct'
|
28
26
|
}
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
adapter_class = RSolr::Connection::Adapter.const_get(types[adapter_name])
|
33
|
-
RSolr::Connection::Base.new(adapter_class.new(opts), opts)
|
27
|
+
adapter_class = RSolr::Adapter.const_get(types[adapter_name])
|
28
|
+
adapter = adapter_class.new(adapter_opts)
|
29
|
+
RSolr::Connection.new(adapter, connection_opts)
|
34
30
|
end
|
35
31
|
|
36
32
|
class RequestError < RuntimeError; end
|
@@ -5,10 +5,9 @@ require 'java'
|
|
5
5
|
#
|
6
6
|
# Connection for JRuby + DirectSolrConnection
|
7
7
|
#
|
8
|
-
class RSolr::
|
8
|
+
class RSolr::Adapter::Direct
|
9
9
|
|
10
10
|
include RSolr::HTTPClient::Util
|
11
|
-
include RSolr::Connection::Adapter::CommonMethods
|
12
11
|
|
13
12
|
attr_accessor :opts, :home_dir
|
14
13
|
|
@@ -58,13 +57,13 @@ class RSolr::Connection::Adapter::Direct
|
|
58
57
|
raise RSolr::RequestError.new($!.message)
|
59
58
|
end
|
60
59
|
{
|
61
|
-
:status_code=>
|
60
|
+
:status_code=>nil,
|
62
61
|
:body=>body,
|
63
62
|
:url=>url,
|
64
63
|
:path=>path,
|
65
64
|
:params=>params,
|
66
65
|
:data=>data,
|
67
|
-
:headers=>
|
66
|
+
:headers=>nil
|
68
67
|
}
|
69
68
|
end
|
70
69
|
|
@@ -1,17 +1,9 @@
|
|
1
1
|
#
|
2
2
|
# Connection for standard HTTP Solr server
|
3
3
|
#
|
4
|
-
class RSolr::
|
4
|
+
class RSolr::Adapter::HTTP
|
5
5
|
|
6
|
-
|
7
|
-
attr_accessor :client_adapter
|
8
|
-
end
|
9
|
-
|
10
|
-
@client_adapter = :net_http
|
11
|
-
|
12
|
-
include RSolr::Connection::Adapter::CommonMethods
|
13
|
-
|
14
|
-
attr_reader :opts
|
6
|
+
attr_reader :opts, :connector, :connection
|
15
7
|
|
16
8
|
# opts can have:
|
17
9
|
# :url => 'http://localhost:8080/solr'
|
@@ -22,10 +14,11 @@ class RSolr::Connection::Adapter::HTTP
|
|
22
14
|
def initialize(opts={}, &block)
|
23
15
|
opts[:url]||='http://127.0.0.1:8983/solr'
|
24
16
|
@opts = opts
|
17
|
+
@connector = RSolr::HTTPClient::Connector.new
|
25
18
|
end
|
26
19
|
|
27
20
|
def connection
|
28
|
-
@connection ||=
|
21
|
+
@connection ||= @connector.connect(@opts[:url])
|
29
22
|
end
|
30
23
|
|
31
24
|
# send a request to the connection
|
data/lib/rsolr/connection.rb
CHANGED
@@ -1,6 +1,107 @@
|
|
1
|
-
|
1
|
+
class RSolr::Connection
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
attr_reader :adapter, :opts
|
4
|
+
|
5
|
+
# "adapter" is instance of:
|
6
|
+
# RSolr::Adapter::HTTP
|
7
|
+
# RSolr::Adapter::Direct (jRuby only)
|
8
|
+
def initialize(adapter, opts={})
|
9
|
+
@adapter = adapter
|
10
|
+
@opts = opts
|
11
|
+
end
|
12
|
+
|
13
|
+
# send a request to the "select" handler
|
14
|
+
def select(params, &blk)
|
15
|
+
send_request('/select', map_params(params), &blk)
|
16
|
+
end
|
17
|
+
|
18
|
+
# sends data to the update handler
|
19
|
+
# data can be a string of xml, or an object that returns xml from its #to_s method
|
20
|
+
def update(data, params={}, &blk)
|
21
|
+
send_request('/update', map_params(params), data, &blk)
|
22
|
+
end
|
23
|
+
|
24
|
+
# send request solr
|
25
|
+
# params is hash with valid solr request params (:q, :fl, :qf etc..)
|
26
|
+
# if params[:wt] is not set, the default is :ruby
|
27
|
+
# if :wt is something other than :ruby, the raw response body is used
|
28
|
+
# otherwise, a simple Hash is returned
|
29
|
+
# NOTE: to get raw ruby, use :wt=>'ruby' <- a string, not a symbol like :ruby
|
30
|
+
#
|
31
|
+
# use a block to get access to the adapter response:
|
32
|
+
# solr.send_request('/select', :q=>'blue') do |solr_response, adapter_response|
|
33
|
+
# raise 'Woops!' if adapter_response[:status] != 200
|
34
|
+
# solr_response[:response][:docs].each {|doc|}
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
def send_request(path, params={}, data=nil, &blk)
|
38
|
+
response = @adapter.send_request(path, map_params(params), data)
|
39
|
+
adapt_response(response, &blk)
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# single record:
|
44
|
+
# solr.update(:id=>1, :name=>'one')
|
45
|
+
#
|
46
|
+
# update using an array
|
47
|
+
# solr.update([{:id=>1, :name=>'one'}, {:id=>2, :name=>'two'}])
|
48
|
+
#
|
49
|
+
def add(doc, &block)
|
50
|
+
update message.add(doc, &block)
|
51
|
+
end
|
52
|
+
|
53
|
+
# send </commit>
|
54
|
+
def commit
|
55
|
+
update message.commit
|
56
|
+
end
|
57
|
+
|
58
|
+
# send </optimize>
|
59
|
+
def optimize
|
60
|
+
update message.optimize
|
61
|
+
end
|
62
|
+
|
63
|
+
# send </rollback>
|
64
|
+
# NOTE: solr 1.4 only
|
65
|
+
def rollback
|
66
|
+
update message.rollback
|
67
|
+
end
|
68
|
+
|
69
|
+
# Delete one or many documents by id
|
70
|
+
# solr.delete_by_id 10
|
71
|
+
# solr.delete_by_id([12, 41, 199])
|
72
|
+
def delete_by_id(id)
|
73
|
+
update message.delete_by_id(id)
|
74
|
+
end
|
75
|
+
|
76
|
+
# delete one or many documents by query
|
77
|
+
# solr.delete_by_query 'available:0'
|
78
|
+
# solr.delete_by_query ['quantity:0', 'manu:"FQ"']
|
79
|
+
def delete_by_query(query)
|
80
|
+
update message.delete_by_query(query)
|
81
|
+
end
|
82
|
+
|
83
|
+
protected
|
84
|
+
|
85
|
+
# shortcut to solr::message
|
86
|
+
def message
|
87
|
+
RSolr::Message
|
88
|
+
end
|
89
|
+
|
90
|
+
# sets default params etc.. - could be used as a mapping hook
|
91
|
+
# type of request should be passed in here? -> map_params(:query, {})
|
92
|
+
def map_params(params)
|
93
|
+
params||={}
|
94
|
+
{:wt=>:ruby}.merge(params)
|
95
|
+
end
|
96
|
+
|
97
|
+
#
|
98
|
+
def adapt_response(adapter_response)
|
99
|
+
if adapter_response[:params][:wt] == :ruby
|
100
|
+
data = Kernel.eval(adapter_response[:body]).to_mash
|
101
|
+
else
|
102
|
+
data = adapter_response[:body]
|
103
|
+
end
|
104
|
+
block_given? ? yield(data, adapter_response) : data
|
105
|
+
end
|
5
106
|
|
6
107
|
end
|
data/lib/rsolr/http_client.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# A simple wrapper for different http client implementations.
|
2
2
|
# Supports #get and #post
|
3
3
|
# This was motivated by: http://apocryph.org/2008/11/09/more_indepth_analysis_ruby_http_client_performance/
|
4
|
-
#
|
4
|
+
# Curb is the default adapter
|
5
5
|
|
6
6
|
# Each adapters response should be a hash with the following keys:
|
7
7
|
# :status_code
|
@@ -13,31 +13,45 @@
|
|
13
13
|
# :headers
|
14
14
|
|
15
15
|
# Example:
|
16
|
-
#
|
16
|
+
# connector = RSolr::HTTPClient.Connector.new
|
17
|
+
# connector.adapter_name = :net_http # switch to Net::HTTP before calling "connect"
|
18
|
+
# hclient = connector.connect('http://www.google.com')
|
17
19
|
# response = hclient.get('/search', :hl=>:en, :q=>:ruby, :btnG=>:Search)
|
18
20
|
# puts response[:status_code]
|
19
21
|
# puts response[:body]
|
20
22
|
|
23
|
+
require 'uri'
|
24
|
+
|
21
25
|
module RSolr::HTTPClient
|
22
26
|
|
23
27
|
autoload :Adapter, 'rsolr/http_client/adapter'
|
24
28
|
|
25
29
|
class UnkownAdapterError < RuntimeError; end
|
26
30
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
else
|
34
|
-
raise UnkownAdapterError.new("Name: #{adapter_name}")
|
31
|
+
class Connector
|
32
|
+
|
33
|
+
attr_accessor :adapter_name
|
34
|
+
|
35
|
+
def initialize(adapter_name = :curb)
|
36
|
+
@adapter_name = adapter_name
|
35
37
|
end
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
|
39
|
+
def connect(url)
|
40
|
+
case adapter_name
|
41
|
+
when :curb
|
42
|
+
klass = 'Curb'
|
43
|
+
when :net_http
|
44
|
+
klass = 'NetHTTP'
|
45
|
+
else
|
46
|
+
raise UnkownAdapterError.new("Name: #{adapter_name}")
|
47
|
+
end
|
48
|
+
begin
|
49
|
+
RSolr::HTTPClient::Base.new RSolr::HTTPClient::Adapter.const_get(klass).new(url)
|
50
|
+
rescue ::URI::InvalidURIError
|
51
|
+
raise "#{$!} == #{url}"
|
52
|
+
end
|
40
53
|
end
|
54
|
+
|
41
55
|
end
|
42
56
|
|
43
57
|
class Base
|