PhoenixとElixirでブログエンジンを作成する/パート1。





翻訳者から:「 エリクサーとフェニックスは、最新のウェブ開発がどこに進んでいるかの良い例です。 すでにこれらのツールは、Webアプリケーションのリアルタイムテクノロジーへの質の高いアクセスを提供します。 対話性が向上したサイト、マルチユーザーブラウザーゲーム、マイクロサービスは、これらの技術がうまく機能する分野です。 以下は、Phoenixフレームワークでの開発の詳細な側面を説明する一連の11の記事の翻訳です。ブログエンジンのような些細なことのように思えます。 急いでつまずかないでください。特に記事がエリクサーに注意を払うか、彼のフォロワーになるように促した場合、それは本当に面白いでしょう 。」



現時点では、アプリケーションは以下に基づいています。





フェニックスのインストール



Phoenixの最適なインストール手順は、公式Webサイトにあります。



ステップ1.投稿を追加する



mixタスクを起動して、 pxblogという新しいプロジェクトを作成することから始めましょう。 これを行うには、コマンド`mix phoenix.new [project] [command]`を実行します。 デフォルトの設定が適切であるため、すべての質問に肯定的に答えます。



mix phoenix.new pxblog
      
      





結論:



 * creating pxblog/config/config.exs ... Fetch and install dependencies? [Yn] y * running mix deps.get * running npm install && node node_modules/brunch/bin/brunch build We are all set! Run your Phoenix application: $ cd pxblog $ mix phoenix.server You can also run your app inside IEx (Interactive Elixir) as: $ iex -S mix phoenix.server Before moving on, configure your database in config/dev.exs and run: $ mix ecto.create
      
      





プロジェクトの作成が完了したという多くの情報と、準備作業のチームが表示されるはずです。 それらを1つずつ実行します。



Postgresデータベースを作成していない場合、またはデータベースが動作するようにアプリケーションが設定されていない場合、 `mix ecto.create`コマンドはエラースローします。 修正するには、 config / dev.exsファイルを開き、データベースを作成する権限を持つロールのユーザー名とパスワードを変更するだけです:



 # Configure your database config :pxblog, Pxblog.Repo, adapter: Ecto.Adapters.Postgres, username: "postgres", password: "postgres", database: "pxblog_dev", hostname: "localhost", pool_size: 10
      
      





すべてが機能したら、サーバーを起動して、すべてが正常であることを確認しましょう。



 $ iex -S mix phoenix.server
      
      





これを行うには、アドレスhttp:// localhost:4000 /に移動し 、「 Welcome to Phoenix!」を参照してください 。 良い基盤が整いました。 ブログエンジンがまだあるので、投稿を操作するためのメインの足場を追加しましょう。



フェニックスの組み込みジェネレーターを使用して、 PostモジュールのCRUD操作を処理するための外部モデル、移行、およびインターフェイスを作成します。 現時点ではこれは非常に単純なエンジンであるため、タイトルとメッセージに限定しています。 タイトルは文字列になり、メッセージはテキストになります。 すべてを作成するチームは非常に単純です。



 $ mix phoenix.gen.html Post posts title:string body:text
      
      





結論:



 * creating web/controllers/post_controller.ex ... Add the resource to your browser scope in web/router.ex: resources "/posts", PostController Remember to update your repository by running migrations: $ mix ecto.migrate
      
      





エラーが発生します! これを修正し、同時にブラウザから投稿インターフェイスにアクセスできるようにするには、 web / router.exファイルを開いて、次の行をルートスコープに追加します。



 resources "/posts", PostController
      
      





これでコマンドを再度実行し、移行が成功したことを確認できます。



 $ mix ecto.migrate
      
      





結論:



 Compiling 9 files (.ex) Generated pxblog app 15:52:20.004 [info] == Running Pxblog.Repo.Migrations.CreatePost.change/0 forward 15:52:20.004 [info] create table posts 15:52:20.019 [info] == Migrated in 0.0s
      
      





そして最後に、サーバーを再起動し、 http:// localhost:4000 / postsページに移動します。ここで、 リストの投稿の見出しと投稿リストが表示されます。



少し手を加えて、新しい投稿を追加、編集、削除する機会を得ました。 このような小さな作業にはかなりクールです!



ステップ1B。 投稿のテストを書く



足場で作業する喜びの1つは、最初から一連の基本的なテストを取得できることです。 特に、アプリケーション自体のコードに重大な変更を加えるまで、それらを変更する必要さえありません。 次に、作成されたテストを見て、独自のテストの書き方をよりよく理解しましょう。



