Ruby/Rails:为http请求创建自定义block/yield以避免重复

Ruby/Rails: create a custom block/yield for http requests to avoid repetition

我想在我的代码中避免过多 copy/paste 的 http 请求,因此我尝试创建以下内容:

post "#{URL}/api/v1/notify" do
  header 'Content-Type', 'application/json'
  header 'Authorization', "Bearer 123456"

  success do
    # what to do in case of success
  end

  error do |e|
    # what to do in case of error
  end
end

但到目前为止,我在创建所需的 post 方法时遇到问题

def post(url, &block)
  puts "post"
 
  url = URI("#{URL}/api/v1/notify")
  http = Net::HTTP.new(url.host, url.port)
  http.use_ssl = url.instance_of?(URI::HTTPS)

  request = Net::HTTP::Post.new(url) # or GET
  request['Content-Type'] = #header content type
  request['Authorization'] = #header bearer
  response = http.request(request)

 response.success? ? success : failure
end

def success(&block)
  puts "success"
  yield
end

def error(&block)
  puts "error"
  yield
end

该方法应该替换以下多次使用的代码:

url = URI("#{URL}/api/v1/notify")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = url.instance_of?(URI::HTTPS)

request = Net::HTTP::Post.new(url) # or GET
request['Content-Type'] = 'application/json'
request['Authorization'] = "Bearer 123456"
response = http.request(request)

if response.code == '200'
  #success
else
  #failure
end

您可以围绕此构建自己的 DSL:

class Client
  def self.post(&block)
    dsl = Dsl.new
    dsl.instance_eval(&block)

    # build request with data from dsl calls
    # and execute request
    puts "Making a request to #{dsl.url} with headers #{dsl.headers}"
    response = "response" # this would be your http response
    if rand > 0.5 # response.success?
      dsl.on_success.call(response) if dsl.on_success
    else
      dsl.on_error.call(response) if dsl.on_error
    end
  end

end

class Dsl
  def initialize
    @headers = {}
  end

  def url(*values)
    return @url if values.length == 0

    @url = values[0]
  end

  def headers
    @headers.freeze
  end

  def header(*values)
    return @headers[values.first] if values.length == 1

    @headers[values[0]] = values[1]
  end

  def on_success(&block)
    return @on_success if !block

    @on_success = block
  end

  def on_error(&block)
    return @on_error if !block

    @on_error = block
  end
end



Client.post do |client|
  url "http://somewhere"
  header "Kwazy", "Cupcakes"
  on_success do |response|
    puts "got #{response} which is a success"
  end

  on_error do |response|
    puts "gor #{response} which is an error"
  end
end

显然,如果您通过可变参数长度调用 getter/setter,区别就有点特殊,也可以通过使用真实的 setter 名称来完成(url=,.. .) 或使用不同的 getter 名称 (get_url)

比较特别的部分是instance_eval(block)。它允许更改 self 以便 post 块内方法的接收者不是 Client 而是 Dsl 实例。

这可能会引起麻烦并使调试变得有点困难。所以你也可以

yield(dsl) 然后将 dsl 作为参数传递给块。

Client.post do |dsl|
  dsl.url "http://somewhere"
  #...
end

更新:

Stefans 的提议听起来也不错。我认为他的意思是这样的(或在评论中查看他的 link):

class DslData
  attr_accessor :url
end


class Dsl
  attr_reader :dsl_data
  def initialize
    @dsl_data = DslData.new
  end 

  def url(value)
    dsl_data.url = value
  end
end

class Client
  def self.post
    dsl = Dsl.new
    dsl.instance_eval(&block)
    puts "Url: #{dsl.dsl_data.url}"
  end
end