「Elixir/Phoenix初級(1)はじめの一歩」を読みました

Phoenixがわからないせいで個人開発がストップしたのでお勉強していきます。

Elixir/Phoenix 初級?: はじめの一歩 (OIAX BOOKS)

Elixir/Phoenix 初級?: はじめの一歩 (OIAX BOOKS)

成果物はこちら → c18t/hello-phoenix

TOC

  • はじめに
  • 01: Elixir/Phoenixへの誘い
  • 02: 各種ソフトウェアのインストール
  • 03: Elixirの初歩
  • 04: 基本データ型
  • 05: Hello, world!
  • 06: 経路設定の初歩
  • 07: コントローラとアクション
  • 08: HTMLとテンプレート
  • 09: パラメータ
  • 10: HTML文書の区分とスタイルシート
  • 11: BootstrapとFont Awesome
  • 12: モジュールと関数
  • 13: リストとタプル
  • 14: マップとキーワードリスト
  • 15: ビューモジュール
  • 16: ページ間のリンク
  • 17: 画像
  • 付録A: Chromeデベロッパーツール
  • 付録B: ベンチマークの計測
  • 付録C: Homebrewのインストール
  • 付録D: Node.jsのインストールと更新
  • 付録E: 本番モード(prod環境)

はじめに

  • 本書の対象はWebアプリ開発にはじめて挑戦する人
    • マジか。ElixirでWebアプリ開発の学習を初めてやる人なんてどれくらいいるんだ…
  • 第1巻では下記のテーマは扱わない
    • パターンマッチング
    • パイプ演算子
    • 再帰
    • 並行プログラミング
  • OIAX BOOKS『Ruby on Rails 5.0 初級①』をベースに、内容をElixir/Phoenix向けに書き直しているため、本文にかなりの重複がある
    • なるほどそれで対象読者がそうなったのか
  • ソフトウェアのバージョンはガンガン無視していきます

01: Elixir/Phoenixへの誘い

02: 各種ソフトウェアのインストール

割愛。Hex, rebar, Phoenixをインストールしました。

03: Elixirの初歩

お試しのスクリプト

n = 1
n = n + 1
IO.puts n

Erlangでは変数に再代入できなかったけど、Elixirでは可能。参照が差し替わるだけだからやらかしは起きないはず。

$ elixir primer/v01/ch03/variable.exs
2
  • IOはモジュール。
  • .putsは関数。関数のカッコは省略可能。
  • 式の終端は改行またはセミコロン
  • #以降はコメント
  • =begin ... =endで複数行コメント
    • 丸っきりRubyだ。

04: 基本データ型

割愛。整数、文字列、アトム。

Hello, world!

$ mix phoenix.new modest_greeter --no-ecto
* creating modest_greeter/config/config.exs
* creating modest_greeter/config/dev.exs
* creating modest_greeter/config/prod.exs
* creating modest_greeter/config/prod.secret.exs
* creating modest_greeter/config/test.exs
* creating modest_greeter/lib/modest_greeter.ex
* creating modest_greeter/lib/modest_greeter/endpoint.ex
* creating modest_greeter/test/views/error_view_test.exs
* creating modest_greeter/test/support/conn_case.ex
* creating modest_greeter/test/support/channel_case.ex
* creating modest_greeter/test/test_helper.exs
* creating modest_greeter/web/channels/user_socket.ex
* creating modest_greeter/web/router.ex
* creating modest_greeter/web/views/error_view.ex
* creating modest_greeter/web/web.ex
* creating modest_greeter/mix.exs
* creating modest_greeter/README.md
* creating modest_greeter/web/gettext.ex
* creating modest_greeter/priv/gettext/errors.pot
* creating modest_greeter/priv/gettext/en/LC_MESSAGES/errors.po
* creating modest_greeter/web/views/error_helpers.ex
* creating modest_greeter/.gitignore
* creating modest_greeter/brunch-config.js
* creating modest_greeter/package.json
* creating modest_greeter/web/static/css/app.css
* creating modest_greeter/web/static/css/phoenix.css
* creating modest_greeter/web/static/js/app.js
* creating modest_greeter/web/static/js/socket.js
* creating modest_greeter/web/static/assets/robots.txt
* creating modest_greeter/web/static/assets/images/phoenix.png
* creating modest_greeter/web/static/assets/favicon.ico
* creating modest_greeter/test/controllers/page_controller_test.exs
* creating modest_greeter/test/views/layout_view_test.exs
* creating modest_greeter/test/views/page_view_test.exs
* creating modest_greeter/web/controllers/page_controller.ex
* creating modest_greeter/web/templates/layout/app.html.eex
* creating modest_greeter/web/templates/page/index.html.eex
* creating modest_greeter/web/views/layout_view.ex
* creating modest_greeter/web/views/page_view.ex

Fetch and install dependencies? [Yn]
* running mix deps.get
* running npm install && node node_modules/brunch/bin/brunch build

We are all set! Run your Phoenix application:

    $ cd modest_greeter
    $ mix phoenix.server

