やってみよう!Elixir / Phoenix 関数型プログラミングの基礎⑤

ルーター
インデックスページの作成
/
にアクセスした場合、画面には「Greeting」と書かれたリンクが表示される。リンク先は/hello
とする。- /helloにアクセスした場合、画面には「Hello World!」というメッセージと、「Back to Home」と書かれたリンクが表示される。リンク先は
/
とする。
indexアクションの追加
defmodule PhxSampleWeb.HomeController do
use PhxSampleWeb, :controller
#追加
def index(conn, _params) do
render(conn, :index)
end
#ここまで
def hello(conn, _params) do
render(conn, :hello)
end
end
次に、このアクションのためのHEExテンプレートindex.html.heex
を作成します。
<p>
<a href="/hello" class="underline text-blue-700">Greeting</a>
</p>
続いて、「Hello World!」を表示するためのHEExテンプレートhello.html.heex
を次のように書き換えます。
<p>
<a href="/" class="underline text-blue-700">Back to Home</a>
</p>
router.ex
を書き換えます。
scope "/", PhxSampleWeb do
pipe_through :browser
get "/", HomeController, :hello
get "/", HomeController, :index
get "/hello", HomeController, :hello
end
end
編集の結果、phx_sampleで有効なURLパスが/と/helloに増えます。GETメソッドでこれらのパスに対するHTTPリクエストが届いたとき、HomeControllerモジュールのindexアクションとhelloアクションがHTML文書を作って返します。
検証済みの経路(Verified routes)
indexアクションのためのHEExテンプレートindex.html.heex
を次のように書き換えてください。
<p>
<a href={~p(/hello)} class="underline text-blue-700">Greeting</a>
</p>
ブラウザの画面変化はありませんが、機能的には重要な変化が起きています。それはhref
属性に指定されたパス/hello
がルーティングに登録されているかどうかがコンパイル時にチェックされるということです。
<a>
タグのhref
属性に対して単に文字列を指定する代わりに、文字列の前にシギル~pを加え中括弧で囲んでいます。Elixirのコンパイラは、シギル~pが加えられた文字列が経路として正しいかどうかを検証し、正しくなければ警告を出します。この機能は検証された経路(Verified routes)と呼ばれます。
hello.html.heexの書き換え
helloアクションのためのHEExテンプレートhello.html.heex
についても、href属性の値を中括弧とシギル~pを利用する形に書き換えてください。
<p class="text-red-500 font-bold text-2xl">Hello, {@name}!</p>
<p>
<a href={~p(/)} class="underline text-blue-700">Back to Home</a>
</p>
リクエストパラメーター
- インデックスページにある「Greeting」リンクを除去して、「Alice」、「Bob」、「Carol」という3つのリンクを
<ul>
タグを使った箇条書き形式で表示する。 - 「Alice」がクリックされたら、「Hello Alice」と表示する
- 「Bob」がクリックされたら、「Hello Bob」と表示する
- 「Carol」がクリックされたら、「Hello Carol」と表示する
indexページの改造
indexアクションのためのHEExテンプレートindex.html.heex
を次のように書き換えてください。
<ol class="list-disc ml-4">
<%= for name <- ~w(Alice Bob Carol) do %>
<li>
<a href={~p(/hello/#{name})} class="underline text-blue-700">
{name}
</a>
</li>
<% end %>
</ol>
“Alice”,"Bob","Carol"という3個の要素を持つリストからforマクロで1個ずつ要素を取り出して、<li>…</li>
で囲まれた<a>
要素を生成しています。
HEExテンプレートの中でforマクロを使う場合の基本的な構文は次の通りです。
<%= for A <- B do %>
C
<% end %>
典型的な使い方ではAに変数、Bにリストをあてはめます。そして、Cの中でAを埋め込むことにより、HTML断片の繰り返しを生成します。5行目の{name}
により変数name
の値が埋め込まれます。
<a>
のhref
属性には次のような式が指定されています。
~p(/hello?name=#{name})
#{name}
の部分は、"Alice"
などの文字列で置き換わりますので、実際には /hello?name=Alice
のような URL が href
属性の値になります。URL の末尾にある ?name=Alice
の部分はクエリ文字列と呼ばれます。
クエリ文字列を含む URL へのリクエストがあると、ルーターはその部分をマップ型の値に変換してアクションに渡します。例えば、?name=Alice
は次のようなマップに変換されます。
%{"name" => "Alice"}
このマップをリクエストパラメータ(request parameters)と呼びます。
helloアクションの改造
helloアクションhome_controller.ex
を次のように書き換えてください。
def hello(conn, %{"name" => name} = _params) do
render(conn, :hello, name: name)
end
こうすることで、アクション hello
がリクエストパラメータを受け取れるようになります。ブラウザから /hello?name=Alice
という URL に対してリクエストが届くと、変数 name
に "Alice"
がセットされます。
hello
アクションのための HEEx テンプレートを次のように書き換えてください。
<p class="text-red-500 font-bold text-2xl">Hello, {@name}!</p>
<p>
<a href={~p(/)} class="underline text-blue-700">Back to Home</a>
</p>
リダイレクション
例外Phoenix.ActionClauseError
前節で行った変更により JauntyGreeter は /hello?name=Alice
のようなクエリ文字列の URL を扱えるようになりました。ここで考慮すべきことがあります。もしもユーザーがブラウザのアドレスバーからクエリ文字列を削除して Enter キーを押したら、どうなるでしょうか?
実際に試してみると、ブラウザにエラー画面が表示されます。
Phoenix.ActionClauseError
という名前の例外が発生しています。エラー画面の中に次のようなメッセージがあります:
no function clause matching in JauntyGreeterWeb.HomeController.hello/2
関数 HomeController.hello/2
において、マッチする関数節(function clause)が存在しないと言っています。パターンマッチングを用いて関数が定義されているときに頻出するエラーメッセージです。
続くエラーメッセージをご覧ください:
The following arguments were given to JauntyGreeterWeb.HomeController.hello/2:
「次のような引数が関数 HomeController.hello/2
に与えられた」と言っています。
その下には与えられた引数の値が列挙されています。
# 1
%PlugConn{...}`
# 2
%{}
注目すべきは第 2 引数の値が空のマップ(%{}
)となっていることです。
以上の観察を踏まえて、関数 HomeController.hello/2
のコードを見返してみましょう。
def hello(conn, %{"name" => name} = _params) do
render(conn, :hello, name: name)
end
第 2 引数が %{"name" => name} = _params
と書かれています。第 2 引数として与えられたマップが "name"
キーを持つ場合にのみこの関数節がマッチします。今は、第 2 引数に空のマップが与えられているので、マッチする関数節が存在せず、例外 Phoenix.ActionClauseError
が発生したのです。
ホームページへのリダイレクション
ユーザーがブラウザのアドレスバーに /hello
と入力したときには、強制的にホームページに遷移させることにしましょう。
heme_controller.ex
を次のように書き換えてください
def hello(conn, _params) do
redirect(conn, to: ~p(/))
end
Phoenix.Controller
モジュールの関数 redirect/2 は、別の URL への強制遷移、すなわちリダイレクション(redirection)を発生させます。この関数は 2 つの引数を取ります:
Plug.Conn
構造体:to
オプションまたは:external
オプション
リダイレクション先の URL がホスト名を含まない場合は :to
オプションを、含む場合は :external
オプションを指定してください。ここでは、to: ~p(/)
を指定することで、ホームページに遷移させています。
パスパラメーター
この節では hello
アクションの URL パスを /hello?name=Alice
のような形式から /hello/Alice
のような形式に変更します。
ルーティング設定の変更
router.ex
を次のように書き換えてください
scope "/", JauntyGreeterWeb do
pipe_through :browser
get "/", HomeController, :index
get "/hello", HomeController, :hello
get "/hello/:name", HomeController, :hello
end
end
追加された経路のパス /hello/:name
に含まれる :name
に注目してください。この部分は任意の文字列にマッチします。すなわち、/hello/Alice
や /hello/Bob
が正しい URL パスとして認識されれます。そして、この可変部分にある文字列からマップ型の値が作られ、アクションに送られます。例えば、ブラウザが hello/Alice
という URL にアクセスした場合、次のようなマップが作られます。
%{"name" => "Alice"}
これをパスパラメータと呼びます。パスパラメータとリクエストパラメータを総称して「パラメータ」と呼びます。
HEExテンプレートの書き換え
新たなルーティング設定に合わせて index.html.heex
を次のように書き換えてください
<ol class="list-disc ml-4">
<%= for name <- ~w(Alice Bob Carol) do %>
<li>
<a href={~p(/hello?name=#{name})} class="underline text-blue-700">
<a href={~p(/hello/#{name})} class="underline text-blue-700">
{name}
</a>
</li>
<% end %>
</ol>
ブラウザでインデックスページを開き、従来どおり正常に機能することを確認してください。