最初に、ファイルtest / models / post_test.exsを開いて、内容を見てください:



 defmodule Pxblog.PostTest do use Pxblog.ModelCase alias Pxblog.Post @valid_attrs %{body: "some content", title: "some content"} @invalid_attrs %{} test "changeset with valid attributes" do changeset = Post.changeset(%Post{}, @valid_attrs) assert changeset.valid? end test "changeset with invalid attributes" do changeset = Post.changeset(%Post{}, @invalid_attrs) refute changeset.valid? end end
      
      





ここで何が起こっているのかを理解するために、このコードを部分的に分解してみましょう。

defmodule Pxblog.PostTest do
明らかに、アプリケーションの名前空間にテストモジュールを定義する必要があります。

Pxblog.ModelCaseを使用します
次に、このモジュールに、 ModelCaseマクロセットで提供される関数とDSLを使用するように指示します。

エイリアスPxblog.Post
次に、テストがモデルに直接アクセスできることを確認します。

@ valid_attrs%{body:“ some content”、title:“ some content”}
リビジョンchangeset )を正常に作成できるようにする主な有効な属性を設定します。 有効なモデルを作成するたびにプルできるモジュールレベルの変数です。

@ invalid_attrs%{}
上記と同じですが、無効な属性のセットを作成します。

 「有効な属性を持つチェンジセット」をテストする
   changeset = Post.changeset(%Post {}、@ valid_attrs)
   changeset.validをアサートしますか?
終わり 
ここで、 テスト関数を使用してテストを直接作成し、文字列名を付けます。 関数の本体内で、最初にPostモデルからリビジョンを作成します(空の構造と有効なパラメーターのリストを渡します)。 次に、 アサート関数を使用して、リビジョンの有効性を検証します。 これは、まさに@valid_attrs変数が必要なものです

 「無効な属性を持つ変更セット」をテストする
   changeset = Post.changeset(%Post {}、@ invalid_attrs)
   changeset.validに反論しますか?
終わり 
最後に、無効なパラメーターを使用してリビジョンの作成を確認し、リビジョンが有効であることを「主張」する代わりに、逆反論操作を実行します。



これは、モデルテストの作成方法の非常に良い例です。 それでは、コントローラーのテストを見てみましょう。 それを見ると、次のようなものが見えるはずです。



 defmodule Pxblog.PostControllerTest do use Pxblog.ConnCase alias Pxblog.Post @valid_attrs %{body: "some content", title: "some content"} @invalid_attrs %{} ... end
      
      





Pxblog.ConnCaseを使用して、特別なコントローラーレベルのDSLを取得します。 残りの行は既におなじみのはずです。



最初のテストを見てみましょう:



 test "lists all entries on index", %{conn: conn} do conn = get conn, post_path(conn, :index) assert html_response(conn, 200) =~ "Listing posts" end
      
      





ここで変数connをキャプチャします。これはConnCaseチューナーを介して送信する必要があります。 これについては後で説明します。 次のステップでは、適切なHTTPメソッドで同じ名前の関数を使用して、目的のパスに沿って要求を行います(アクションに対するGET要求 この場合はインデックス )。 次に、このアクションの応答がステータス200(「OK」)の HTMLを返し、フレーズListing postsが含まれていることを確認します。



 test "renders form for new resources", %{conn: conn} do conn = get conn, post_path(conn, :new) assert html_response(conn, 200) =~ "New post" end
      
      





次のテストは基本的に同じで、 新しいアクションのみが既にテストされています。 すべてがシンプルです。



 test "creates resource and redirects when data is valid", %{conn: conn} do conn = post conn, post_path(conn, :create), post: @valid_attrs assert redirected_to(conn) == post_path(conn, :index) assert Repo.get_by(Post, @valid_attrs) end
      
      





そして、ここで何か新しいことをしています。 最初に、有効なパラメーターのリストを含むPOSTリクエストをpost_pathに送信します。 投稿のリストにリダイレクトされる予定です(action :index )。 redirected_to関数は、リダイレクトが発生した場所を知る必要があるため、接続オブジェクトを引数として受け取ります。



最後に、これらの有効なパラメーターによって表されるオブジェクトがデータベースに正常に追加されたと主張します。 このチェックは、 Ecto Repoリポジトリへのリクエストを通じて行われ、 @ valid_attrsパラメーターに一致するPostモデルを見つけます。



 test "does not create resource and renders errors when data is invalid", %{conn: conn} do conn = post conn, post_path(conn, :create), post: @invalid_attrs assert html_response(conn, 200) =~ "New post" end
      
      





