简体   繁体   English

如何在 Rails API 中为 PATCH 请求转换嵌套参数

[英]How to transform nested parameters in Rails API for PATCH requests

I'm having problems trying to implement a PATCH endpoint for a Rails API which deals with complex request objects that are structurally different from the ActiveRecord model.我在尝试为 Rails API 实现 PATCH 端点时遇到问题,该端点处理在结构上不同于ActiveRecord model 的复杂请求对象。

As an example let's say I have the following request object:例如,假设我有以下请求 object:

{
    "details": {
        "color": {
            "id": 1
        }
    },
    "name": "Hello, world!"
    ...
}

However, on my model I expect a flat color_id attribute:但是,在我的 model 上,我希望有一个平坦的color_id属性:

class CreateModel < ActiveRecord::Migration[7.0]
  def change
    create_table :model do |t|
      t.string :name, null: false
      t.integer :color_id, null: false
    end
  end
end

Therefore I need to transform the request params.因此我需要转换请求参数。 For this I've found one approach which works pretty well in case of PUT requests, but not at all for PATCH:为此,我找到了一种在 PUT 请求的情况下效果很好的方法,但对于 PATCH 则完全没有:

ActionController::Parameters.new({
    color_id: params.dig(:details, :color, :id),
    name: params.dig(:name)
})

If I issue a PUT request this solution works great since PUT expects the whole object as payload, PATCH on the other hand would cause issues when passing only a subset of the properties since everything else will be set to nil due to how dig works.如果我发出 PUT 请求,此解决方案效果很好,因为 PUT 期望整个 object 作为有效负载,另一方面,当仅传递属性的子集时,PATCH 会导致问题,因为由于dig的工作方式,其他所有内容都将设置为nil

Assuming I have no control over the request format, how can I transform the request params in the backend so that omitted keys will not result in nil values?假设我无法控制请求格式,如何在后端转换请求参数,以便省略的键不会导致nil值? Of course I could imperatively handle each property line by line, checking whether the key is present in the original params and then setting it in the new one, but is there a more elegant approach?当然,我可以命令式地逐行处理每个属性,检查密钥是否存在于原始参数中,然后将其设置为新参数,但有没有更优雅的方法?

I've found a generic solution using mapping logic with a lookup table.我找到了一个使用带有查找表的映射逻辑的通用解决方案。 For the example above:对于上面的例子:

{
    "details": {
        "color": {
            "id": 1
        }
    },
    "name": "Hello, world!"
    ...
}

I would have the following mapping variable:我将有以下映射变量:

MAPPING = {
    [:details, :color, :id] => [:color_id]
}

Then I'm able to transform the params using this recursive algorithm:然后我可以使用这个递归算法来转换参数:

def handle(params, keys)
    output = Hash.new
    params.each do |k,v| 
        sym_keys = (keys + [k]).map &:to_sym
        target_keys = MAPPING[sym_keys]
        if v.is_a?(Hash)
            keys << k
            tmp = handle(v, keys)
            output = output.deep_merge!(tmp)
        end

        unless target_keys.nil?
            target_value = target_keys.reverse().reduce(v) do |v, k| 
                Hash[k, v]
            end
            output = output.deep_merge!(target_value)
        end
    end
    output
end

def transform(params)
    output = handle(params, [])
end

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

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