mwmitchell-rsolr 0.7.1 → 0.8.0
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.
- 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
|