6
\$\begingroup\$

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
\$\endgroup\$
5
  • \$\begingroup\$ Do you mean PUT or PATCH? What flag parameter are you talking about? \$\endgroup\$ May 26 '16 at 14:13
  • \$\begingroup\$ One method makes GET and other PUT request the rest of the code is similar and I wanted to differentiate then somehow \$\endgroup\$ May 26 '16 at 14:22
  • \$\begingroup\$ Then why does your code say Net::HTTP::Patch? \$\endgroup\$ May 26 '16 at 14:24
  • \$\begingroup\$ My mistake but that's not relevant to what I was asking \$\endgroup\$ May 26 '16 at 14:36
  • \$\begingroup\$ Every detail is relevant in a code review, because every detail matters when running code. \$\endgroup\$ May 26 '16 at 14:38
6
\$\begingroup\$

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.

\$\endgroup\$
0
2
\$\begingroup\$

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.

\$\endgroup\$
0
1
\$\begingroup\$

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)
\$\endgroup\$
0
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.