Dynamic fields in views
Currently, if I define some fields in my JSONAPI.View.fields/0 callback that are not part of the data passed when rendering, then I have to define one public function per such field.
This is alright, but it limits flexibility when we need something akin to dynamic fields. For example, imagine that for convenience I keep my data structure as
%{
id: 1,
attrs: %{foo: true, bar: false, baz: nil}
}
With the current capabilities, I can do this:
def fields do
[:id, :foo, :bar, :baz]
end
def foo(%{attrs: %{foo: foo}}, _conn), do: foo
def bar(%{attrs: %{bar: bar}}, _conn), do: bar
def baz(%{attrs: %{baz: baz}}, _conn), do: baz
It works, but imagine what this can become if you want to do something similar with many more fields. Metaprogramming can come in handy if you generate the public functions programmatically, but it leads to code that's a bit harder to understand (especially for newcomers).
Possible Solution
A possible solution could be a c:JSONAPI.View.get_field/3 callback that takes the field name, the data, and the Plug connection. I would reimplement the example above as:
def fields do
[:id, :foo, :bar, :baz]
end
def get_field(field, %{attrs: attrs}, _conn) do
Map.fetch!(attrs, field)
end
Did I missing an already-existing way to do this? 🙃 What are you folks' thoughts on it in case I didn't?
Seems reasonable. I imagine it would be beneficial to still allow individually defined functions named after fields to take precedence over get_field/3.
So for the following
def fields do
[:id, :foo, :bar, :baz]
end
def foo(attrs, conn), do: ...
def get_field(field, attrs, conn), do: ...
The framework would be expected to call foo/2 for the foo attribute but call get_field/3 for all other attributes.
Yes, exactly. I'll work on a PR for this 🙃
@mattpolzin PR open https://github.com/beam-community/jsonapi/pull/273!