もう一度、投稿の作成を試みますが、パラメーター@invalid_attrsの無効なリストを使用して、投稿を作成するためのフォームが再び表示されることを確認します。



 test "shows chosen resource", %{conn: conn} do post = Repo.insert! %Post{} conn = get conn, post_path(conn, :show, post) assert html_response(conn, 200) =~ "Show post" end
      
      





showアクションをテストするには、 Postモデルを作成する必要があります。これを使用して作業します。 次に、 post_pathヘルパーを使用してget関数を呼び出し、対応するリソースが返されることを確認します。



次のように、存在しないリソースへのパスを要求することもできます。



 test "renders page not found when id is nonexistent", %{conn: conn} do assert_error_sent 404, fn -> get conn, post_path(conn, :show, -1) end end
      
      





別のテスト記録テンプレートを使用しますが、実際には非常に簡単に理解できます。 存在しないリソースへのリクエストがエラー404につながるという期待について説明します。そこで、実行時にこのエラーを返すコードを含む匿名関数も渡します。 すべてがシンプルです!



残りのテストは、残りのパスに対してのみ上記を繰り返します。 しかし、削除の詳細については次のとおりです。



 test "deletes chosen resource", %{conn: conn} do post = Repo.insert! %Post{} conn = delete conn, post_path(conn, :delete, post) assert redirected_to(conn) == post_path(conn, :index) refute Repo.get(Post, post.id) end
      
      





一般に、HTTP 削除メソッドを使用することを除いて、すべてが類似しています。 削除ページから投稿のリストにリダイレクトする必要があると主張しています。 ここでも新しい機能を使用します-refute関数を使用してPostオブジェクトの存在を「拒否」します。



ステップ2.ユーザーを追加する



ユーザーモデルを作成するには、他の列を追加することを除いて、投稿のモデルを作成するときとほぼ同じ手順を実行します。 開始するには、次を実行します。



 $ mix phoenix.gen.html User users username:string email:string password_digest:string
      
      





結論:



 * creating web/controllers/user_controller.ex ... Add the resource to your browser scope in web/router.ex: resources "/users", UserController Remember to update your repository by running migrations: $ mix ecto.migrate
      
      





次に、 web / router.exファイルを開き、以前と同じスコープに次の行を追加します。



 resources "/users", UserController
      
      





ここの構文は、最初の引数がURLで、2番目がコントローラークラスの名前である標準リソースパスを定義します。 それから:



 $ mix ecto.migrate
      
      





結論:



 Compiling 11 files (.ex) Generated pxblog app 16:02:03.987 [info] == Running Pxblog.Repo.Migrations.CreateUser.change/0 forward 16:02:03.987 [info] create table users 16:02:03.996 [info] == Migrated in 0.0s
      
      





最後に、サーバーを再起動し、 http:// localhost:4000 / usersを確認します 。 投稿に加えて、ユーザーを追加できるようになりました!



残念ながら、これはまだあまり有用なブログではありません。 最終的には、ユーザーを作成できます( 残念ながら、誰でも作成できるようになっています )が、ログインすらできません。 さらに、パスワードダイジェストは暗号化アルゴリズムを使用しません。 ユーザーが入力したテキストを愚かに保持します! まったくクールではありません!



この画面をもっと登録のようなビューにしましょう。



ユーザーテストは投稿用に自動生成されたものとまったく同じように見えるので、ロジックを変更し始めるまでそのままにしておきます( そして今すぐそれを行います! )。



ステップ3.パスワード自体の代わりにパスワードハッシュを保存する



アドレス/ users / newを開くと、 UsernameEmailPasswordDigestの 3つのフィールドが表示されます。 しかし、他のサイトで登録するときは、パスワードダイジェストではなく、パスワード自体とその確認を入力するように求められます! どうすれば修正できますか?



web / templates / user / form.html.eexファイルで 、次の行を削除します。



 <div class="form-group"> <%= label f, :password_digest, class: "control-label" %> <%= text_input f, :password_digest, class: "form-control" %> <%= error_tag f, :password_digest %> </div>
      
      





そして、代わりに追加します:



 <div class="form-group"> <%= label f, :password, "Password", class: "control-label" %> <%= password_input f, :password, class: "form-control" %> <%= error_tag f, :password %> </div> <div class="form-group"> <%= label f, :password_confirmation, "Password Confirmation", class: "control-label" %> <%= password_input f, :password_confirmation, class: "form-control" %> <%= error_tag f, :password_confirmation %> </div>
      
      





