🧪

ElixirSchoolのNimblePublisherのレッスンをPhoenix 1.7.8対応する

2023/10/16に公開

導入

ElixirSchoolにはNimblePublisherの素晴らしいレッスンがあります。
https://elixirschool.com/ja/lessons/misc/nimble_publisher

しかし、現在はレッスンの通りにやってもプログラムが動きません。
レッスンの公開当時からPhoenixフレームワークのバージョンが上がったためです。

この記事では、ElixirShcoolのNimblePublisherレッスンを最新のPhoenixフレームワークで動かすために対応する箇所を紹介します。

NimblePublisherとは

NimblePulisherとは、Dashbit社[1]が開発した記事公開ライブラリです。
Dashbit社のブログシステムの大部分をカプセル化して作られました。
https://dashbit.co/blog/welcome-to-our-blog-how-it-was-made

NimblePublisherのコードはGitHub上で公開されています。
https://github.com/dashbitco/nimble_publisher

NimblePublisherは、以下のような特徴を持ちます。

  • DB不要(ファイルベース)
  • 機能は必要最低限
  • マークダウン対応
  • コードハイライト対応

ElixirSchoolもNimblePublisherを使って記事を作成しています。
https://github.com/elixirschool/elixirschool#posting-an-article

変更点

ElixirSchoolのNimblePublisherのレッスンで作られたソースコードはGitHubで公開されています。
そこのmix.lockを見ると、Phoenixのバージョンがv1.5.6だと分かります。
Phoenix v1.5.6からv1.7.8へのアップデートに必要な対応が必要です(最低限動作するための変更になります)
https://github.com/jaimeiniesta/nimble_school/blob/49d6b6cf7112cc5037721b14a23bb89df1ba0d75/mix.lock#L16

  • ファイルやディレクトリ構造の変更、拡張子の変更
    • .eex.heexに拡張子を変更
      • 記法も変更
    • lib/nimble_school_web/controllers/blog_html/ を作成し、index.html.heexとshow.html.heexを配置
    • template/, view/ ディレクトリは不要
  • 呼び出すモジュールや関数を変更
    • RoutesVerifiedRoutesに変更
    • Phoenix.HTML.Link.link/2を、Phoenix.Component.link/1に置き換え

具体的な変更

ファイルやディレクトリ構造の変更、拡張子の変更

まず、Phoenixライブラリのバージョンが上がってディレクトリ構造やファイルの配置場所が変更になります。
具体的には、lib/nimble_school_web配下のtemplates/ディレクトリとviews/ディレクトリが不要になり、controllers/blog_html/ディレクトリを追加して記事一覧と記事詳細のためのファイルを配置します。
また、ファイルの拡張子が .eexから.heexに変更になります。

ディレクトリ構造の差分

差分のあるディレクトリやファイルには (*) をマークしました。
また、分かりやすくするため変更の無いディレクトリやファイルは省略しています。

【before】

lib
(前略)
├── nimble_school_web
│  ├── controllers
│  │  ├── blog_controller.ex
│  │  └── page_controller.ex
│  ├── templates(*)
│  │  ├── blog
│  │  │  ├── index.html.eex
│  │  │  └── show.html.eex
│  │  ├── layout
│  │  │  └── app.html.eex
│  │  └── page
│  │     └── index.html.eex
│  └── views(*)
│     ├── blog_view.ex
│     ├── error_helpers.ex
│     ├── error_view.ex
│     ├── layout_view.ex
│     └── page_view.ex
(後略)

【after】

lib
(前略)
├── nimble_school_web
│  ├── controllers
│  │  ├── blog_controller.ex
│  │  ├── blog_html(*)
│  │  │  ├── index.html.heex(*)
│  │  │  └── show.html.heex(*)
│  │  ├── blog_html.ex
│  │  ├── error_html.ex
│  │  ├── error_json.ex
│  │  ├── page_controller.ex
│  │  ├── page_html
│  │  │  └── home.html.heex
│  │  └── page_html.ex
(後略)

呼び出すモジュールや関数を変更

Phoenixのバージョンが上がり、aliasの設定などが変更されたため、モジュールや関数を変更します。

RoutesVerifiedRoutesに変更

lib/nimble_school_web/templates/blog/index.html.eexlib/nimble_school_web/templates/blog/show.html.eexで使っている Routesモジュールが呼べなくなっています。

