As you’ll soon notice, I’m very new to Phoenix and Elixir, and I’m not really a dev (more background at the bottom of this post.) So, prepare to laugh at my expense, but if you’ve got some pointers, I’d appreciate it.
I’ve been reading official docs, tutorials, stackexchange threads, and threads here, but I’ve still been stuck on this for a couple of days. It seems like I must be missing something obvious, because this task seems like it must be simple. There seem to be multiple ways to do what I’m trying to do, and I’ve tried several, but I’m still flailing, so…
I’m working with an existing DB from an old Ruby version of the site. The DB contains all the monologues from all of Shakespeare’s plays.
Since it’s an existing DB, and I want to get the site back online asap, when I got the static stuff in place and was ready to start writing queries , I started with this tutorial: Breaking Out of Ecto Schemas , which might be how/why I’ve painted myself into this corner…
I’ve got a route with a variable for playid
. I’ve got a page displaying all titles of all the plays. When a play title is clicked on, it sends the playid
to a controller with a join query in it (details from two different tables), and pulls up a list of all the monologues for that play in a different page/view. It also displays some details from some other cells (one of those values includes the title of the play.) That’s working fine. (If I’m not describing that well, you can see how it used to work in the Ruby version of the site, via the Wayback Machine if you have patience for how slowly pages load there. Follow this link, wait for it to load, then click on any play title, and you’ll see what I described previously in this current paragraph: Monologues in Shakespeare )
I’m stuck at trying to display the title of that play at the top of the page, above the list of monologues (in an <h1>
.) So far, I can get the title be repeated multiple times, e.g. If the link clicked was a play with 34 monologues, I can get it to display the title 34 times, above the list of the monologues. I do understand why that’s happening (and you will too, shortly), but I can’t figure out the correct way to do it so that it displays the title in an <h1>
only once.
So, some details:
Erlang/OTP 24
Elixir 1.12.2
Phoenix 1.6.15
deps
{:phoenix, "~> 1.6.15"},
{:phoenix_ecto, "~> 4.4"},
{:ecto_sql, "~> 3.6"},
{:postgrex, ">= 0.0.0"},
{:phoenix_html, "~> 3.0"},
{:phoenix_live_reload, "~> 1.2", only: :dev},
{:phoenix_live_view, "~> 0.17.5"},
{:floki, ">= 0.30.0", only: :test},
{:phoenix_live_dashboard, "~> 0.6"},
{:esbuild, "~> 0.4", runtime: Mix.env() == :dev},
{:swoosh, "~> 1.3"},
{:telemetry_metrics, "~> 0.6"},
{:telemetry_poller, "~> 1.0"},
{:gettext, "~> 0.18"},
{:jason, "~> 1.2"},
{:plug_cowboy, "~> 2.5"},
{:html_assertion, "0.1.5", only: :test},
{:credo, "~> 1.6", only: [:dev, :test], runtime: false}
From lib/mono_phoenix_v01_web/router.ex
, this is the route for the links with the play ids:
get("/play/:playid", PlayPageController, :play)
Here are the contents of that controller. (The first query I can use, the other isn’t currently working. One question I’ve been unable to find an answer to is: “Is it valid to have two queries in the same controller?”):
lib/mono_phoenix_v01_web/controllers/play_page_controller.ex
defmodule MonoPhoenixV01Web.PlayPageController do
use MonoPhoenixV01Web, :controller
import Ecto.Query
# Show monologues for the play link clicked
@spec play(Plug.Conn.t(), map) :: Plug.Conn.t()
def play(conn, params) do
playid = String.to_integer(params["playid"])
query =
from(m in "monologues",
join: p in "plays",
on: m.play_id == p.id,
where: p.id == ^playid,
group_by: [p.id, p.title, m.character, m.first_line, m.location, m.body],
select: %{
play: p.title,
character: m.character,
firstline: m.first_line,
location: m.location,
body: m.body
}
)
rows = MonoPhoenixV01.Repo.all(query)
render(conn, "play.html", rows: rows)
end
# Show the title of the play
@spec title(Plug.Conn.t(), map) :: Plug.Conn.t()
def title(conn, params) do
playid = String.to_integer(params["playid"])
query =
from(p0 in MonoPhoenixV01.Play,
where: p0.id == ^playid,
select: [:id, :title]
)
titles = MonoPhoenixV01.Repo.one(query)
render(conn, "plays.html", title: titles)
end
end
Here’s the version of the template which does the repeating of the title (row.play
):
lib/mono_phoenix_v01_web/templates/play_page/play.html.heex
# this section causes the title ('row.play') to be repeated as described earlier in the post:
<section class="play">
<%= for row <- @rows do %>
<h1><%= row.play %></h1>
<% end %>
</section>
# this section works fine. probably not the best approach, but it works for now
<section class="row">
<article class="monologue_preview">
<%= for row <- @rows do %>
<br><br>
<%= row.play %>, <%= row.character %>, <%= row.firstline %>, <%= row.location %>
<br>
<%= raw(row.body) %>
<% end %>
</article>
</section>
(If you’re currently asking yourself “Why did he do it that way?” The answer is: Because I’m a clueless n00b. )
I’ve tried every other relevant word I can think of to use instead of rows
(and in the 2nd part of the controller), such as “columns”, “titles”, “plays” etc etc. All lead to Compilation errors, which makes me think “rows” is defined somewhere in default files, but I’ve been unable to find info about how I’d add my own.
So, I tried to correct my schema-less path, by creating a schema for the play titles:
lib/mono_phoenix_v01/play.ex
defmodule MonoPhoenixV01.Play do
@moduledoc """
This module provides the schema for the title from the Plays table. Or would If I knew what I'm doing.
"""
use Ecto.Schema
schema "titles" do
field(:title, :string)
field(:classification, :string)
end
end
I can query that schema in iex:
iex(11)> query = from(MonoPhoenixV01.Play, where: [id: ^"9"], select: [:id, :title])
#Ecto.Query<from p0 in MonoPhoenixV01.Play, where: p0.id == ^"9",
select: [:id, :title]>
iex(12)> MonoPhoenixV01.Repo.all(query)
[debug] QUERY OK source="plays" db=1.0ms idle=1521.7ms
SELECT p0."id", p0."title" FROM "plays" AS p0 WHERE (p0."id" = $1) [9]
↳ :erl_eval.do_apply/6, at: erl_eval.erl:685
[
%MonoPhoenixV01.Play{
__meta__: #Ecto.Schema.Metadata<:loaded, "plays">,
classification: nil,
id: 9,
title: "All's Well That Ends Well"
}
]
iex(13)>
So I tried changing the first section in lib/mono_phoenix_v01_web/templates/play_page/play.html.heex
to this:
<section class="play">
<%= for title <- @titles do %>
<h1><%= title.title %></h1>
<% end %>
</section>
Which gives me:
KeyError at GET /play/9
key :titles not found in: %{conn: %Plug.Conn{adapter: {Plug.Cowboy.Conn, :...}, assigns: %{layout: {MonoPhoenixV01Web.LayoutView, "app.html"}, rows: [%{body: "If ever we are nature's, these are ours; this thorn ...
etc.
Also tried: <h1><%= row.title %></h1>
But got:
== Compilation error in file lib/mono_phoenix_v01_web/views/play_page_view.ex ==
** (CompileError) lib/mono_phoenix_v01_web/templates/play_page/play.html.heex:4: undefined function row/0
(elixir 1.12.2) src/elixir_locals.erl:114: anonymous fn/3 in :elixir_locals.ensure_no_undefined_local/3
(stdlib 3.17.2.2) erl_eval.erl:685: :erl_eval.do_apply/6
(elixir 1.12.2) lib/kernel/parallel_compiler.ex:319: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/7
“Help me ObiWan ElixirForum. You’re my only hope.”
=-=-=-=-=-= Boring backstory, and why I’m so clueless atm =-=-=-=-=-=-=
I first built the site in plain ol’ html back in 1997. later moved it to php.
In 2010 a good friend asked if he could rebuild the site in Ruby On Rails, to beef up his skills and resume. A couple of years after that, we moved it from Rails to Padrino to cut down on maintenance time. We were using Ruby v2.3.0. Then both of our lives got much busier. The site always worked, rarely needed anything changed, an ad revenue arrived monthly. I didn’t think about the site much for several years. I do not like Ruby, so I never felt like trying to change things unless I had to.
Fast forward to December 1, 2022. Heroku stop supporting a few things, and the site went down. It took a few weeks, but I realized that trying to upgrade the site from Padrino and Ruby 2.3.0 to Sinatra and Ruby 3.1.3 was a fools errand, due to the age of the gems in the site, and a massive myriad of incompatible things on the upgrade path, and, well, the general state of Ruby these days.
Then I setup Phoenix and Elixir on my local and it only took me a week to make the same progress that took 6 weeks in Ruby. Working on the site is fun again.
I was clipping along at a good pace until I hit the wall of my lack of knowledge and got bleary-eyed reading docs and tutorials and trying things to figure out how to display the title once at the top of the play/:playid
page. Hoping some kind soul will see this post and help me over my current hurdle. Thanks.