correct
Hmm … I’m not sure if assuming that only by it is good …
First of all it looks like a typical Phoenix
app which is using Ecto.Schema
(I guess that there are just virtual
fields). The fields are generated only in the controller
for show
action. Of course there may be something in Clients.get_project!/1
, but we can’t be sure about that. If that’s a typical phoenix
context function then it’s just a simple query by id
.
If we are using struct
then put_new/1
would have no affect as project
would have default
value (or nil
) already. We know that those fields are generated based on other fields (imagine full_name = "#{first_name} #{last_name}"
. In most cases such fields could be update (or more precisely re-generated) several times and therefore more interesting here is if related fields are changing.
Finally Map.put_new/3
is not good for such usage. Look that total_budget/1
and budget_used/1
functions would be called regardless if there would be already value or not - in many cases it causes many problems. When I see such mistakes I just can’t assume such things and I’m simply providing a solution for most typical cases.
The best way is to provide sample input and output right after question, so our examples could be tested properly. Right now we guess
that x
solution is good or not. That’s said op asked more about code organization than a typical solution. I think that my examples are helpful also in this part.
Regarding my solution it’s really easy to update it by just wrapping do … end
block inside an if
with simple condition using Map.has_key?/2
…
defmodule Project do
defstruct [:budget, :budget_items, :budget_remaining, :budget_used, :work_items]
def calculate(project) do
fields = [:budget, :budget_used, :budget_remaining]
Enum.reduce(fields, project, fn key, acc ->
if Map.has_key?(project, key) do
project
else
value = Project.calculate(project, key)
Map.put(project, key, value
end
end)
# short notation:
# updated_project = Enum.reduce(fields, project, &Map.put(&2, &1, Project.calculate(&2, &1))
end
def calculate(%{budget_items: items}, :budget), do: total_budget(items)
def calculate(%{work_items: items}, :budget_used), do: budget_used(items)
def calculate(%{budget: budget, budget_used: used}, :budget_remaining), do: budget - used
end
project = id |> Clients.get_project!() |> Project.calculate()
Look that my solution is really easy to change.