ページが更新されたら(自動的に行われます)、ユーザーデータを入力し、[ 送信 ]ボタンをクリックします。



エラー:



 Oops, something went wrong! Please check the errors below.
      
      





これは、アプリケーションが何も知らないパスワードとパスワード確認フィールドを使用するためです。 この問題を解決するコードを書きましょう。



回路を変更することから始めましょう。 web / models / user.exファイルで、次の行を追加します



 schema "users" do field :username, :string field :email, :string field :password_digest, :string timestamps # Virtual Fields field :password, :string, virtual: true field :password_confirmation, :string, virtual: true end
      
      





passwordpassword_confirmationの2つのフィールドが追加されていることに注意してください。 実際にはデータベースに存在しないため、仮想フィールドとして宣言しましたが、 ユーザー構造のプロパティとして存在する必要があります 。 また、 チェンジセット機能に変換を適用することもできます。



次に、必須フィールドのリストに:passwordおよび:password_confirmationを追加します。



 def changeset(struct, params \\ %{}) do struct |> cast(params, [:username, :email, :password, :password_confirmation]) |> validate_required([:username, :email, :password, :password_confirmation]) end
      
      





test / models / user_test.exsファイルからテストを実行しようとすると、 「有効な属性を持つ変更セット」テストは失敗します。 これは、必要なパラメーターに:passwordおよび:password_confirmationを追加したが、 @valid_attrsを更新しなかったためです 。 この行を変更しましょう:



 @valid_attrs %{email: "[email protected]", password: "test1234", password_confirmation: "test1234", username: "testuser"}
      
      





モデルテストは再びパスするはずです! ここで、コントローラーのテストを修正する必要があります。 test / controllers / user_controller_test.exsファイルにいくつかの変更を加えます 。 まず、有効な属性を選択して、別の変数にオブジェクトを作成します。



 @valid_create_attrs %{email: "[email protected]", password: "test1234", password_confirmation: "test1234", username: "testuser"} @valid_attrs %{email: "[email protected]", username: "testuser"}
      
      





次に、ユーザー作成テストを変更します。



 test "creates resource and redirects when data is valid", %{conn: conn} do conn = post conn, user_path(conn, :create), user: @valid_create_attrs assert redirected_to(conn) == user_path(conn, :index) assert Repo.get_by(User, @valid_attrs) end
      
      





ユーザー更新テスト:



 test "updates chosen resource and redirects when data is valid", %{conn: conn} do user = Repo.insert! %User{} conn = put conn, user_path(conn, :update, user), user: @valid_create_attrs assert redirected_to(conn) == user_path(conn, :show, user) assert Repo.get_by(User, @valid_attrs) end
      
      





テスト再び緑色になったら、パスワードを変更セットのダイジェストに変換する関数を追加する必要があります。



 def changeset(struct, params \\ %{}) do struct |> cast(params, [:username, :email, :password, :password_confirmation]) |> validate_required([:username, :email, :password, :password_confirmation]) |> hash_password end defp hash_password(changeset) do changeset |> put_change(:password_digest, "ABCDE") end
      
      





今のところ、ハッシュ関数の動作を安定させるだけです。 まず、リビジョンが正しく変更されていることを確認しましょう。 http:// localhost:4000 / usersページのブラウザに戻り、 New userリンクをクリックして、任意のデータで新しいユーザーを作成します。 これで、ユーザーリストに新しい行が追加され、パスワードダイジェストがABCDEに等しくなります。



このファイルでテストを再度実行します。 それらは合格しますが、 hash_password関数をテストするのに十分なテストがありません。 追加しましょう:



 test "password_digest value gets set to a hash" do changeset = User.changeset(%User{}, @valid_attrs) assert get_change(changeset, :password_digest) == "ABCDE" end
      
      





これはアプリケーションにとって大きな前進ですが、セキュリティにとってはそれほど大きなものではありません! Comeoninライブラリから提供されているBCryptを使用して、パスワードハッシュを現在に迅速に修正する必要があります。



これを行うには、 mix.exsファイルを開き、 アプリケーションリストにcomeoninを追加します



 def application do [mod: {Pxblog, []}, applications: [:phoenix, :phoenix_pubsub, :phoenix_html, :cowboy, :logger, :gettext, :phoenix_ecto, :postgrex, :comeonin]] end
      
      





