tj0

tj0

Notes on 1.6 to 1.7 upgrade with verified_routes and phoenix_view removal

I recently started on a 1.6 to 1.7 upgrade with all the changes to remove phoenix_view and had some troubles. Despite Jose mentioning that phoenix_view will be maintained, I was already half-way done, so I continued on a complete migration.

Hopefully, this documentation helps someone. Also, the pre-requisite reading is:

Phoenix 1.6 to 1.7 upgrade
Phoenix View - replaced by Phoenix.Component

Main changes

A sample phoenix 1.7 project is required (currently at 1.7.2).

app_web.ex

Several additional functions need to be copied from a new project

  • live_component
  • live_view
  • html
  • html_helpers
  • verified_routes

Need to eventually remove “view”

Copy files / replace from app_web/components/*

  • app_web/components/core_components.ex

  • app_web/controllers/error_html.ex

  • app_web/controllers/error_json.ex

  • Layout replacement. root.html.heex now takes on the function of both templates/layouts/{app.html.heex, live_app.html.heex}

    • app_web/components/layouts.ex (replaces app_web/views/layout_view.ex)
    • app_web/components/layouts/app.html.heex
<%= @inner_content %>
  • app_web/components/layouts/root.html.heex
-     <%= csrf_meta_tag() %>
+     <meta name="csrf-token" content={get_csrf_token()} />

  • router.ex
-  plug :put_layout, {AppWeb.LayoutView, :app}
+  plug :put_root_layout, {AppWeb.Layouts, :root}
  • config/config.ex
 config :app, AppWeb.Endpoint,
-   render_errors: [view: AppWeb.ErrorView, accepts: ~w(html json)]
+   render_errors: [
+    formats: [html: AppWeb.ErrorHTML, json: AppWeb.ErrorJSON],
+   layout: false
+ ]

Non-Liveview

Directory structure changes

app_web/controllers/bla_controller.ex → app_web/controllers/bla_controller.ex
app_web/templates/bla/* → app_web/controllers/bla_html/*
app_web/views/bla_view.ex → app_web/controllers/bla_html.ex

File changes bla_view.ex → bla_html.ex

use AppWeb, :view

def some_helper() do
end

to

use AppWeb, :html
embed_templates "bla_html/*"
# Or to keep directory structure
# embed_templates "../templates/bla/*"

def some_helper() do
end

 @doc """
  NOTE: In migration to Phoenix 1.7.2, needed to keep this function for some generated eex files
  Generates tag for inlined form input errors.
  """
  def error_tag(form, field) do
    Enum.map(Keyword.get_values(form.errors, field), fn error ->
      content_tag(:span, translate_error(error), class: "help-block")
    end)
  end

Moving/Deletion

Can eventually delete

  • lib/app_web/controllers/fallback_controller.ex
  • lib/app_web/views/*

New location could be found for

  • lib/app_web/templates/*

Render changes

Have to be very explicit in templates now as there is no way to dynamically choose a function that I can see so far. The functions here are actually template names as found by the directive embed_templates/* in the controllers/bla_html.ex

-         <%= render "#{@page_name}.html", email: @email, action: Routes.session_path(@conn, @page_action), changeset: @changeset %>
+     <%=  case @page_name do
+          "landing" -> landing(assigns)
+          "login_form" -> login_form(assigns)
+          "register_form" -> register_form(assigns)
+          "forgot_form" -> forgot_form(assigns)
+        end
+      %>

LiveView changes

Directory structure changes

Perhaps I’m doing it wrong, but this is what I had to do.

  • app_web/live/first/ - the directory
  • app_web/live/first/main.ex - the live_view / live_component
- use Phoenix.LiveComponent
+ use AppWeb, :live_component
+ import AppWeb.First.Helpers # these were previously defined in AppWeb.FirstView
+ embed_templates "templates/*"

- def render(assigns) do
    AppWeb.FirstView.render("main.html", assigns)
  end
  • app_web/live/first/helpers.ex - whatever functions that were previously in FirstView
+ use AppWeb, :html
  • app_web/live/first/main.html.heex - the file that gets directly rendered on component/view load. Any sub-templates need to be rendered from the templates/* directory. The render changes to a function call. If there is any conditional rendering, there needs to be a case statement with the function calls.
-  <%= render("file.html", target: @myself) %>
+ <.file target={@myself}

What if you want to keep the render function in your liveview?

This is needed for conditional rendering of templates. In that case, move the main.html.heex into the whatever templates directory you have defined in embed_templates/* and it changes from

 def render(assigns) do
    cond do
      assigns.print == true  ->
        AppWeb.FirstView.render("print.html", assigns)
      !is_nil(assigns.alt_layout ) >
        AppWeb.FirstView.render("layout#{assigns.alt_layout}.html", assigns)
      true ->
        AppWeb.FirstView.render("main.html", assigns)
     end
  end

  def render(assigns) do
    cond do
      assigns.print == true  ->
        ~H"<%= print(assigns) %>"

     !is_nil(assigns.alt_layout)  ->
        current_layout = assigns.alt_layout
        case current_layout do
          "1" -> ~H"<%= layout1(assigns) %>"
          "2" -> ~H"<%= layout2(assigns) %>"
        end

      true ->
        ~H"<%= main(assigns) %>"
     end
  end

Conclusion

In the end, the migration wasn’t hard, just time consuming. I had an organic sprawl of 30 templates and 20 liveview components in templates and live and had to touch around 70 files total and took the opportunity to namespace everything better.

I’m solo right now, but in the future if someone had to touch the code and look at the phoenix documentation, they would have been very confused.

Regardless, I prefer this new setup without phoenix_view as there seems like there is a bit less magic. Verified routes is also a nice incremental improvement. Also, perhaps this is a pipedream, but I’d like to keep this application running for the next 10 years (preferably 50) without too many major migrations.

Most Liked

codeanpeace

codeanpeace

Great job with the write up!

Swapping the cond block with pattern matching in function heads could be a nice approach.

def render(%{print: true} = assigns), do: ~H"<%= print(assigns) %>"
def render(%{alt_layout: "1"} = assigns), do: ~H"<%= layout1(assigns) %>"
def render(%{alt_layout: "2"} = assigns), do: ~H"<%= layout2(assigns) %>"
def render(assigns), do: ~H"<%= main(assigns) %>"

Or if sticking with one function head, collapsing the case nested in cond with one case statement.

def render(assigns) do
  case assigns do
    %{print: true} -> ~H"<%= print(assigns) %>"
    %{alt_layout: "1"} -> ~H"<%= layout1(assigns) %>"
    %{alt_layout: "2"} -> ~H"<%= layout2(assigns) %>"
    _ -> ~H"<%= main(assigns) %>"
  end
end

Where Next?

Popular in Discussions Top

matthias_toepp
I’d love to hear what people think about Wisp, the new Gleam web framework started by Gleam’s primary creator Louis Pilfold. Gleam, alon...
New
vans163
So useless benchmarks aside, Its possible to write a webserver that can serve 300k requests per second (perhaps more with optimizations)....
New
PragTob
Hello everyone, I know we had quite some threads (read through lots of them) about background job processing but it remains a hotly deba...
New
Ankhers
Just a little information upfront. Generally speaking, if I feel like I need to either break a pipe chain or use an anonymous function in...
New
Crowdhailer
I’ve been hearing much about the new formatter and it’s something I have been keen to try. I find examples buy far the most illuminating...
248 19327 150
New
PragTob
Hey everyone, this has been brewing in my head some time and it came up again while reading Adopting Elixir. GenServers, supervisors et...
New
tomekowal
Hey guys! I want to create a toy project that shows a chart of temperature over time and updates every 5 seconds. I feel LiveView is per...
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New
paulanthonywilson
I like Umbrella projects and pretty much always use them for personal Elixir stuff, especially Nerves things. But I don’t think this is ...
New
slashdotdash
Phoenix Live View is now publicly available on GitHub. Here’s Chris McCord’s tweet announcing making it public.
New

Other popular topics Top

sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
New
AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New

Latest on Elixir Forum

We're in Beta

About us Mission Statement