作って学ぶPhoenix、Railsのアレどうやんの？随時更新

RailsチュートリアルをPhoenixで実装しました。  https://github.com/pojiro/sample_app

Phoenixではどうやるか？をまとめています。随時更新します。

※RailsはRailsチュートリアルでしかさわったことがありません。

# bundle install

~~~ sh
$ mix deps.get
~~~

# railsコマンド

## rails new

~~~ sh
$ mix phx.new
~~~

## rails console

~~~ sh
$ iex -S mix
~~~

## rails server

~~~ sh
$ mix phx.server
~~~

## rails db:create

~~~ sh
$ mix ecto.create
~~~

## rails db:migrate:reset & rails db:seed

~~~ sh
$ mix ecto.reset # aliasなので、自身のプロジェクトのmix.exsのdeps aliasesを見ること
~~~

## rails generate migration

~~~ sh
$ mix ecto.gen.migration
~~~

## rails db:migrate

~~~ sh
$ mix ecto.migrate
~~~

## rails db:rollback

~~~ sh
$ mix ecto.rollback
~~~

## rails test

~~~ sh
$ mix test
~~~

## rails destroy

ない。gitでdiscardする。

# modelのメソッドはどこに書くの？

schemaをラップするコンテキストモジュールに関数を作成する。

RailsとPhoenixは思想が大きく異なる。
Railsでは、データと関数のペアのクラスでモデルを定義するが、
Phoenixでは、データと関数をモジュールを分けて定義する。
schemaとchangesetをデータ側にそのデータを操作する関数の集まりをコンテキストモジュールとして定義する。

これはruby（オブジェクト指向）とelixir（関数型）の思想の違いによる。Phoenixでオブジェクト指向をやるのはよくない。

# validates

validatesの各キーはそれぞれ関数として用意される。

- presence => validate_required
- length => validate_length
- format => validate_format
- uniqueness => unique_constraint

※[Ecto.Changesetのドキュメント](https://hexdocs.pm/ecto/Ecto.Changeset.html)

# provide & yield

Phoenixではtemplate内での変数参照はPlug.connストラクトのassignsを介して行う。
渡したいデータはassign(conn, key, value)すると、template内では@ keyで参照できる。

※[Plug.Connのドキュメント](https://hexdocs.pm/plug/Plug.Conn.html)

# flash

put_flash(conn, key, message)をつかう。
template側で取るときはget_flash(conn, key)

# ファイルのアップロード

form_forでfile_inputを使う。[multipart: true]を忘れないようにする。

~~~ elixir
<%= form_for @changeset, Routes.micropost_path(@conn, :create), [multipart: true], fn f-> %>
  <div class="field">
    <%= textarea f, :content, placeholder: "Compose new micropost..." %>
    <%= error_tag f, :content %>
  </div>
  <span class="picture">
    <%= file_input f, :picture, class: "form-control" %>
    <%= error_tag f, :picture %>
  </span>
  <%= content_tag :input, nil, type: "submit", class: "btn btn-primary", value: "Post" %>
<% end %>
~~~

# sessionメソッド

~~~ elixir
put_session(conn, key, value)
get_session(conn, key)
delete_session(conn, key)
~~~

- expire期間をkey毎に設定できない。一律にはできる。  
  （endpoint.exのPlug.sessionにmax_ageオプションを設定する）
- デフォルトは改ざん防止のために署名される。
- 暗号化も可能。（endpoint.exのPlug.sessionにencryption_saltオプションを設定する）

# cookiesメソッド

~~~ elixir
put_resp_cookie(conn, key, value)
get_resp_cookie(conn, key)
delete_resp_cookie(conn, key)
~~~

- 署名、暗号化はされないので、必要な場合はput_resp_cookieする前に行う。Phoenic.Tokenが便利
- expire期間をkey毎に設定できる。

# before_action

plugを作って、

~~~ elixir
# 例
def logged_in_user(conn, _opts) do
  if logged_in?(conn) do
    conn
  else
    conn
    |> SessionHelper.store_location()
    |> put_flash(:error, "Please log in.")
    |> redirect(to: Routes.session_path(conn, :new))
    |> halt()
  end
end
~~~

コントローラにつなぐ。

~~~ elixir
defmodule SampleAppWeb.UserController do
  use SampleAppWeb, :controller
  plug :logged_in_user when action in [:index, :show, :edit, :update, :delete]
  ...
end
~~~

# request.referer

~~~ elixir
List.first(get_req_header(conn, "referer"))
~~~

# assert_select

[floki](https://github.com/philss/floki)を使ってassertすればやりたいことはできるはず。

~~~ elixir
conn = get(logged_in_conn, Routes.user_path(conn, :show, user))

parsed_html = Floki.parse_document!(html_response(conn, 200))
assert Floki.find(parsed_html, "h1") |> Floki.text() =~ user.name
~~~

# テストの自動実行、guard

guardに相当するのが[mix_test_watch](https://github.com/lpil/mix-test.watch)

~~~ sh
$ bundle exec guard # rails
$ mix test.watch # phoenix
~~~

# テスト結果の通知、minitest-reporters

[ex_unit_notifier](https://github.com/navinpeiris/ex_unit_notifier)を使う。

# heroku

herokuに相当するのがgigalixir。
コマンドも同じようにつくられているようだ。

~~~ sh
$ heroku logs
$ gigalixir logs

$ heroku run rails db:migrate
$ gigalixir run mix ecto.migrate
~~~