依存関係も変更する必要があります。 {:comeonin、“〜> 2.3”}に注意してください:



 defp deps do [{:phoenix, "~> 1.2.0"}, {:phoenix_pubsub, "~> 1.0"}, {:phoenix_ecto, "~> 3.0"}, {:postgrex, ">= 0.0.0"}, {:phoenix_html, "~> 2.6"}, {:phoenix_live_reload, "~> 1.0", only: :dev}, {:gettext, "~> 0.11"}, {:cowboy, "~> 1.0"}, {:comeonin, "~> 2.3"}] end
      
      





実行中のサーバーの電源を切り、 `mix deps.get`コマンドを実行します。 すべてがうまくいけばそして、そうすべきです! )、コマンド`iex -S mix phoenix.server`で 、サーバーを再起動できます。



古いhash_passwordメソッド 悪く あり ませんが、一般的にはパスワードを実際にハッシュする必要があります。 Comeoninライブラリーを追加したため、 hashpwsaltメソッドを備えた美しいBcryptモジュールが提供され、 ユーザーモデルにインポートされます。 web / models / user.exファイルで、 Pxblog.Web使用した直後に以下の行を追加します :model



 import Comeonin.Bcrypt, only: [hashpwsalt: 1]
      
      





私たちは今何をしましたか? Comeonin名前空間からBcryptモジュールを取り出し 、arity 1でhashpwsaltメソッドをインポートしました 。次のコードを使用して、 hash_password関数を機能させます。



 defp hash_password(changeset) do if password = get_change(changeset, :password) do changeset |> put_change(:password_digest, hashpwsalt(password)) else changeset end end
      
      





ユーザーを再度作成することをお勧めします! 今回は、登録後、 password_digestフィールドに暗号化されたダイジェストが表示されるはずです!



それでは、 hash_password関数を少し改良してみましょう。 まず、パスワードの暗号化によってテストが妨げられないように、テスト環境の設定を変更する必要があります。 これを行うには、 config / test.exsファイルを開き、次の行を一番下に追加します。



 config :comeonin, bcrypt_log_rounds: 4
      
      





これにより、テストではセキュリティよりも速度が重要になるため、テスト実行中にパスワードをあまり暗号化しないようにComeoninライブラリに指示します! そして本番環境config / prod.exs file )では、逆に保護を強化する必要があります:



 config :comeonin, bcrypt_log_rounds: 14
      
      





Comeoninを呼び出すテストを書きましょう。 暗号化が機能することを確認したいだけなので、詳細を少なくします。 ファイルtest / models / user_test.exsで



 test "password_digest value gets set to a hash" do changeset = User.changeset(%User{}, @valid_attrs) assert Comeonin.Bcrypt.checkpw(@valid_attrs.password, Ecto.Changeset.get_change(changeset, :password_digest)) end
      
      





