Elixir Phoenix: Can I call a context function from a template, and should I

Viewed 365

I want to display a list of article_categories in an article form (used for creation and update) as a drop down input.

I have a function in my blog context (blog.ex) to retrieve all categories through Ecto and format the result as a drop-downable list:

defmodule MyApp.Blog do
  import Ecto.Query, warn: false
  alias MyApp.Repo

  alias MyApp.Blog.ArticleCategory

  def get_categories() do
    MyApp.Repo.all from c in ArticleCategory, select: {c.title, c.id}
  end
end

In my template (templates/article/form.html.heex) I call my categories like this:

<%= select f, :category_id, MyApp.Blog.get_categories(), prompt: [key: "Choose your category"] %>

Visually I get the result I want, BUT... the literature seems to hint at passing my categories as an assign.

Is there an issue in doing it this way? (performance? design?)

1 Answers

I'm all for putting that logic in the controller. The main motivation here is Separation of Concerns:

  • your controller is responsible for parsing incoming parameters and getting the correct data from the data/business layer
  • your view layer is responsible for presenting that data to the user

One of the advantages is that you end up with a system that is easier to maintain and test.

I guess it can be hard to see why this would be a best practice, but as your application grows, it will show.

For example: a new requirement is that get_categories now only lists categories for which the user has access. With a controller assign you could create a controller test that contains something like assert conn.assigns.categories == ["one", "two"]. But when you fetch the categories in the view, the test would become something like assert html_response(conn, 200) =~ "<li>one</li><li>two</li>" which is far more brittle (ie. it doesn't guarantee that there's no item 'three', or if you add a class to each li element then you have to update the test as well).

Related