【问题标题】:How to build complex json to POST to a web service with Rails 5.2 and Faraday gem?如何使用 Rails 5.2 和 Faraday gem 构建复杂的 json 以 POST 到 Web 服务?
【发布时间】:2021-05-17 15:40:09
【问题描述】:

我的应用程序通过 REST API 将数据发送到元数据存储库。我选择 Faraday 来处理 HTTP 请求。我基本上设置了一些标头、一个 json 数据集和 POST 到 web 服务。以下代码发生在技能控制器中,并在用户决定发布变量定义时触发:

    ### Create the variable for the BusinessArea, get the location header in return
    connection = Faraday.new("https://sis-sms-r.application.opendataquality.ch", :ssl => {:verify => false})
    request_body = {
     definedVariableType: @skill.skill_type.property,
     description: {
       en: @skill.description_translations.where(language: :en).take!,
       de: @skill.description_translations.where(language: :de_OFS).take!,
       fr: @skill.description_translations.where(language: :fr_OFS).take!
      },
     identifier: "Variable TEST 10",
     name: {
       en: @skill.name_translations.where(language: :en).take!,
       de: @skill.name_translations.where(language: :de_OFS).take!,
       fr: @skill.name_translations.where(language: :fr_OFS).take!
      },
     pattern: nil,
     pseudonymized: true,
     validFrom: Time.now,
     validTo: Time.now + 1.year,
     version: "1",
     responsibleDeputy: {
         identifier: @skill.deputy.email,
         name: @skill.deputy.external_directory_id
      },
     responsibleOrgUnit: {
         identifier: @skill.organisation.code,
         name: @skill.organisation.external_reference
      },
     responsiblePerson: {
         identifier: @skill.responsible.email,
         name: @skill.responsible.external_directory_id
      }
    }
  
    puts "--- body"
    puts request_body

    response = connection.post("/api/InformationFields/#{business_area.uuid}/definedVariables") do |req|
      req.body = request_body.to_json
      req.headers['Content-Type'] = 'application/json'
      req.headers['Accept'] =  'application/json'
      req.headers['Authorization'] = "Bearer #{token}"
    end

    puts "--- response"
    puts response.status               # Status 201 => successful request
    puts response.body                 # Message
    puts response.headers["location"]  # uuid of new object

然后该方法呈现技能的显示视图的更新部分及其更新状态。

只要请求的主体非常简单,它就可以正常工作。但我想处理可变数量的翻译,并且在某些情况下还将子记录发送到 Web 服务:即实现循环、嵌套的 json 对象,可能还有部分。

我阅读了 Jbuilder 功能来为视图创建复杂的 json。我可以在控制器中使用类似的东西吗?或者有没有办法创建一个 json 视图(和部分)并将其呈现到法拉第的请求正文中?哪个是构建此功能的好架构?你知道有什么文章可以描述这个吗?

非常感谢你给我指路。

【问题讨论】:

    标签: ruby-on-rails json


    【解决方案1】:

    首先创建一个触及应用程序边界的对象:

    class JSONClient
      attr_reader :connection
    
      def initialize(base_uri, **opts, &block)
        @connection = Faraday.new(
          base_uri, 
          **opts
        ) do |f|
            f.request :json # encode req bodies as JSON
            f.response :json # decode response bodies as JSON
            yield f if block_given?
        end
      end
    end
    
    class BusinessAreaClient < JSONClient
      def initialize(**opts)
        super(
          "https://sis-sms-r.application.opendataquality.ch", 
          ssl: { verify: false},
          **opts
        )
      end
    
      def defined_variables(skill:, uiid:  token:)
        response = connection.post(
          "/api/InformationFields/#{uuid}/definedVariables"
          SkillSerializer.serialize(skill),
          {
            'Authorization' => "Bearer #{token}"
          }
        )
        if response.success?
          response 
        else
          # handle errors
        end
      end
    end
    
    response = BusinessAreaClient.new
                                 .defined_variables(
                                     skill: skill,
                                     uuid: business_area.uuid,
                                     token: token
                                  )
    
    

    这为您提供了可以单独测试和存根的对象。它也是唯一应该了解 API 的怪癖和特殊性的对象,从而限制了如果应用程序发生更改对您的应用程序的影响。

    虽然最初使用视图听起来是个好主意,但您基本上是在使用非常笨拙的 DSL 来生成基本数据结构,例如将 1-1 映射到 JSON 的数组和散列。 jBuilder 也很慢。

    作为重构的第一步,您可以只提取将技能转换为 JSON 到它自己的 PORO:

    class SkillSerializer < SimpleDelegator
      LANG_MAPPING = {
        en: :en,
        de: :de_OFS,
        fr: :fr_OFS
      }.freeze
      
      def serialize
        {
          definedVariableType: skill_type.property,
          description: translate(description_translations),
          identifier: "Variable TEST 10",
          name: translate(name_translations),
          pattern: nil,
          pseudonymized: true,
          validFrom: Time.now,
          validTo: Time.now + 1.year,
          version: "1",
          responsibleDeputy: {
            identifier: deputy.email,
            name: deputy.external_directory_id
          },
          responsibleOrgUnit: {
            identifier: organisation.code,
            name: organisation.external_reference
          },
          responsiblePerson: {
            identifier: responsible.email,
            name: responsible.external_directory_id
          }
        }
      end
    
      def self.serialize(object)
        new(object).serialize
      end
    
      private 
      # should probally be refactored to not cause an excessive amount of queries
      def translate(relation)
        LANG_MAPPING.dup.transform_values do |lang|
          relation.where(language: lang).take!
        end
      end
    end
    

    ActiveModel::Serializers 也是一个选项。

    【讨论】:

    • 是的,PORO 仍然是一个非常好的解决方案。我会坚持下去。非常感谢您的架构建议!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-09-21
    • 1970-01-01
    • 2015-11-24
    • 1970-01-01
    • 1970-01-01
    • 2023-03-22
    • 2017-04-14
    相关资源
    最近更新 更多