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を開くず、 Username 、 Email 、 PasswordDigestの 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 

passwordずpassword_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. おわりに


すべおの䞍正確さ、゚ラヌ、䞍十分な翻蚳に぀いおは、個人的なメッセヌゞで曞いおください、私はすぐにそれを修正したす。事前に感謝したす。

Source: https://habr.com/ru/post/J311088/


All Articles