Reputation: 1387
I have come up with the following solution which I call before the cast:
attrs = payload_fields_to_payload(attrs)
The issue was that the incoming attrs can be atom-based, but doesn't have to be. So is there any faster or cleaner way than what I'm doing here? I'm just renaming a key in the attributes. It seems to be lots of code for this task.
def payload_fields_to_payload(attrs) do
attrs = cond do
Map.has_key?(attrs, "payload_fields") ->
Map.put(attrs, "payload", Map.get(attrs, "payload_fields"))
Map.has_key?(attrs, :payload_fields) ->
Map.put(attrs, :payload, Map.get(attrs, :payload_fields))
true -> attrs
end
attrs
end
Upvotes: 0
Views: 1045
Reputation: 48649
I'm just renaming a key in the attributes.
That's not what your code actually does--your code adds a new key to the map:
defmodule A do
def go do
attrs_list = [
%{"payload_fields" => "hello", type: "ABC"},
%{payload_fields: "goodbye", type: "XYZ"},
%{abc: "dog", xyz: "cat"}
]
Enum.map(attrs_list, fn attrs -> payload_fields_to_payload(attrs) end)
end
def payload_fields_to_payload(attrs) do
cond do
Map.has_key?(attrs, "payload_fields") ->
Map.put(attrs, "payload", Map.get(attrs, "payload_fields"))
Map.has_key?(attrs, :payload_fields) ->
Map.put(attrs, :payload, Map.get(attrs, :payload_fields))
true -> attrs
end
end
end
output:
iex(1)> A.go | |
[ V V
%{:type => "ABC", "payload" => "hello", "payload_fields" => "hello"},
%{payload: "goodbye", payload_fields: "goodbye", type: "XYZ"},
%{abc: "dog", xyz: "cat"}
]
But, if the old key gets filtered out by cast()
, then it's no big deal.
I would use pattern matching and multiple function clauses instead of using logic inside the function body to determine what to do. The following solution replaces the key payload_fields
with the key payload
:
defmodule A do
def go do
attrs_list = [
%{"payload_fields" => "hello", type: "ABC"},
%{payload_fields: "goodbye", type: "XYZ"},
%{abc: "dog", xyz: "cat"}
]
Enum.map(attrs_list, fn attrs -> convert_key(attrs) end)
end
def convert_key(%{"payload_fields" => value}=map) do #string key
map
|> Map.delete("payload_fields")
|> Map.put("payload", value)
end
def convert_key(%{payload_fields: value}=map) do # atom key
map
|> Map.delete(:payload_fields)
|> Map.put(:payload, value)
end
def convert_key(map), do: map
end
output:
iex(1)> A.go
[
%{:type => "ABC", "payload" => "hello"},
%{payload: "goodbye", type: "XYZ"},
%{abc: "dog", xyz: "cat"}
]
If you really want to add a new key to the map--rather than rename the key--the code simplifies to:
def convert_key(%{"payload_fields" => value}=map) do
Map.put(map, "payload", value)
end
def convert_key(%{payload_fields: value}=map) do
Map.put(map, :payload, value)
end
def convert_key(map), do: map
The issue was that the incoming attrs can be atom-based
The problem with allowing that is: what if an attr map has 14 million atom keys? Boom! Your app crashes. That same thing can happen with millions of attr maps that each contain only a few atom keys. That's the reason why Phoenix uses string keys in the params
map for form data--doing that prevents an attacker from flooding the atom table by sending millions of requests with unique keys.
Upvotes: 3