【问题标题】:Set a Ruby class variable, then use it in a class method. HTTParty设置一个 Ruby 类变量,然后在类方法中使用它。 HTTP派对
【发布时间】:2021-11-16 20:23:14
【问题描述】:

我正在尝试创建一个用于充值的 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 &lt;OpenStruct accepts_marketing=nil, analytics_data={"utm_params"=&gt;[]}, 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 类继承的。

任何人都可以建议我如何进行此操作,或以任何方式改进此代码。 谢谢!

【问题讨论】:

    标签: ruby api oop refactoring


    【解决方案1】:

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

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

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

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

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

    理想情况下,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 继承。

    【讨论】:

    • 有时您甚至想要添加一个单独的对象,它位于您的客户端和“模型”之间,只处理数据的规范化。
    • is_karen 哈哈哈,非常感谢,我会接受这个!使用# @todo what are you going to do when the request is not successful?,您认为值得为每个 get 制定一个方法以便我可以处理错误吗?
    • 另外format :jsonRechargeAPI::Client 中做了什么?再次感谢!
    • 您可以做的是创建一个RechargeAPI::Client#get 方法,该方法包装了HTTParty get 方法并整合了错误处理。
    • 难道 api 没有一个指向单个记录的端点 - 比如'/customers/:id'
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-04-28
    • 1970-01-01
    • 2014-05-06
    • 2012-01-07
    • 1970-01-01
    • 2012-03-07
    • 1970-01-01
    相关资源
    最近更新 更多