Search code examples
elixirphoenix-frameworkecto

Show queried data from Phoenix controller in eex template


I have a query in a Phoenix controller which gets a list of students, and for each row in the list does a join with the bloodgroup on its id and gets the bloodgroup name. I would like to display this in an eex template that I generated but am getting errors.

Here's the Student schema:

schema "students" do
  field :firstname, :string
  field :lastname, :string
  field :birthday, Ecto.Date
  field :joined_on, Ecto.Date
  field :bloodgroup_id, :integer

  timestamps()
end

Here's the BloodGroup model:

schema "bloodgroups" do
  field :name, :string
  timestamps()
end

I'm getting the data in the controller like so:

def query do
  query =  from s in Student,
        join: b in BloodGroup, on: s.bloodgroup_id == b.id,
        select: [s.firstname, s.lastname, s.birthday, s.joined_on, s.bloodgroup_id, b.name]
end

def index(conn, _params) do
  students = Repo.all(query)
  render(conn, "index.html", student_info: students)
end

And displaying in the template like so:

<%= for student <- @student_info do %>
<tr>
  <td><%= Enum.fetch(student, 0) %></td>
  <td><%= student[:lastname] %></td>
  <td><%= student[:birthday] %></td>
  <td><%= student[:joined_on] %></td>
  <td><%= student[:name] %></td>

  <td class="text-right">
    <%= link "Show", to: student_path(@conn, :show, student), class: "btn btn-default btn-xs" %>
    <%= link "Edit", to: student_path(@conn, :edit, student), class: "btn btn-default btn-xs" %>
    <%= link "Delete", to: student_path(@conn, :delete, student), method: :delete, data: [confirm: "Are you sure?"], class: "btn btn-danger btn-xs" %>
  </td>
</tr>
<% end %>

But the using the form Enum.fetch and student[:firstname] don't work and throw either Argument error or protocol Phoenix.HTML.Safe not implemented

I want to show what has been sent from the controller in the template but am hitting these errors. Is there a better way to do this? I'm new to Phoenix and Elixir.


Solution

  • Based on the query you've posted, you should be getting back a list of lists with Repo.all. You can use pattern matching to extract the data, something like:

    <%= for [firstname, lastname, birthday, joined_on, bloodgroup_id, name] <- @student_info do %>
    

    BUT, that's not idiomatic. You really should be using Ecto's relationships here.

    Add BloodGroup as a belongs_to relation to Student:

    schema "students" do
      ...
      belongs_to :bloodgroup, BloodGroup, foreign_key: :bloodgroup_id
    end
    

    Then do the query like this:

    students = Repo.all(Student) |> Repo.preload(:bloodgroup)
    

    Then your template simply becomes:

    <%= for student <- @student_info do %>
      <tr>
        <td><%= student.lastname %></td>
        <td><%= student.birthday %></td>
        <td><%= student.joined_on %></td>
        <td><%= student.bloodgroup.name %></td>
    
        <td class="text-right">
          <%= link "Show", to: student_path(@conn, :show, student), class: "btn btn-default btn-xs" %>
          <%= link "Edit", to: student_path(@conn, :edit, student), class: "btn btn-default btn-xs" %>
          <%= link "Delete", to: student_path(@conn, :delete, student), method: :delete, data: [confirm: "Are you sure?"], class: "btn   btn-danger btn-xs" %>
        </td>
      </tr>
    <% end %>
    

    All code is untested. Please let me know if this doesn't work. It's probably a typo somewhere.