I have two very similar methods, which make HTTP requests, the difference is that one makes PUT and another GET. Is there any proper Ruby way not to repeat the setup code and not to pass the flag parameter?
def notify_client(url, params)
uri = URI.parse(url)
https = Net::HTTP.new(uri.host, uri.port)
https.use_ssl = !Rails.env.development?
req = Net::HTTP::Patch.new(uri.path)
req.body = {data: {attributes: params}}.to_json
res = https.request(req)
puts "Response #{res.code} #{res.message}: #{res.body}"
end
def notify_vendor(url, params)
uri = URI.parse(url)
https = Net::HTTP.new(uri.host, uri.port)
https.use_ssl = !Rails.env.development?
req = Net::HTTP::Get.new(uri.path)
req.body = {data: {attributes: params}}.to_json
res = https.request(req)
puts "Response #{res.code} #{res.message}: #{res.body}"
end
4 Answers 4
In such cases I try to move repeated code to separate method that accepts block.
def notify_client(url, params)
notify(url, params) { |path| Net::HTTP::Patch.new(path) }
end
def notify_vendor(url, params)
notify(url, params) { |path| Net::HTTP::Get.new(path) }
end
def notify(url, params)
uri = URI.parse(url)
https = Net::HTTP.new(uri.host, uri.port)
https.use_ssl = !Rails.env.development?
req = yield uri.path
req.body = {data: {attributes: params}}.to_json
res = https.request(req)
puts "Response #{res.code} #{res.message}: #{res.body}"
end
That code refactoring quite simple. If you need more details - let me know.
I like Sergii's solution a lot, but just as an extra idea, this is also possible:
%w(vendor client).each do |subject|
define_method("notify_#{subject}") do |url, params|
uri = URI.parse(url)
https = Net::HTTP.new(uri.host, uri.port)
https.use_ssl = !Rails.env.development?
req = (subject == 'client' ? Net::HTTP::Patch.new(uri.path) : Net::HTTP::Get.new(uri.path))
req.body = {data: {attributes: params}}.to_json
res = https.request(req)
puts "Response #{res.code} #{res.message}: #{res.body}"
end
end
You create two methods dynamically. Again, not as elegant as Sergii's but this will still work.
One way is a block, already shown, the other sending a string/symbol to select the method:
def request(url, params, method)
uri = URI.parse(url)
https = Net::HTTP.new(uri.host, uri.port)
https.use_ssl = !Rails.env.development?
class_obj = Net::HTTP.const_get(method.to_s.capitalize)
req = class_obj.new(uri.path)
req.body = {data: {attributes: params}}.to_json
res = https.request(req)
puts "Response #{res.code} #{res.message}: #{res.body}"
end
In any case, using net/http is slightly masochistic, there are more friendly libraries, for example rest-client:
response = RestClient::Request.execute(method: method, url: url, params: params)
You could inject the class for constructing the request, but this would break the abstraction, as the caller would have to know that we're using Net::HTTP
. You could pass the HTTP verb as a symbol instead and look up the corresponding class like this:
METHOD_CLASS = {get: Net::HTTP::Get, patch: Net::HTTP::Patch}
def notify(url, params, method)
uri = URI.parse(url)
https = Net::HTTP.new(uri.host, uri.port)
https.use_ssl = !Rails.env.development?
req = METHOD_CLASS.fetch(method).new(uri.path)
req.body = {data: {attributes: params}}.to_json
res = https.request(req)
puts "Response #{res.code} #{res.message}: #{res.body}"
end
I'd use a block instead for evaluating the response, printing the response shouldn't be a responsibility of this method. Depending on your use case you might want to yield the response to the block only if the request was successful or if it failed.
Net::HTTP::Patch
? \$\endgroup\$