In the JSON API I’m building I am returning resources which are often the denormalized version of my database schemas. I’m wondering what the most idiomatic approach is for this, transforming the data via views in Phoenix, creating a custom schema, or doing the manipulation via the query?
Here’s an example. In my database schema, I have a Plan which has two subtypes, MatchPlan and FixedPlan.
schema "frequency" do
field :name, :string
has_many :plan, Plan
end
end
schema "plan" do
belongs_to :frequency, Frequency
has_one :match_plan, MatchPlan
has_one :fixed_plan, FixedPlan
end
schema "match_plan" do
field :percentage, :decimal
field :limit
belongs_to :plan, Plan
end
schema "fixed_plan" do
field :amount
belongs_to :plan, Plan
end
This is too abstracted for my API to return, I’d like to return a resource like this:
{
"id": 1,
"object": "plan",
"frequency": "monthly",
"plan_type": "match",
"limit": 100,
"amount": null
}
Here’s the approaches I see:
Pass a preloaded struct to a view like-so. The downside here is there’s a lot null checking and the transformation logic can get nasty. I have much more complex data structures than this too.
def render("show.json", %{match_plan: match_plan, fixed_plan: fixed_plan, frequency: frequency} = plan) do
%{
id: plan.id,
object: "plan",
plan_type: if match_plan, do: "match_plan", else: "fixed_plan",
frequency: frequency.name,
limit: if match_plan, do: match_plan.limit, else: nil,
amount: if fixed_plan, do: fixed_plan.amount, else: nil
}
end
Second, create a custom schema. I know this could be done by creating a view in the database and mapping a schema to it. But I’d rather not have my business logic at the database level.
Lastly, I can use a query. This method is easy, but might not support all the transformations I need to do and it doesn’t seem like the right place for the business logic.
from p in Plan,
join: f in assoc(p, Frequncy),
left_join: mp in assoc(p, MatchPlan),
left_join: fp in assoc(p, FixedPlan),
select: %{
id: p.id,
object: "plan",
frequency: f.name,
plan_type: fragment("IF ? IS NULL THEN 'match' ELSE 'fixed' END", mp.id),
limit: mp.limit,
amount: fp.amount
}
What do you guys think? Is there another option?