fukuoka.ex/kokura.exのpiacereです
ご覧いただいて、ありがとうございます
Phoenixは、Rails同様、MVC※やルーティングによるWebアプリ構築を前提に置いているため、PHPのような気軽さでWebアプリを作るのに向いていないと思われがちです
※正確には、Phoenixのソレは、MVCのようで、MVCではありません
他にも、「Phoenixを使わず、薄いWebサーバを使いたい」といった声を聞くことがありますが、よくよく聞いてみると、ステータスコードをキメ細かく調整したり、サーバプロセスを自前で起動したい等の本来的な薄いWebサーバを必要としている訳では無く、「MVCを使いたくない」「ルーティングを書きたくない」といったレベルの要望だったりすることも多いです
そこで、Phoenixのルーティングやリクエストハンドラーを司る「Plug」の柔軟性を用いれば、PHPと同様の気軽さ…つまり「ロジック入りhtmlファイルを置いたパスにエンドポイントが作られる」という形式でWebアプリ開発することもできる … ということを実感していただくためにコラムを書きました
Advent Calendar、fukuoka.ex1位、Elixir2位達成ヽ(=´▽`=)ノ
fukuoka.ex Advent Calendar、Webテクノロジーカテゴリで堂々1位 … 各コラムぜひお読みください
https://qiita.com/advent-calendar/2020/fukuokaex
そして、プログラミング言語カテゴリは、1位がRust、2位がElixir、3位がGoとモダン言語揃い踏みでのトップ3、熱いネー
https://qiita.com/advent-calendar/2020/elixir
本コラムの検証環境
本コラムは、以下環境で検証しています(Windowsで実施していますが、Linuxやmacでも動作する想定です)
- Windows 10
- Elixir 1.10.1 ※最新版のインストール手順はコチラ
- Phoenix 1.4.15 ※最新版のインストール手順はコチラ
- Node.js 12.14.0
なおPhoenixは、1.3系でも動作します(ElixirもPhoenixのバージョンに準じた古いものでも大丈夫です)
事前準備:Phoenix PJを作成する
まずPhoenix PJを作成し、Phoenixを起動します
mix phx.new basic --no-ecto
…
Fetch and install dependencies? [Yn] 【Yを入力してEnter】
cd basic
iex -S mix phx.server
なおチョイ技として、上記Yで走るmix deps.getとnpm installのうち、npm installが遅いので、コンソールを2枚立てて、裏で走らせると、構築を待たずに先に進むこともできます
mix phx.new basic --no-ecto
…
Fetch and install dependencies? [Yn] 【Nを入力してEnter】
cd basic
mix deps.get
iex -S mix phx.server
cd 【上記Phoenix PJを作った元のフォルダ】
cd basic/asset
npm install
ブラウザで「http://localhost:4000」
にアクセスすると、Phoenixで作られたデフォルトのWebページが見れます
PHPのような気軽さでWebアプリを作るには?
Phoenixが前提としている「特定MVCへのルーティング」をhtml.eexファイル名へと自動マッピングし、mix phx.newで生成されたコントローラがデフォルトで素通ししていないGETパラメータを素通しすればOKです
なお、html.eexファイル名へのマッピング時、複数階層のフォルダもマッピング可能とすることで、サブフォルダ配下のhtml.eexファイルにも気軽にアクセスできるようになります
手順①:html.eexの表示をルーティング不要に
まずはルーティングで、「*path_」というワイルドカード指定をすることで、多階層のURLパスを「path_」パラメータで取得できるようになります
「path_」パラメータには、URLに指定されたフォルダ階層が、リストで入ってきます(たとえば、abc/defというパスであれば、[ "abc", "def" ]という感じで)
defmodule BasicWeb.Router do
…
scope "/", BasicWeb do
pipe_through :browser
# get "/", PageController, :index # <-- remove here
get "/*path_", PageController, :index # <-- add here
…
手順②:html.eexの複数階層フォルダ指定可能に
render()の第2引数に指定するパスは、デフォルトだと複数階層を指定できないので、Viewにワイルドカード的なパスのパターン指定を追加すれば、複数階層を指定可能にできます
defmodule BasicWeb do
…
def view do
quote do
use Phoenix.View,
pattern: "**/*", # <-- add here
root: "lib/basic_web/templates",
namespace: BasicWeb,
…
手順③:パスの自動マッピング、GETパラメータの素通し
PageControllerで、「path_」パラメータのフォルダ階層リストを、html.eexのパスにマッピングします
また、mix phx.newで生成されたコントローラが「_params」という感じでアンダースコアで未使用にしているので、これを解除し、renderの第3引数に渡すことで、GETパラメータを素通しさせます
defmodule BasicWeb.PageController do
use BasicWeb, :controller
def index( conn, params ) do
template = if params[ "path_" ] == nil, do: "index.html", else: Path.join( params[ "path_" ] ) <> ".html"
render( conn, template, params: params )
end
end
手順④:html.eexページを追加する
1)templates/page直下にhtml.eex追加
これで、page配下にhtml.eexを追加するだけで、新たなページが追加可能となったので、追加してみます
<h1>This is another page</h1>
<%= inspect( System.build_info() ) %>
<hr>
<%= inspect( @params ) %>
ここまでが終わったら、ブラウザで「http://localhost:4000/abc/def?abc=xyz123」
にアクセスすると、以下のような結果が表示されることを確認して、準備完了です
これで、ルーティングやMVCを追加せずとも、PHPのように、新たなページ追加やGETパラメータ処理できるようになりました
2)templates/page配下のサブフォルダ下にhtml.eex追加
pageフォルダ配下にフォルダ階層を掘って、そこにhtml.eexを配置しても動きます
「abc」というフォルダを掘り、「def.html.eex」というWebテンプレートを追加します
<h1>I'm def page</h1>
<%= inspect( @params ) %>
ブラウザで「http://localhost:4000/abc/def」
にアクセスすると、以下のような結果が表示されます(「path_」パラメータにフォルダ階層がリストで入っていることも確認できます)
3)GETフォーム処理も気軽に書く
GETパラメータが通るようになっているので、formでフォーム処理も気軽に書けます
checkboxのような複数値を取るタイプには、nameの末尾に「[]」を置く点が、忘れてはならないポイントです
<h1>I'm form page</h1>
<form method="GET" action="/abc/def">
<p>
メモ:
<input name="memo" type="text">
</p>
<p>
好きな言語:
<input name="language[]" type="checkbox" value="Elixir">Elixir
<input name="language[]" type="checkbox" value="Rust">Rust
<input name="language[]" type="checkbox" value="Julia">Julia
</p>
<p>
年代:
<select name="age">
<option value="10">10代
<option value="20">20代
<option value="30">30代
<option value="30">40代
<option value="50">50代
<option value="60">60代
<option value="70">70代以上
</select>
</p>
<input type="submit" value="送信">
</form>
ブラウザで「http://localhost:4000/abc/form」
にアクセスすると、以下のような結果が表示されます
「送信」ボタンを押下すると、入力されたパラメータが表示されますが、def.html.eexでやっているように、@ paramsの中身を使ってフォーム処理を行うことができます
4)POSTフォーム処理も気軽に書く
POSTパラメータによるフォーム処理に対応するには、ルーティングにPOSTの分も書きます
defmodule BasicWeb.Router do
…
scope "/", BasicWeb do
…
post "/*path_", PageController, :index # <-- add here
…
formのmethodをPOSTに変更します
<h1>I'm form page</h1>
<form method="POST" action="/abc/def">
^-- modify here
…
ブラウザで「http://localhost:4000/abc/form」
にアクセスし、「送信」ボタンを押下すると、GET同様、POSTでも入力されたパラメータが表示されるようになります(GETのときはURLにパラメータがあったのに対し、POSTでは無くなっています)
終わり
Phoenixで、PHPみたいに気軽なWebアプリ開発をする手順を紹介しました
Phoenixは、ルーティングとMVCを前提にしていますが、Plugの機能とコントローラでのパラメータ処理を併用すれば、このような気軽さを導入することもできます
無論、ルーティングやMVCは、理由があって採用されている面もあるので、そのメリット/デメリットはちゃんと学んだり、理解しつつ、一方で、このテクニックを使って、簡単なWebアプリ構築もお楽しみください
なお、Elixirサーバサイドでリアルタイムフロントが書ける「LiveView」においても、この気軽さを導入するコラム書いていますので、ご参考にどうぞ