You can also run your app inside IEx (Interactive Elixir) as:

    $ iex -S mix phoenix.server

modest_greeterプロジェクトが出来たので動かしてみる。

$ cd modest_greeter
$ mix phoenix.server

12:22:04.503 [info]  Compiling file system watcher for Mac...

12:22:06.052 [info]  Done.
==> file_system
Compiling 7 files (.ex)
Generated file_system app
==> gettext
Compiling 1 file (.yrl)
Compiling 1 file (.erl)
Compiling 20 files (.ex)
Generated gettext app
===> Compiling ranch
warning: String.strip/1 is deprecated, use String.trim/1
  /Users/private/Projects/github.com/c18t/hello-phoenix/modest_greeter/deps/poison/mix.exs:4

==> poison
Compiling 4 files (.ex)
warning: Integer.to_char_list/2 is deprecated, use Integer.to_charlist/2
  lib/poison/encoder.ex:173

Generated poison app
==> phoenix_pubsub
Compiling 12 files (.ex)
Generated phoenix_pubsub app
===> Compiling cowlib
src/cow_multipart.erl:392: Warning: call to crypto:rand_bytes/1 will fail, since it was removed in 20.0; use crypto:strong_rand_bytes/1

===> Compiling cowboy
==> mime
Compiling 1 file (.ex)
Generated mime app
==> plug
Compiling 1 file (.erl)
Compiling 48 files (.ex)
warning: function :cowboy_stream_h.data/4 is undefined (module :cowboy_stream_h is not available)
  lib/plug/adapters/cowboy2/stream.ex:9

warning: function :cowboy_stream_h.info/3 is undefined (module :cowboy_stream_h is not available)
Found at 2 locations:
  lib/plug/adapters/cowboy2/stream.ex:13
  lib/plug/adapters/cowboy2/stream.ex:21

warning: function :cowboy_stream_h.init/3 is undefined (module :cowboy_stream_h is not available)
  lib/plug/adapters/cowboy2/stream.ex:5

Generated plug app
==> phoenix_html
Compiling 8 files (.ex)
Generated phoenix_html app
==> phoenix
Compiling 74 files (.ex)
warning: Enum.partition/2 is deprecated, use Enum.split_with/2
  lib/phoenix/endpoint/handler.ex:39

warning: Enum.partition/2 is deprecated, use Enum.split_with/2
  lib/mix/tasks/phoenix.gen.model.ex:155

warning: Enum.partition/2 is deprecated, use Enum.split_with/2
  lib/mix/phoenix/schema.ex:234

warning: Plug.Conn.WrapperError.reraise/3 is deprecated. Use reraise/1 or reraise/4 instead.
Found at 2 locations:
  lib/phoenix/controller/pipeline.ex:138
  lib/phoenix/router.ex:280

Generated phoenix app
==> phoenix_live_reload
Compiling 4 files (.ex)
Generated phoenix_live_reload app
mix phoenix.server is deprecated. Use phx.server instead.
==> modest_greeter
Compiling 11 files (.ex)
warning: Plug.Conn.WrapperError.reraise/3 is deprecated. Use reraise/1 or reraise/4 instead.
Found at 2 locations:
  web/router.ex:4
  web/router.ex:12

Generated modest_greeter app
[info] Running ModestGreeter.Endpoint with Cowboy using http://0.0.0.0:4000
12:22:20 - info: compiled 6 files into 2 files, copied 3 in 815 ms

f:id:Cormorant:20180526184453p:plain

動いた!

Hello, world!をやるため、不要なファイルを削除していく。

rm web/controllers/page_controller.ex
rm web/static/css/phoenix.css
rm web/views/page_view.ex
rm -rf web/templates/page/

続いて、router.exの修正

defmodule ModestGreeter.Router do
  use ModestGreeter.Web, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  scope "/", ModestGreeter do
    pipe_through :browser
  end
end

レイアウトテンプレートの修正

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">

    <title>ModestGreeter</title>
    <link rel="stylesheet" href="<%= static_path(@conn, "/css/app.css") %>">
  </head>

  <body>
    <%= render @view_module, @view_template, assigns %>
    <script src="<%= static_path(@conn, "/js/app.js") %>"></script>
  </body>
</html>

経路の作成

web/router.exget "/hello", HelloController, :showを追加。

アクションの作成

defmodule ModestGreeter.HelloController do
  use ModestGreeter.Web, :controller

  def show(conn, _params) do
    render conn, "show.html"
  end
end

ビューの作成

defmodule ModestGreeter.HelloView do
  use ModestGreeter.Web, :view
end

テンプレートの作成

<p>Hello, world!</p>

再度サーバー実行

mix phoenix.server

http://localhost:4000/hello にアクセスすると

f:id:Cormorant:20180526184735p:plain

テンプレートの内容がレイアウトに沿って出た!

06: 経路設定の初歩

割愛。よくあるフレームワークのルーティングと同じように定義する。

07: コントローラとアクション

割愛。リクエストの構造体connについては8章、16章、そして次巻で取り扱う。 メタプログラミングのコラムあり。