テストの範囲を改善するために、 `if the password = get_change()が正しくない場合を見てみましょう:



 test "password_digest value does not get set if password is nil" do changeset = User.changeset(%User{}, %{email: "[email protected]", password: nil, password_confirmation: nil, username: "test"}) refute Ecto.Changeset.get_change(changeset, :password_digest) end
      
      





この場合、 password_digestフィールドは空のままにしておく必要があります 。 私たちはコードをテストでカバーするのに良い仕事をしています!



ステップ4.始めましょう!



新しいSessionControllerと付随するSessionViewを追加します。 単純なものから始めましょう。時間の経過とともに、より適切な実装になります。



web / controllers / session_controller.exファイルを作成します。



 defmodule Pxblog.SessionController do use Pxblog.Web, :controller def new(conn, _params) do render conn, "new.html" end end
      
      





また、 web / views / session_view.ex



 defmodule Pxblog.SessionView do use Pxblog.Web, :view end
      
      





そして最後に、 web / templates / session / new.html.eex



 <h2>Login</h2>
      
      





「/」スコープに次の行を追加します。



 resources "/sessions", SessionController, only: [:new]
      
      





したがって、新しいコントローラーをルーターに含めます。 今それを必要とする唯一の方法はnewです。これは明示的に示しています。 繰り返しますが、最も単純な方法で最も安定した基盤を取得する必要があります。



アドレスhttp:// localhost:4000 / sessions / newに移動すると、見出しPhoenixフレームワークの下にLogin見出しが表示されます。



ここに実際のフォームを追加します。 これを行うには、 web / templates / session / form.html.eexファイルを作成します



 <%= form_for @changeset, @action, fn f -> %> <%= if f.errors != [] do %> <div class="alert alert-danger"> <p>Oops, something went wrong! Please check the errors below:</p> <ul> <%= for {attr, message} <- f.errors do %> <li><%= humanize(attr) %> <%= message %></li> <% end %> </ul> </div> <% end %> <div class="form-group"> <label>Username</label> <%= text_input f, :username, class: "form-control" %> </div> <div class="form-group"> <label>Password</label> <%= password_input f, :password, class: "form-control" %> </div> <div class="form-group"> <%= submit "Submit", class: "btn btn-primary" %> </div> <% end %>
      
      





そして、1行だけでweb / templates / session / new.html.eexファイルにフォームを作成しましょう:



 <%= render "form.html", changeset: @changeset, action: session_path(@conn, :create) %>
      
      





自動コード再読み込みのため、 @ changeset変数をまだ定義していないため、ページにエラーが表示されます。 フィールド:name:passwordを持つオブジェクトで作業しているので、それらを使用しましょう!



web / controllers / session_controller.exファイルで、 ユーザーモデルのエイリアスを追加して、さらに簡単にアクセスできるようにする必要があります。 クラスの先頭で、 Pxblog.Web ,:コントローラーを使用する行の下に、以下を追加します。



 alias Pxblog.User
      
      





そして、 新しい関数で、以下に示すようにレンダーコールを変更します。



 render conn, "new.html", changeset: User.changeset(%User{})
      
      





ここでは、接続オブジェクト、レンダリングするテンプレート(eex拡張なし)、およびテンプレート内で使用される追加変数のリストを渡す必要があります。 この場合、 changeset:を指定し、空のユーザー構造を持つユーザーの Ectoリビジョンを渡す必要があります。



ページを更新します。 次のような別のエラーが表示されるはずです。



 No helper clause for Pxblog.Router.Helpers.session_path/2 defined for action :create. The following session_path actions are defined under your router: *:new
      
      





このフォームでは、まだ存在しないパスを参照します。 session_pathヘルパーを使用して@connオブジェクトを渡しますが、まだ作成されていないパスcreateを指定します。



半分が過ぎました。 それでは、セッションを使用して実際のログインの可能性を実装しましょう。 これを行うには、パスを変更します。



web / router.exファイルで、次を含めます SessionControllerの説明にcreate



 resources "/sessions", SessionController, only: [:new, :create]
      
      





web / controllers / session_controller.exファイルで、 ComeoninライブラリのBcryptモジュールからcheckpw関数をインポートします。



 import Comeonin.Bcrypt, only: [checkpw: 2]
      
      





この行は、「Comonin.Bcryptモジュールから、arity 2のcheckpw関数のみをインポートします」と表示されます。



次に、ユーザーデータを操作するscrub_paramsプラグイン接続します。関数の前に追加します。



 plug :scrub_params, "user" when action in [:create]
      
      





scrub_paramsは、ユーザー入力をクリアする特別な関数です。たとえば、属性が空の文字列として渡される場合、scrub_paramsはそれをnil変換して、データベースに空の文字列を持つレコードが作成されないようにします。



次に、作成アクションを処理する関数を追加しますSessionControllerモジュールの下部に配置します。ここにはたくさんのコードがあるので、それを分解してみましょう。web / controllers / session_controller.ex



ファイルで



 def create(conn, %{"user" => user_params}) do Repo.get_by(User, username: user_params["username"]) |> sign_in(user_params["password"], conn) end
      
      





Repo.get_byコードの最初の部分(ユーザー、ユーザー名:user_params [“ユーザー名”])は、ユーザーnilに一致するか返される場合Ecto Repoリポジトリから適切なユーザーを取得します以下は、この動作をテストするための小さな出力です。







 iex(3)> Repo.get_by(User, username: "flibbity") [debug] SELECT u0."id", u0."username", u0."email", u0."password_digest", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."username" = $1) ["flibbity"] OK query=0.7ms nil iex(4)> Repo.get_by(User, username: "test") [debug] SELECT u0."id", u0."username", u0."email", u0."password_digest", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."username" = $1) ["test"] OK query=0.8ms %Pxblog.User{__meta__: %Ecto.Schema.Metadata{source: "users", state: :loaded}, email: "test", id: 15, inserted_at: %Ecto.DateTime{day: 24, hour: 19, min: 6, month: 6, sec: 14, usec: 0, year: 2015}, password: nil, password_confirmation: nil, password_digest: "$2b$12$RRkTZiUoPVuIHMCJd7yZUOnAptSFyM9Hw3Aa88ik4erEsXTZQmwu2", updated_at: %Ecto.DateTime{day: 24, hour: 19, min: 6, month: 6, sec: 14, usec: 0, year: 2015}, username: "test"}
      
      





次に、ユーザーを取得し、そのチェーンをsign_in関数に渡しますまだ書いてないので、やりましょう!



 defp sign_in(user, password, conn) when is_nil(user) do conn |> put_flash(:error, "Invalid username/password combination!") |> redirect(to: page_path(conn, :index)) end defp sign_in(user, password, conn) do if checkpw(password, user.password_digest) do conn |> put_session(:current_user, %{id: user.id, username: user.username}) |> put_flash(:info, "Sign in successful!") |> redirect(to: page_path(conn, :index)) else conn |> put_session(:current_user, nil) |> put_flash(:error, "Invalid username/password combination!") |> redirect(to: page_path(conn, :index)) end end
      
      





注意する必要がある主なことは、これらの関数が定義される順序です。それらの最初のものはガード条件を持っているため、このメソッドはこの条件が真である場合にのみ実行されます。そのため、ユーザーが見つからなかった場合は、対応するメッセージとともにroot_pathにリダイレクトします。



2番目の関数は、他のすべてのシナリオを処理します(セキュリティ条件がfalseの場合)。checkpw関数を使用してパスワードを確認します。正しい場合は、ユーザーをセッション変数current_userに書き込み、ログイン成功に関するメッセージでリダイレクトします。それ以外の場合は、現在のユーザーセッションをクリアし、エラーメッセージを設定して、ルートにリダイレクトします。



http:// localhost:4000 / sessions / newページに移動して、動作を確認できます。正しいデータがあれば、中に入り、間違ったデータではエラーになります。



このコントローラーのテストも作成する必要があります。ファイルtest / controllers / session_controller_test.exs作成し、次のコードを入力します。



 defmodule Pxblog.SessionControllerTest do use Pxblog.ConnCase alias Pxblog.User setup do User.changeset(%User{}, %{username: "test", password: "test", password_confirmation: "test", email: "[email protected]"}) |> Repo.insert {:ok, conn: build_conn()} end test "shows the login form", %{conn: conn} do conn = get conn, session_path(conn, :new) assert html_response(conn, 200) =~ "Login" end test "creates a new user session for a valid user", %{conn: conn} do conn = post conn, session_path(conn, :create), user: %{username: "test", password: "test"} assert get_session(conn, :current_user) assert get_flash(conn, :info) == "Sign in successful!" assert redirected_to(conn) == page_path(conn, :index) end test "does not create a session with a bad login", %{conn: conn} do conn = post conn, session_path(conn, :create), user: %{username: "test", password: "wrong"} refute get_session(conn, :current_user) assert get_flash(conn, :error) == "Invalid username/password combination!" assert redirected_to(conn) == page_path(conn, :index) end test "does not create a session if user does not exist", %{conn: conn} do conn = post conn, session_path(conn, :create), user: %{username: "foo", password: "wrong"} assert get_flash(conn, :error) == "Invalid username/password combination!" assert redirected_to(conn) == page_path(conn, :index) end end
      
      





標準のセットアップブロックとGETリクエストのかなり基本的なチェックから始めます作成テストはより興味深いように見えます:



 test "creates a new user session for a valid user", %{conn: conn} do conn = post conn, session_path(conn, :create), user: %{username: "test", password: "test"} assert get_session(conn, :current_user) assert get_flash(conn, :info) == "Sign in successful!" assert redirected_to(conn) == page_path(conn, :index) end
      
      





最初の行は、セッション作成パスにPOSTリクエストを送信しています。次に、current_userセッション変数が設定されているかどうかを確認するチェックがあり、ログインメッセージが表示され、最後にリダイレクトが行われました。残りのテストでは、sign_in関数が取得できる他の方法もチェックします繰り返しますが、すべてが非常に簡単です!



ステップ5. current_userの改善



メインテンプレートを変更して、ユーザーがログインしているかどうかに応じて、ユーザー名またはログインリンクのいずれかを表示するようにします。



これを行うには、web / views / layout_view.exファイルにヘルパーを追加します。これにより、現在のユーザーに関する情報の取得が容易になります。



 def current_user(conn) do Plug.Conn.get_session(conn, :current_user) end
      
      





web / templates / layout / app.html.eexファイル開いてGet Startedリンクの代わりに以下追加します:



 <li> <%= if user = current_user(@conn) do %> Logged in as <strong><%= user.username %></strong> <br> <%= link "Log out", to: session_path(@conn, :delete, user.id), method: :delete %> <% else %> <%= link "Log in", to: session_path(@conn, :new) %> <% end %> </li>
      
      





もう一度手順を見ていきましょう。最初に行う必要があることの1つは、現在のユーザーが既にログインしていると仮定して、現在のユーザーが誰であるかを調べることです。最初に額を決定し、次にリファクタリングを行います。テンプレートからセッションからユーザーを直接インストールします。get_session関数Connオブジェクトの一部です



ユーザーがログインしている場合、ユーザーに終了リンクを表示する必要があります。セッションを通常のリソースと見なすため、終了するには、このアクションへのリンクを使用してセッションを削除するだけです。



また、現在のユーザーの名前を表示する必要があります。ユーザー名を取得できるように、ユーザー構造をcurrent_userセッション変数保存しますuser.usernameを使用します



ユーザーが見つからない場合は、入り口へのリンクを表示するだけです。ここでは、再びそのことを、リソースとしてセッションを検討し、新たな補助金の正しい方法は、新しいセッションを作成します。



おそらく、ページを更新した後、不足している機能に関する別のエラーメッセージが表示されることに気づいたでしょう。フェニックスが幸せになるように、必要なパスを接続しましょう!



ファイル内のWeb / router.exだけでなく、セッションへの追加ルートを削除します



 resources "/sessions", SessionController, only: [:new, :create, :delete]
      
      





まだコントローラーを変更する必要があります。web / controllers / session_controller.exファイルで、次を追加します。



 def delete(conn, _params) do conn |> delete_session(:current_user) |> put_flash(:info, "Signed out successfully!") |> redirect(to: page_path(conn, :index)) end
      
      





current_userキーを削除したばかりなので、どのパラメーターが来ても関係ないので、最初にアンダースコアを付けて未使用としてマークします。また、成功した終了メッセージを設定し、投稿のリストにリダイレクトしました。



これで、失敗したエントリを入力、終了、確認できます。すべてが良い方向に進んでいます!しかし、最初にいくつかのテストを書く必要があります。LayoutViewのテストから始めます。最初に行うことは、コードを短縮するためにLayoutViewおよびUserモジュールのエイリアスを作成することです。次に、構成ブロックで、ユーザーを作成し、データベースに追加します。そして、標準タプル{:ok、conn:build_conn()}を返します。



 defmodule Pxblog.LayoutViewTest do use Pxblog.ConnCase, async: true alias Pxblog.LayoutView alias Pxblog.User setup do User.changeset(%User{}, %{username: "test", password: "test", password_confirmation: "test", email: "[email protected]"}) |> Repo.insert {:ok, conn: build_conn()} end test "current user returns the user in the session", %{conn: conn} do conn = post conn, session_path(conn, :create), user: %{username: "test", password: "test"} assert LayoutView.current_user(conn) end test "current user returns nothing if there is no user in the session", %{conn: conn} do user = Repo.get_by(User, %{username: "test"}) conn = delete conn, session_path(conn, :delete, user) refute LayoutView.current_user(conn) end end
      
      







次に、テスト自体を検討します。これらの最初の部分では、セッションを作成し、LayoutView.current_user関数が特定のデータを返すように記述します。2番目では、逆の状況を考慮します。セッションを明示的に削除し、current_user関数がユーザーを返すことに反論します。



また、削除アクションSessionController追加したため、このテストも作成する必要があります。



 test "deletes the user session", %{conn: conn} do user = Repo.get_by(User, %{username: "test"}) conn = delete conn, session_path(conn, :delete, user) refute get_session(conn, :current_user) assert get_flash(conn, :info) == "Signed out successfully!" assert redirected_to(conn) == page_path(conn, :index) end
      
      





ここでは、セッションのcurrent_userが空であることを確認し、返されたフラッシュメッセージとリダイレクトを確認します。



これで、最初の部分は終わりました。



翻訳者からの重要な結論



私はこの記事とシリーズ全体の翻訳の両方を翻訳する素晴らしい仕事をしました。私が今し続けていること。したがって、記事自体またはRuNetでElixirを普及させる努力が気に入った場合は、プラス、コメント、再投稿で記事をサポートしてください。これは私個人にとっても、エリクサーコミュニティ全体にとっても非常に重要です。



シリーズの他の記事



  1. エントリー
  2. ログイン
  3. 役割を追加
  4. コントローラーで役割を処理します
  5. ExMachinaを接続します
  6. マークダウンのサポート
  7. コメントを追加
  8. コメントで終了
  9. チャンネル
  10. チャネルテスト
  11. おわりに




すべての不正確さ、エラー、不十分な翻訳については、個人的なメッセージで書いてください、私はすぐにそれを修正します。事前に感謝します。



All Articles