Routesモジュールの実態は、NimbleSchoolWeb.Router.Helpersです。
lib/nimble_school_web.ex にある NimbleSchoolWeb.view/0 から view_helpers/0の中で alias NimbleSchoolWeb.Router.Helpers, as: Routes という形でalias登録されています。

NimbleSchoolWeb.view/0は、NimbleSchoolWeb.BlogViewから呼ばれます。
templatesのファイルはBlogViewを経由して呼ばれるため、templatesのファイル内でRoutesモジュールが呼べます。

lib/nimble_school_web/views/blog_view.ex
defmodule NimbleSchoolWeb.BlogView do
  use NimbleSchoolWeb, :view
end
lib/nimble_school_web.ex
defmodule NimbleSchoolWeb do

  ()

  def view do
    quote do
      use Phoenix.View,
        root: "lib/nimble_school_web/templates",
        namespace: NimbleSchoolWeb

      # Import convenience functions from controllers
      import Phoenix.Controller,
        only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1]

      # Include shared imports and aliases for views
      unquote(view_helpers())
    end
  end

  ()

  defp view_helpers do
    quote do
      # Use all HTML functionality (forms, tags, etc)
      use Phoenix.HTML

      # Import basic rendering functionality (render, render_layout, etc)
      import Phoenix.View

      import NimbleSchoolWeb.ErrorHelpers
      import NimbleSchoolWeb.Gettext
      alias NimbleSchoolWeb.Router.Helpers, as: Routes
    end
  end

  ()
end

Phoenix 1.7.8では、Phoenix.VerifiedRoutesモジュールを使うようになっているのでこれに置き換えます。
https://hexdocs.pm/phoenix/1.7.8/Phoenix.VerifiedRoutes.html

Phoenix.VerifiedRoutesでは、sigil_pによる短縮記法が利用できるようになっています。
https://hexdocs.pm/phoenix/1.7.8/Phoenix.VerifiedRoutes.html#sigil_p/2

この2つは同じパスを生成します
# before
NimbleSchoolWeb.Router.Helpers.blog_path(@conn, :show, post)

# after
~p"/blog/#{post.id}"

【before】

lib/nimble_school_web/templates/blog/index.html.eex
<h1>Listing all posts</h1>

<%= for post <- @posts do %>
  <div id="<%= post.id %>" style="margin-bottom: 3rem;">
    <h2>
      <%= link post.title, to: Routes.blog_path(@conn, :show, post)%>
    </h2>

    <p>
      <time><%= post.date %></time> by <%= post.author %>
    </p>

    <p>
      Tagged as <%= Enum.join(post.tags, ", ") %>
    </p>

    <%= raw post.description %>
  </div>
<% end %>

【after】

lib/nimble_school_web/controllers/blog_html/index.html.heex
<h1>Listing all posts</h1>

<%= for post <- @posts do %>
  <div id="{post.id}" style="margin-bottom: 3rem;">
    <h2>
      <.link href={~p"/blog/#{post.id}"}><%= post.title %></.link>
    </h2>

    <p>
      <time><%= post.date %></time> by <%= post.author %>
    </p>

    <p>
      Tagged as <%= Enum.join(post.tags, ", ") %>
    </p>

    <%= raw post.description %>
  </div>
<% end %>

Phoenix.HTML.Link.link/2を、Phoenix.Component.link/1に置き換え

読み込むモジュールが変わったことでリンクの書き方が変更になっています。

具体的には、
Phoenix.HTML.Link.link/2を呼び出す形から、
https://hexdocs.pm/phoenix_html/2.14.2/Phoenix.HTML.Link.html#link/2

Phoenix.Component.link/1を呼び出す形に変わっています。
https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#link/1

【before】

lib/nimble_school_web/templates/blog/index.html.eex
<%= link post.title, to: Routes.blog_path(@conn, :show, post)%>

【after】

lib/nimble_school_web/controllers/blog_html/index.html.heex
<.link href={~p"/blog/#{post.id}"}><%= post.title %></.link>

おわりに

ElixirSchoolのNimblePublisherのレッスンをPhoenix 1.7.8で動かす方法を紹介しました。
ElixirSchoolの記事はGitHubで公開されPRも受け付けているため、時間を見つけて修正PR投げてみようと思います。

この記事で書いたPhoenix 1.7.8対応のソースコードはこのリポジトリに載せています(コミット汚いけれど)
https://github.com/yellowsman/nimble_school_transcribing

脚注
  1. Dashbit社はElixir言語の生みの親であるJosé Valim氏が所属する会社です ↩︎

Discussion