08: HTMLとテンプレート

HTML自体の説明とテンプレートについて。erbとだいたい一緒。

テンプレートに値を渡すには、アクションのrender関数の第3引数にキーワードリストを渡す。テンプレート内ではキーワードで要素にアクセスできる。

アクションの修正

defmodule ModestGreeter.HelloController do
  use ModestGreeter.Web, :controller

  def show(conn, _params) do
    render conn, "show.html", name: "Alice"
  end
end

テンプレートの修正

<p>Hello, <%= @name %>!</p>

f:id:Cormorant:20180526184807p:plain

テンプレート中の @ はマクロであり、キーワードリストの値を取得するコードに展開される。

レイアウトテンプレートでも渡されたキーワードリストを参照できる。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">

    <title>ModestGreeter (<%= @name %>)</title>
    ...

f:id:Cormorant:20180526184948p:plain

ライブリロードされることも確認できた。

09: パラメータ

クエリパラメータを受け取るにはこうする。

defmodule ModestGreeter.HelloController do
  use ModestGreeter.Web, :controller

  def show(conn, params) do
    render conn, "show.html", name: param["name"]
  end
end

f:id:Cormorant:20180526185034p:plain

URLパスにパラメータを埋め込むにはこうする。

defmodule ModestGreeter.Router do
  use ModestGreeter.Web, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  scope "/", ModestGreeter do
    pipe_through :browser

    get "/hello", HelloController, :show
    get "/hello/:name", HelloController, :show
  end
end

URLパラメータの省略記法はないのでパラメータなしのルートも作成する。

f:id:Cormorant:20180526185144p:plain

デフォルト値を設定するにはこうする。

defmodule ModestGreeter.HelloController do
  use ModestGreeter.Web, :controller

  def show(conn, params) do
    render conn, "show.html", name: param["name"] || "world"
  end
end

f:id:Cormorant:20180526185152p:plain

汚染が気になる…

f:id:Cormorant:20180526185209p:plain

ちゃんと除染されてた。

10: HTML文書の区分とスタイルシート

割愛。HTMLとCSSについて。PhoenixはBrunchでSCSSなどのリソースを変換・出力をしている。

11: BootstrapとFont Awesome

割愛。章タイトルまま。

12: モジュールと関数

モジュールの定義と利用について。

defmodule Hello1 do
  def greet(name) do
    IO.puts "Hello, #{name}!"
  end
end

Hello1.greet "Alice"
Hello1.greet "Bob"
$ elixir primer/v01/ch12/hello1.exs
Hello, Alice!
Hello, Bob!

関数はモジュール外に定義できないので注意。

exファイルをコンパイルするとbeamファイルができる。

defmodule Hello do
  def greet(name) do
    IO.puts "Hello, #{name}!"
  end
end
$ elixirc primer/v01/ch12/hello2.ex
$ ls
Elixir.Hello2.beam modest_greeter     primer
$ iex
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe][kernel-poll:false]

Interactive Elixir (1.6.5) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Hello2.greet "Alice"
Hello, Alice!
:ok
iex(2)>

スクリプトはカレントディレクトリのBeamファイルも読み込む。

Hello2.greet "Alice"
Hello2.greet "Bob"
$ elixir primer/v01/ch12/hello2.exs
Hello, Alice!
Hello, Bob!

後は割愛。引数のアリティとデフォルト値について。また、無名関数について。

13: リストとタプル

割愛。コラムはErlang / Elixirのリストは連結リストだから末尾の参照は遅いよという話。

14: マップとキーワードリスト

割愛。

15: ビューモジュール

割愛。テンプレートから@view_moduleでアクセスできる。

16: ページ間のリンク

割愛。リンクは<a>タグではなくlink関数とパスヘルパーで作ろうねという話。パスヘルパーの最後の引数[]を省略するエレガントなやり方は次巻で。

17: 画像

割愛。

付録A〜D

割愛。

付録E: 本番モード(prod環境)

mix deps.get --only prod
MIX_ENV=prod PORT=4001 mix do compile, phoenix.server

感想

本当にはじめの一歩といった内容で、さすがにWebアプリ開発は全くの初心者ってわけではないのであまり自分向きではなかった。Elixirの言語仕様については他所でやってしまったので、Phoenixの情報を期待していたんだけど、自分にとって目新しいものはルーティングと付録Eくらいか。

Web技術(HTML,CSS)の解説とElixirの仕様解説が程良い塩梅で用意されており、テキスト通りすすめるための準備が大変なのを除けば、順序よくステップアップしていける内容になっているので良いと入門に良いと思います。この本の想定している読者が果たしてElixirに入門すべきかは疑問ですが…素直にRuby on RailsとかLaravelやったほうがいいと思う。ただ、本当に新しめのスタンダートな内容を丁寧に手取り足取り教えてくれるいい本だと思いますので、これからWebを始めようと考えていて、RubyPHPPythonなんてみんながやってる言語は嫌で、やるなら当然モダンでハイパフォーマンスなプログラムを書きたくて、時代は関数型言語だ!って方にならオススメできます。