简体   繁体   English

如何使用 Rails 5.2 和 Faraday gem 构建复杂的 json 以发布到 web 服务?

[英]How to build complex json to POST to a web service with Rails 5.2 and Faraday gem?

My application sends data to a metadata repository through a REST API.我的应用程序通过 REST API 将数据发送到元数据存储库。 I choosed Faraday to handle the HTTP requests.我选择 Faraday 来处理 HTTP 请求。 I basically setup some headers, a json dataset, and POST to the webservice.我基本上设置了一些标头,一个 json 数据集,并 POST 到 web 服务。 The following code takes place in the skills_controller, and is triggered when the user decides to publish the definition of a variable:以下代码发生在技能控制器中,并在用户决定发布变量定义时触发:

    ### 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

The method then renders an updated partial of the Show view of the skill, with its updated status.然后,该方法呈现技能的 Show 视图的更新部分及其更新状态。

This works fine as long as the request body is quite simple.只要请求主体非常简单,它就可以正常工作。 But I'd like to handle a variable number of translations, and in some cases also send child records to the web service: ie implement loops, nested json objects, and probably partials.但我想处理可变数量的翻译,并且在某些情况下还将子记录发送到 web 服务:即实现循环、嵌套 json 对象,以及可能的部分。

I read about Jbuilder features to create complex json for views.我阅读了 Jbuilder 功能来创建复杂的 json 以获得视图。 Is there something similar I could use in a controller?我可以在 controller 中使用类似的东西吗? Or is there a way to create a json view (and partials) and render it into Faraday' request body?或者有没有办法创建一个 json 视图(和部分)并将其呈现到法拉第的请求正文中? Which would be a good architecture to build this feature?哪个是构建此功能的好架构? Do you know any article that would describe this?你知道有什么文章可以描述这个吗?

Thanks a lot for showing me the way.非常感谢你给我指路。

Start by creating an object that touches your application boundry:首先创建一个触及应用程序边界的 object:

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
                              )

This gives you object that can be tested in isolation and stubbed out.这为您提供了 object,可以单独测试并剔除。 Its also the only object that should know about the quirks and particularities of the API thus limiting the impact on your application if it should change.它也是唯一一个应该了解 API 的怪癖和特殊性的 object,从而限制了如果它应该更改对您的应用程序的影响。

While using a view sounds like a good idea intially you're basically using a very awkward DSL to generate basic data structures like arrays and hashes that map 1-1 to JSON.虽然最初使用视图听起来是个好主意,但您基本上是在使用非常笨拙的 DSL 来生成基本数据结构,例如 arrays 和 map 1-1 到 Z0ECD11C1D7A287401D148A23BBD7A22 的散列。 jBuilder is also very slow. jBuilder 也很慢。

As a first step to refactoring you could just extract turning a Skill into JSON into its own PORO:作为重构的第一步,您可以将技能转换为 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 is also an option. ActiveModel::Serializers 也是一个选项。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM