首页 > 解决方案 > 设置一个 Ruby 类变量,然后在类方法中使用它。HTTP派对

问题描述

我正在尝试创建一个用于充值的 API 包装器(Shopify 订阅服务),我正在使用 HTTParty gem

module RechargeAPI
  require 'httparty'
  BASE_URI = 'https://api.rechargeapps.com'
  API_TOKEN = 'my_token'

  class Client
    include HTTParty
    base_uri BASE_URI
    headers 'X-Recharge-Access-Token': API_TOKEN
  end

  class Customer < Client
    def self.search(params)
      response = get('/customers', query: params)
      self.from_json(response.body)
    end

    def self.find(params)
      self.search(params).first
    end

    def self.all
      response = get('/customers')
      self.from_json(response.body)
    end

    def self.from_json(customers_json)
      customers = JSON.parse(customers_json).dig('customers')
      customers.map do|customer| OpenStruct.new(customer)
      end
    end

  end

end

RechargeAPI::Customer.find(shopify_customer_id: 5363543224286) # returns <OpenStruct accepts_marketing=nil, analytics_data={"utm_params"=>[]}, billing_address1=....

它工作正常,但是我觉得我没有使用编写 api 包装器的最佳实践。理想情况下,我会设置我的 api 令牌,RechargeAPI.api_token = 'token'而不是硬编码或在 ENV 文件中。但我不知道我会如何使用headers 'X-Recharge-Access-Token': API_TOKEN

理想情况下RechargeAPI::Customer.find(shopify_customer_id: 5363543224863)也会返回一个RechargeAPI::Customer对象而不是一个OpenStruct. 我很想能够继承,Struct但显然我不能,因为我是从我的RechargeAPI::Client班级继承的。

任何人都可以建议我如何去做这件事,或者任何改进这段代码的方法。谢谢!

标签: rubyapiooprefactoring

解决方案


理想情况下,我会使用 RechargeAPI.api_token = 'token' 之类的东西设置我的 api 令牌,而不是硬编码或在 ENV 文件中。

最简单的方法是在模块上创建访问器:

# /lib/recharge_api.rb
module RechargeAPI
  class << self
    attr_accessor :api_token 
  end
end

这看起来像黑魔法,但请记住模块是 Module 类的一个实例。这会将 api 令牌存储在@api_token模块中。

对于更高级的配置,您可以使用该MyGem.configure模式

理想情况下,RechargeAPI::Customer.find(shopify_customer_id: 5363543224863) 将返回 RechargeAPI::Customer 对象而不是 OpenStruct。我希望能够从 Struct 继承,但显然我不能,因为我是从 RechargeAPI::Client 类继承的。

这种设计有一个大问题,就是如果你的RechargeAPI::Customer类负责发送 HTTP 请求和管理来自响应的数据,那么你的类会做得太多。

我想构建一个类似 ActiveRecord 的接口,但在这种情况下,你最终会遇到一个很难处理的上帝类。

而是让客户端只执行 HTTP,一旦您真正开始处理错误和意外响应,这已经足够复杂了。

# /lib/recharge_api/client.rb
require 'httparty'
module RechargeAPI
  # Base client class for RechargeAPI
  class Client
    include HTTParty
    format :json
    headers 'X-Recharge-Access-Token' => RechargeAPI.api_token
    def initialize(**options)
      @options = {
         # set defaults here
      }.merge(options)
    end
  end
end
# /lib/recharge_api/customer_client.rb
module RechargeAPI
  # Gets customer data from RechargeAPI
  class CustomerClient < Client
    def search(params)
      # @todo what are you going to do when the request is not successful?
      handle_response(self.class.get('/customers', query: params))
    end

    def find(params)
      search(params).first
    end

    def all
      # @todo what are you going to do when the request is not successful?
      response = self.class.get('/customers')
      handle_response(response.body)
    end

    private 

    # refactor into a separate object if this gets too complex
    def handle_response(json)
      # @todo what are you going to do when the JSON does not contain the 
      # expected values?
      json.dig('customers')&.map do |raw| 
        Customer.from_json(raw)
      end
    end
  end
end
customers = RechargeAPI::CustomerClient.new(
  foo_option: 'bar'
).all

然后创建一个数据对象,它获取原始 JSON 数据并对其进行规范化:

# /lib/recharge_api/customer.rb
module RechargeAPI
  # Model representing a customer
  class Customer
    attr_accessor :email
    attr_accessor :name
    attr_accessor :is_karen
    # Simple attribute assignment from keyword arguments
    def initialize(**attributes)
      attributes.each do |key, value|
        send("#{key}=", value)
      end
    end

    # factory method for creating a model from the API response
    # refactor into a separate object if it gets too complex   
    def self.from_json(**json)
      # do your data normalization here
      new(**json)  
    end
  end
end

这使您可以在不触及应用程序边界和存根 API 的情况下对其进行测试。如果你想允许任何属性,你也可以从 OpenStruct 继承。


推荐阅读