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.
As an example let's say I have the following request object:
{
"details": {
"color": {
"id": 1
}
},
"name": "Hello, world!"
...
}
However, on my model I expect a flat color_id
attribute:
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:
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.
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? 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?
CodePudding user response:
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