LiveViewチャットチュートリアル
完全なコード、テスト、AUTHを備えた無料のオープンソースの実世界の例が本当に欲しかった。
私たちはこれを書いたので、私たちのチーム/コミュニティで人々をPhoenix LiveViewを学習していることを指摘することができました。
このLiveView例/チュートリアルでは、20分でゼロから完全に機能するアプリになります。
この例/チュートリアルで説明できるものの目次は次のとおりです。
LiveViewチャットチュートリアルPhoenixアプリを作成しますliveディレクトリ、 LiveViewコントローラー、テンプレートを作成しますrouter.exを更新しますmount/3機能を更新しますmount/3handle_event/3を更新しますhandle_info/2を作成しますAUTH_API_KEYを作成しますauth_plugをインストールしますrouter.exでオプションの認証パイプラインを作成しますAuthControllerを作成しますon_mount/4関数を作成します Setup 、 Testing 、 Authentication 、存在、 Presenceなど、自己完結型のチュートリアルを望んでいるPhoenix LiveView学習する人は誰でも
必須ではありませんが、 LiveViewカウンターチュートリアルに従うことをお勧めします。少なくとも、前提条件のリストをチェックアウトして、この冒険を始める前にコンピューターにインストールする必要があるものを知っています!
Elixir 、 Phoenix 、 Postgresがインストールされている場合は、行ってもいいです!
Phoenixアプリを作成します新しいliveview_chat Phoenixアプリケーションを作成することから始めます。
mix phx.new liveview_chat --no-mailer --no-dashboard emailやdashboard機能は必要ないので、アプリからそれらを除外しています。実行することで、新しいPhoenixアプリの作成について詳しく知ることができますmix help phx.new
mix deps.getを実行して、依存関係を取得します。次に、コマンドを実行してliveview_chat_dev postgresデータベースを作成します。
mix ecto.setup次のような出力が表示されるはずです。
The database for LiveviewChat.Repo has been created
14:20:19.71 [info] Migrations already upそのコマンドが成功したら、コマンドを実行してアプリケーションを開始できるようになりました。
mix phx.server以下と同様の端子出力が表示されます。
[info] Running LiveviewChatWeb.Endpoint with cowboy 2.9.0 at 127.0.0.1:4000 (http)
[debug] Downloading esbuild from https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.29.tgz
[info] Access LiveviewChatWeb.Endpoint at http://localhost:4000
[watch] build finished, watching for changes... URLを開くと、 http://localhost:4000 webブラウザーで、次のようなものが表示されます。

liveディレクトリ、 LiveViewコントローラー、テンプレートを作成しますlib/liveview_chat_web/liveフォルダーとlib/liveview_chat_web/live/message_live.exでコントローラーを作成
defmodule LiveviewChatWeb.MessageLive do
use LiveviewChatWeb , :live_view
def mount ( _params , _session , socket ) do
{ :ok , socket }
end
def render ( assigns ) do
LiveviewChatWeb.MessageView . render ( "messages.html" , assigns )
end
end注:ファイル名もコードにも「コントローラー」という単語はどこにもありません。うまくいけば、それは混乱していません。アプリで何が起こるかを制御するという意味で、これは「コントローラー」です。
LiveViewコントローラーでは、関数mount/3とrender/1定義する必要があります。
コントローラーをシンプルに保つためにmount/3 {:ok, socket}タプルを変更せずに戻すだけです。 render/1 template LiveviewChatWeb.MessageView.render/2 ( Phoenixに含まれる) messages.html.heex呼び出します。
lib/liveview_chat_web/views/message_view.exファイルを作成します:
defmodule LiveviewChatWeb.MessageView do
use LiveviewChatWeb , :view
endこれは、通常のPhoenix viewに似ています。ここで特別/面白いことは何もありません。
次に、 lib/liveview_chat_web/templates/messageディレクトリを作成し、作成しますlib/liveview_chat_web/templates/message/messages.html.heexファイルとHTMLの次の行を追加します。
< h1 > LiveView Message Page </ h1 >最後に、ルートレイアウトをよりシンプルにするには、 lib/liveview_chat_web/templates/layout/root.html.heexファイルを開き、 <body>の内容を更新します。
< body >
< header >
< section class =" container " >
< h1 > LiveView Chat Example </ h1 >
</ section >
</ header >
< %= @inner_content % >
</ body > router.exを更新します必要なファイルを作成したので、ルーターlib/liveview_chat_web/router.ex PageController開きます。
get "/" , PageController , :index MessageLiveコントローラーで:
scope "/" , LiveviewChatWeb do
pipe_through :browser
live "/" , MessageLive
endページを更新すると、次のことが表示されます。

この時点で、自動化されたテストスイートが渡されなくなることを意味するいくつかの変更を加えました...次のコマンドでコマンドラインでテストを実行します。
mix test次のような出力が表示されます。
Generated liveview_chat app
..
1) test GET / (LiveviewChatWeb.PageControllerTest)
test/liveview_chat_web/controllers/page_controller_test.exs:4
Assertion with = ~ failed
code: assert html_response(conn, 200) = ~ " Welcome to Phoenix! "
left: " <!DOCTYPE html><html lang= " en " > <head> <meta charset= " utf-8 " > <meta http-equiv= " X-UA-Compatible " content= " IE=edge " >
<title data-suffix= " · Phoenix Framework " >LiveviewChat · Phoenix Framework</title> <link phx-track-static rel= " stylesheet " href= " /assets/app.css " > <script defer phx-track-static type= " text/javascript " src= " /assets/app.js " ></script> </head>
<body> <header> <section class= " container " >
<h1>LiveView Chat Example</h1></section> </header>
<h1>LiveView Message Page</h1></main></div> </body></html> "
right: " Welcome to Phoenix! "
stacktrace:
test/liveview_chat_web/controllers/page_controller_test.exs:6: (test)
Finished in 0.03 seconds (0.02s async, 0.01s sync)
3 tests, 1 failureこれは、 page_controller_test.exsホームページに"Welcome to Phoenix!"を含むことを期待しているためです。文章。
テストを更新しましょう! test/liveview_chat_web/liveフォルダーとmessage_live_test.exsファイルを作成します: test/liveview_chat_web/live/message_live_test.exs
次のテストコードを追加します。
defmodule LiveviewChatWeb.MessageLiveTest do
use LiveviewChatWeb.ConnCase
import Phoenix.LiveViewTest
test "disconnected and connected mount" , % { conn: conn } do
conn = get ( conn , "/" )
assert html_response ( conn , 200 ) =~ "LiveView Message Page"
{ :ok , _view , _html } = live ( conn )
end
end /エンドポイントにアクセス可能であり、ページに"LiveView Message Page"があることをテストしています。
テストとLiveViewの詳細については、LiveViewTestモジュールも参照してください。
最後に、 PageControllerにリンクされたすべてのデフォルトの生成コードを削除できます。
rm test/liveview_chat_web/controllers/page_controller_test.exsrm lib/liveview_chat_web/controllers/page_controller.exrm test/liveview_chat_web/views/page_view_test.exsrm lib/liveview_chat_web/views/page_view.exrm -r lib/liveview_chat_web/templates/pageこれで、 mix testコマンドでテストを再度実行できます。次のことを確認する必要があります(テストに合格します):
Generated liveview_chat app
...
Finished in 0.1 seconds (0.06s async, 0.1s sync)
3 tests, 0 failures
Randomized with seed 841084LiveView構造が定義されているため、メッセージの作成に焦点を当てることができます。データベースはメッセージと送信者の名前を保存します。新しいスキーマと移行を作成しましょう。
mix phx.gen.schema Message messages name:string message:string注:
mix ecto.migrateを実行することを忘れないでください。データベースに新しいmessagesテーブルを作成します。
これで、 Messageスキーマを更新して、新しいメッセージを作成して既存のメッセージをリストするための関数を追加できます。また、Changesetを更新して、メッセージテキストに要件と検証を追加します。 lib/liveview_chat/message.exファイルを開き、次のようにコードを更新します。
defmodule LiveviewChat.Message do
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query
alias LiveviewChat.Repo
alias __MODULE__
schema "messages" do
field :message , :string
field :name , :string
timestamps ( )
end
@ doc false
def changeset ( message , attrs ) do
message
|> cast ( attrs , [ :name , :message ] )
|> validate_required ( [ :name , :message ] )
|> validate_length ( :message , min: 2 )
end
def create_message ( attrs ) do
% Message { }
|> changeset ( attrs )
|> Repo . insert ( )
end
def list_messages do
Message
|> limit ( 20 )
|> order_by ( desc: :inserted_at )
|> Repo . all ( )
end
endメッセージ入力にvalidate_length関数を追加して、メッセージが少なくとも2文字の文字があることを確認しました。これは、 LiveViewページのフォームでchangeset Validationがどのように機能するかを示す例にすぎません。
次に、 create_message/1およびlist_messages/0関数を作成しました。 Phoenix-chat-exampleと同様に、最新の20に返されるメッセージの数limit 。
mount/3機能を更新しますlib/liveview_chat_web/live/message_live.exファイルを開き、3行目で次の行を追加します。
alias LiveviewChat.Message次に、 lib/liveview_chat_web/live/message_live.exファイルのmount/3関数を更新して、 list_messages関数を使用します。
def mount ( _params , _session , socket ) do
messages = Message . list_messages ( ) |> Enum . reverse ( )
changeset = Message . changeset ( % Message { } , % { } )
{ :ok , assign ( socket , changeset: changeset , messages: messages ) }
end mount/3 messagesのリストを取得し、メッセージフォームに使用されるchangesetを作成します。次に、LiveViewページに表示されるSocketにchangesetとmessagesを割り当てます。
messages.html.heexテンプレートを次のコードに更新します。
< ul id =' msg-list ' phx-update =" append " >
< %= for message < - @messages do % >
< li id = { "msg-#{message.id}"} >
< b > < %= message.name % > : </ b >
< %= message.message % >
</ li >
< % end % >
</ ul >
< .form let={f} for={@changeset} id="form" phx-submit="new_message" phx-hook="Form" >
< %= text_input f, :name, id: "name", placeholder: "Your name", autofocus: "true" % >
< %= error_tag f, :name % >
< %= text_input f, :message, id: "msg", placeholder: "Your message" % >
< %= error_tag f, :message % >
< %= submit "Send"% >
</ .form >最初に新しいメッセージを表示し、次に人々が新しいメッセージcreateフォームを提供します。
ページを更新する場合は、次のことを確認する必要があります。

<.form></.form>構文は、フォーム関数コンポーネントの使用方法です。
関数コンポーネントは、
assignsMapを引数として受信し、~Hsigilで構築されたレンダリングされたstructを返す関数です。
最後にtest/liveview_chat_web/live/message_live_test.exsファイルでassertを更新することによって、テストがまだ次のように渡されていることを確認しましょう。
assert html_response ( conn , 200 ) =~ "LiveView Chat" LiveView Message Page H1タイトルを削除したため、代わりにルートレイアウトのタイトルをテストし、ページが正しく表示されていることを確認できます。
現時点では、 Phoenix App mix phx.serverを実行し、ブラウザでフォームを送信すると、何も起こりません。サーバーログを見ると、次のことがわかります。
** (UndefinedFunctionError) function LiveviewChatWeb.MessageLive.handle_event/3
is undefined or private
(liveview_chat 0.1.0) LiveviewChatWeb.MessageLive.handle_event("new_message",
%{"_csrf_token" => "fyVPIls_XRBuGwlkMhxsFAciRRkpAVUOLW5k4UoR7JF1uZ5z2Dundigv",
"message" => %{"message" => "", "name" => ""}}, #Phoenix.LiveView.Socket
提出時に、フォームはphx-submitで定義された新しいイベントを作成しています。
< . form let = { f } for = { @ changeset } id = "form" phx - submit = "new_message" >
ただし、このイベントはまだサーバー上で管理されていませんが、 lib/liveview_chat_web/live/message_live.exにhandle_event/3関数を追加することでこれを修正できます。
def handle_event ( "new_message" , % { "message" => params } , socket ) do
case Message . create_message ( params ) do
{ :error , changeset } ->
{ :noreply , assign ( socket , changeset: changeset ) }
{ :ok , _message } ->
changeset = Message . changeset ( % Message { } , % { "name" => params [ "name" ] } )
{ :noreply , assign ( socket , changeset: changeset ) }
end
end create_message関数は、フォームの値で呼び出されます。データベースに情報を保存しようとしているときにerrorが発生した場合、たとえば、名前またはmessageが空の場合、またはmessageが短すぎる場合、 changeset changesetエラーを返すことができます。これにより、フォームがerror情報を表示できます。

メッセージがエラーなしで保存されている場合、フォームから名前を再び入力しないようにフォームから名前を含む新しい変更セットを作成し、新しい変更セットをソケットに割り当てます。

フォームが表示されると、次のテストをtest/liveview_chat_web/live/message_live_test.exsに追加できます:
import Plug.HTML , only: [ html_escape: 1 ]
test "name can't be blank" , % { conn: conn } do
{ :ok , view , _html } = live ( conn , "/" )
assert view
|> form ( "#form" , message: % { name: "" , message: "hello" } )
|> render_submit ( ) =~ html_escape ( "can't be blank" )
end
test "message" , % { conn: conn } do
{ :ok , view , _html } = live ( conn , "/" )
assert view
|> form ( "#form" , message: % { name: "Simon" , message: "" } )
|> render_submit ( ) =~ html_escape ( "can't be blank" )
end
test "minimum message length" , % { conn: conn } do
{ :ok , view , _html } = live ( conn , "/" )
assert view
|> form ( "#form" , message: % { name: "Simon" , message: "h" } )
|> render_submit ( ) =~ "should be at least 2 character(s)"
end form/3関数を使用してフォームを選択し、名前とメッセージの異なる値で送信イベントをトリガーしています。エラーが適切に表示されていることをテストしています。
新しく作成されたメッセージを表示するためにページをリロードする代わりに、PubSub( Pub Lish Sub Scribe)を使用して、すべての接続されたクライアントに新しいメッセージが作成されたことを通知し、UIを更新して新しいメッセージを表示することができます。
lib/liveview_chat/message.exファイルを開き、上部近くに次の行を追加します。
alias Phoenix.PubSub次に、次の3つの関数を追加します。
def subscribe ( ) do
PubSub . subscribe ( LiveviewChat.PubSub , "liveview_chat" )
end
def notify ( { :ok , message } , event ) do
PubSub . broadcast ( LiveviewChat.PubSub , "liveview_chat" , { event , message } )
end
def notify ( { :error , reason } , _event ) , do: { :error , reason } subscribe/0クライアントがLiveViewページを適切に表示し、新しいメッセージを聞くときに呼び出されます。これは、phoenix.pubsub.subscribeの単なるラッパー関数です。
notify/2接続されたクライアントにメッセージをブロードキャストするために新しいメッセージが作成されるたびに呼び出されます。 Repo.insert {:ok, message}または{:error, reason}を返すことができるため、両方のケースをnotify/2ハンドルを定義する必要があります。
message.exでcreate_message/1関数を更新して、新しく作成したnotify/2関数を呼び出します。
def create_message ( attrs ) do
% Message { }
|> changeset ( attrs )
|> Repo . insert ( )
|> notify ( :message_created )
endmount/3 LiveViewページがレンダリングされたときに、クライアントを接続できるようになりました。 lib/liveview_chat_web/live/message_live.exファイルの上部で、次の行を追加します。
alias LiveviewChat.PubSub次に、次のようにmount/3関数を更新します。
def mount ( _params , _session , socket ) do
if connected? ( socket ) , do: Message . subscribe ( )
messages = Message . list_messages ( ) |> Enum . reverse ( )
changeset = Message . changeset ( % Message { } , % { } )
{ :ok , assign ( socket , messages: messages , changeset: changeset ) }
end mount/3 、ソケットが接続されていることをチェックし、新しいMessage.subscribe/0関数を呼び出します。
handle_event/3を更新しますcreate_message/1の返品値が変更されたため、 handle_event/3を以下に更新する必要があります。
def handle_event ( "new_message" , % { "message" => params } , socket ) do
case Message . create_message ( params ) do
{ :error , changeset } ->
{ :noreply , assign ( socket , changeset: changeset ) }
:ok -> # broadcast returns :ok (just the atom!) if there are no errors
changeset = Message . changeset ( % Message { } , % { "name" => params [ "name" ] } )
{ :noreply , assign ( socket , changeset: changeset ) }
end
endhandle_info/2を作成します最後のステップはlib/liveview_chat_web/live/message_live.exでhandle_info/2関数を定義することにより:message_createdイベントを処理することです。
def handle_info ( { :message_created , message } , socket ) do
messages = socket . assigns . messages ++ [ message ]
{ :noreply , assign ( socket , messages: messages ) }
endイベントが受信されると、既存のメッセージのリストに新しいメッセージが追加されます。その後、新しいリストがソケットに割り当てられ、UIを更新して新しいメッセージを表示します。
次のテストをtest/liveview_chat_web/live/message_live_test.exsに追加して、メッセージがページに正しく表示されるようにします。
test "message form submitted correctly" , % { conn: conn } do
{ :ok , view , _html } = live ( conn , "/" )
assert view
|> form ( "#form" , message: % { name: "Simon" , message: "hi" } )
|> render_submit ( )
assert render ( view ) =~ "<b>Simon:</b>"
assert render ( view ) =~ "hi"
end
test "handle_info/2" , % { conn: conn } do
{ :ok , view , _html } = live ( conn , "/" )
assert render ( view )
# send :created_message event when the message is created
Message . create_message ( % { "name" => "Simon" , "message" => "hello" } )
# test that the name and the message is displayed
assert render ( view ) =~ "<b>Simon:</b>"
assert render ( view ) =~ "hello"
endこれで、LiveViewを使用して機能的なチャットアプリケーションが必要です。 Phoenixアプリを実行します:
mix phx.server App localhost:4000を2つ以上のブラウザにアクセスして、メッセージを送信してください!

気付く問題の1つは、入力フィールドのEnterキーを使用してメッセージを送信した後、メッセージ入力が必ずしも空の値にリセットされないということです。これにより、新しいメッセージを書いて送信する前に、以前のメッセージを手動で削除する必要があります。
その理由は次のとおりです。
JavaScriptクライアントは、常に現在の入力値の真実の源です。フォーカスを備えた任意の入力について、
LiveView、サーバーのレンダリングされた更新から逸脱していても、入力の現在の値を上書きすることはありません。参照:https://hexdocs.pm/phoenix_live_view/form-bindings.html#javascript-client-pecifics
私たちの解決策は、 phx-hookを使用して、 LiveViewライフサイクルコールバックの1つ(マウントされた、使用前、更新、破壊、切断、再接続)の後にクライアントにJavaScriptを実行することです。
メッセージフォームがupdatedときに監視するためにフックを追加しましょう。 message.html.heexファイルでphx-hook属性を<.form>要素に追加します。
< .form let={f} for={@changeset} id="form" phx-submit="new_message" phx-hook="Form" >次に、 assets/js/app.jsファイルで、次のJavaScriptロジックを追加します。
// get message input element
let msg = document . getElementById ( 'msg' ) ;
// define "Form" hook, the name must match the one
// defined with phx-hoo="Form"
let Hooks = { }
Hooks . Form = {
// Each time the form is updated run the code in the callback
updated ( ) {
// If no error displayed reset the message value
if ( document . getElementsByClassName ( 'invalid-feedback' ) . length == 0 ) {
msg . value = '' ;
}
}
}
let csrfToken = document . querySelector ( "meta[name='csrf-token']" ) . getAttribute ( "content" )
let liveSocket = new LiveSocket ( "/live" , Socket , { params : { _csrf_token : csrfToken } , hooks : Hooks } ) // Add hooks: Hooksメッセージ値をリセットする主なロジックはupdated()コールバック関数内に含まれています。
if ( document . getElementsByClassName ( 'invalid-feedback' ) . length == 0 ) {
msg . value = '' ;
}値を空の文字列に設定する前に、最初にinvalid-feedback CSSクラスをチェックしてフォームにエラーが表示されないことを確認します。 (フィードバックの詳細:https://hexdocs.pm/phoenix_live_view/form-bindings.html#phx-feedback-for)
最後のステップは、 hooks: HooksでliveSocketにhooks設定することです。新しいメッセージが追加されたら、メッセージ入力をリセットする必要があります!
現時点では、 mount/3関数は、データベースから最新の20メッセージをロードすることにより、メッセージのリストを最初に初期化します。
def mount ( _params , _session , socket ) do
if connected? ( socket ) , do: Message . subscribe ( )
messages = Message . list_messages ( ) |> Enum . reverse ( ) # get the list of messages
changeset = Message . changeset ( % Message { } , % { } )
{ :ok , assign ( socket , messages: messages , changeset: changeset ) } ## assigns messages to socket
end次に、新しいメッセージが作成されるたびに、 handle_info関数はメッセージのリストにメッセージを追加します。
def handle_info ( { :message_created , message } , socket ) do
messages = socket . assigns . messages ++ [ message ] # append new message to the existing list
{ :noreply , assign ( socket , messages: messages ) }
endすべてのメッセージがサーバーのメモリに保持されているため、メッセージのリストがあまりにも長くなると問題を引き起こす可能性があります。
メモリの使用を最小限に抑えるために、メッセージを一時的なassignとして定義できます。
def mount ( _params , _session , socket ) do
if connected? ( socket ) , do: Message . subscribe ( )
messages = Message . list_messages ( ) |> Enum . reverse ( )
changeset = Message . changeset ( % Message { } , % { } )
{ :ok , assign ( socket , messages: messages , changeset: changeset ) ,
temporary_assigns: [ messages: [ ] ] }
endメッセージのリストは一度取得され、空のリストにリセットされます。
これで、 handle_info/2新しいメッセージをソケットに割り当てるだけでいいのです。
def handle_info ( { :message_created , message } , socket ) do
{ :noreply , assign ( socket , messages: [ message ] ) }
end最後に、 heexメッセージテンプレートはphx-updateを使用したメッセージのリストの変更の耳を傾け、既存の表示リストに新しいメッセージを追加します。
< ul id =' msg-list ' phx-update =" append " >
< %= for message < - @messages do % >
< li id = {message.id} >
< b > < %= message.name % > : </ b >
< %= message.message % >
</ li >
< % end % >
</ ul > Phoenixのtemporary-assignsドキュメントページ:https://hexdocs.pm/phoenix_live_view/dom-patching.html#temporary-Assignsも参照してください
現在、 nameフィールドは、メッセージを送信する前に手動で定義するためにその人に任されています。これは基本的なデモアプリでは問題ありませんが、より良いことができることはわかっています。このセクションでは、 auth_plugを使用して認証を追加します。これにより、アプリを使用している人々がGitHubまたはGoogleアカウントで認証され、メッセージフォームにnameを事前に記入することができます。
AUTH_API_KEYを作成します指示に従って、最初にhttps://authdemo.fly.dev/で新しいAPIキーを作成します。

次に、 .envファイルを作成し、作成した新しいAPIキーを追加します。
export AUTH_API_KEY = 88SwQGzaZoJYXs6ihvwMy2dRVtm6KVeg4tSCjRKtwDvMUYUbi/88SwQDatWtSTMd2rKPnaZsAWFNpbf4vv2ZK7JW2nwuSypMeg/authdemo.fly.dev注:セキュリティ上の理由から、これは有効なAPIキーではありません。独自に作成してください、無料で、1分もかかりません。
auth_plugをインストールしますauth_plugパッケージを依存関係に追加します。 mix.exsファイルでdeps関数を更新して追加します。
{ :auth_plug , "~> 1.4.10" }この依存関係は、あなたのために新しいセッションを作成し、Dwyl authアプリケーションと通信します。
忘れないでください:
source .envmix deps.get新しい依存関係がコンパイルされる前に、 AUTH_API_KEYにアクセスできることを確認してください。
mix deps.compile --forceで依存関係を再コンパイルできます。
これで、認証機能の追加を開始できます。
router.exでオプションの認証パイプラインを作成します[unauthenticated] "guest"ユーザーがチャットにアクセスできるようにするには、 AuthPlugOptionalプラグを使用します。 Optional Authで詳細をご覧ください。
router.exファイルで、新しいPlugパイプラインを作成します。
# define the new pipeline using auth_plug
pipeline :authOptional , do: plug ( AuthPlugOptional )次に、 scope "/", LiveviewChatWeb do 。
scope "/" , LiveviewChatWeb do
pipe_through [ :browser , :authOptional ]
live "/" , MessageLive
get "/login" , AuthController , :login
get "/logout" , AuthController , :logout
end現在、ルーター内のすべてのルートに対して認証をオプションにすることを許可しています。簡単、ねえ?
AuthControllerを作成しますlogin/2とlogout/2機能の両方を使用してAuthControllerを作成します。
新しいファイルを作成します: lib/liveview_chat_web/controllers/auth_controller.exを作成し、次のコードを追加します。
defmodule LiveviewChatWeb.AuthController do
use LiveviewChatWeb , :controller
def login ( conn , _params ) do
redirect ( conn , external: AuthPlug . get_auth_url ( conn , "/" ) )
end
def logout ( conn , _params ) do
conn
|> AuthPlug . logout ( )
|> put_status ( 302 )
|> redirect ( to: "/" )
end
end login/2関数は、Dwyl Authアプリにリダイレクトします。 AuthPlug.get_auth_url/2関数の使用方法の詳細をご覧ください。認証されると、ユーザーは/エンドポイントにリダイレクトされ、 jwtセッションがクライアントに作成されます。
logout/2関数は、(JWT)セッションを削除し、ホームページにリダイレクトするAuthPlug.logout/1を呼び出します。
on_mount/4関数を作成しますLiveView 、 mountの前にコードを実行できるon_mountコールバックを提供します。このコールバックを使用して、 jwtセッションを確認し、 person ( Map )とloggedin ( boolean )値をsocketに割り当てます。
lib/liveview_chat_web/controllers/auth_controller.exファイル次のコードを追加して、 mount/4の2つのバージョンを定義します。
# import the assign_new function from LiveView
import Phoenix.LiveView , only: [ assign_new: 3 ]
# pattern match on :default auth and check session has jwt
def on_mount ( :default , _params , % { "jwt" => jwt } = _session , socket ) do
# verify and retrieve jwt stored data
claims = AuthPlug.Token . verify_jwt! ( jwt )
# assigns the person and the loggedin values
socket =
socket
|> assign_new ( :person , fn ->
AuthPlug.Helpers . strip_struct_metadata ( claims )
end )
|> assign_new ( :loggedin , fn -> true end )
{ :cont , socket }
end
# when jwt is not defined just returns the current socket
def on_mount ( :default , _params , _session , socket ) do
socket = assign_new ( socket , :loggedin , fn -> false end )
{ :cont , socket }
endassile_new/3存在しない場合は、ソケットに値を割り当てます。
on_mount/2コールバックが定義されたら、 lib/liveview_chat_web/live/message_live.exファイルで呼び出すことができます。
defmodule LiveviewChatWeb.MessageLive do
use LiveviewChatWeb , :live_view
alias LiveviewChat.Message
# run authentication on mount
on_mount LiveviewChatWeb.AuthController
これで、人々が認証できるすべてのロジックがあります。ルートレイアウトファイルlib/liveview_chat_web/templates/layout/root.html.heexを更新するだけで、 login ( logout )リンクを表示する必要があります。
< body >
< header >
< section class =" container " >
< nav >
< ul >
< %= if @loggedin do % >
< li >
< img width =" 40px " src = {@person.picture}/ >
</ li >
< li > < %= link "logout", to: "/logout" % > </ li >
< % else % >
< li > < %= link "Login", to: "/login" % > </ li >
< % end % >
</ ul >
</ nav >
< h1 > LiveView Chat Example </ h1 >
</ section >
</ header >
< %= @inner_content % >
</ body >その人がまだloggedinでない場合、 loginリンクが表示されます。そうしないと、 logoutリンクが表示されます。
最後のステップは、メッセージフォームの名前フィールドに記録された人の名前を表示することです。そのため、 mount関数のフォームの変更セットを更新して、名前パラメーターを設定できます。
def mount ( _params , _session , socket ) do
if connected? ( socket ) , do: Message . subscribe ( )
# add name parameter if loggedin
changeset =
if socket . assigns . loggedin do
Message . changeset ( % Message { } , % { "name" => socket . assigns . person [ "givenName" ] } )
else
Message . changeset ( % Message { } , % { } )
end
messages = Message . list_messages ( ) |> Enum . reverse ( )
{ :ok , assign ( socket , messages: messages , changeset: changeset ) ,
temporary_assigns: [ messages: [ ] ] }
endこれでアプリケーションを実行し、ログイン/ログアウトできるようになりました!

このセクションでは、 Phoenixの存在を使用して、現在アプリケーションを使用している人々のリストを表示します。
最初のステップはlib/liveview_chat/presence.exファイルを作成することです。
defmodule LiveviewChat.Presence do
use Phoenix.Presence ,
otp_app: :liveview_chat ,
pubsub_server: LiveviewChat.PubSub
end次に、 lib/liveview_chat/application.exで、スーパーバイザーが開始するためのアプリケーションのリストに新しく作成されたPresenceモジュールを追加します。
def start ( _type , _args ) do
children = [
# Start the Ecto repository
LiveviewChat.Repo ,
# Start the Telemetry supervisor
LiveviewChatWeb.Telemetry ,
# Start the PubSub system
{ Phoenix.PubSub , name: LiveviewChat.PubSub } ,
# Presence
LiveviewChat.Presence ,
# Start the Endpoint (http/https)
LiveviewChatWeb.Endpoint
# Start a worker by calling: LiveviewChat.Worker.start_link(arg)
# {LiveviewChat.Worker, arg}
]
...
LiveView Endpointで存在機能を使用する準備ができました。
lib/liveview_chat_web/live/message_live.exファイルで、以下でmount機能を更新してください。
@ presence_topic "liveview_chat_presence"
def mount ( _params , _session , socket ) do
if connected? ( socket ) do
Message . subscribe ( )
{ id , name } =
if socket . assigns . loggedin do
{ socket . assigns . person [ "id" ] , socket . assigns . person [ "givenName" ] }
else
{ socket . id , "guest" }
end
{ :ok , _ } = Presence . track ( self ( ) , @ presence_topic , id , % { name: name } )
Phoenix.PubSub . subscribe ( PubSub , @ presence_topic )
end
changeset =
if socket . assigns . loggedin do
Message . changeset ( % Message { } , % { "name" => socket . assigns . person [ "givenName" ] } )
else
Message . changeset ( % Message { } , % { } )
end
messages = Message . list_messages ( ) |> Enum . reverse ( )
{ :ok ,
assign ( socket ,
messages: messages ,
changeset: changeset ,
presence: get_presence_names ( )
) , temporary_assigns: [ messages: [ ] ] }
end mount/3関数の主な変更を要約しましょう:
最初に、モジュール属性@presence_topicを作成して、プレゼンス関数で使用するtopicを定義します。
コードの次の部分は、人のidとその名前を含むタプルを定義します。その人がログディンではない場合、名前は「ゲスト」にデフォルトになります。
{ id , name } =
if socket . assigns . loggedin do
{ socket . assigns . person [ "id" ] , socket . assigns . person [ "givenName" ] }
else
{ socket . id , "guest" }
end第二に、トラック/4関数を使用して、新しいクライアントがアプリケーションを検討していることを存在感に知らせます。
{ :ok , _ } = Presence . track ( self ( ) , @ presence_topic , id , % { name: name } )第三に、pubsubを使用して、プレゼンスの変更に耳を傾けます(アプリケーションに参加または去る人):
Phoenix.PubSub . subscribe ( PubSub , @ presence_topic )最後に、ソケットに新しいpresenceを作成します。
presence : get_presence_names ( ) get_presence_names関数は、loggedinユーザーのリストを返し、「ゲスト」ユーザーの数がある場合は返されます。
MessageLiveモジュールの最後に次のコードを追加します。
defp get_presence_names ( ) do
Presence . list ( @ presence_topic )
|> Enum . map ( fn { _k , v } -> List . first ( v . metas ) . name end )
|> group_names ( )
end
# return list of names and number of guests
defp group_names ( names ) do
loggedin_names = Enum . filter ( names , fn name -> name != "guest" end )
guest_names =
Enum . count ( names , fn name -> name == "guest" end )
|> guest_names ( )
if guest_names do
[ guest_names | loggedin_names ]
else
loggedin_names
end
end
defp guest_names ( 0 ) , do: nil
defp guest_names ( 1 ) , do: "1 guest"
defp guest_names ( n ) , do: " #{ n } guests"上記のコードの重要な関数呼び出しはPresence.list(@presence_topic)です。リスト/1関数は、アプリケーションを使用してユーザーのリストを返します。 function group_namesとguest_names 、 listで返される存在データを操作するためにここにあります。https://hexdocs.pm/phoenix/phoenix.presence.html#c:list/1-presence-data-crutureを参照してください
これまでに、 mount機能のチャットページを使用して新しい人を追跡し、PubSubを使用してプレゼンスの変更を聞いてきました。最後のステップは、 handle_info関数を追加してこれらの変更を処理することです。
def handle_info ( % { event: "presence_diff" , payload: _diff } , socket ) do
{ :noreply , assign ( socket , presence: get_presence_names ( ) ) }
end最後に、「存在感」イベントでリアルタイムで発生すると、クライアントに参加しているイベントと休暇イベントの違いが送信されます。
handle_info関数は、 presence_diffイベントをキャッチし、 get_presence_names関数呼び出しの結果としてソケットにpresence値を再割り当てします。
名前を表示するには、 lib/liveview_chat_web/templates/message/messages.html.heexテンプレートファイルに次のものを追加します。
< b > People currently using the app: </ b >
< ul >
< %= for name < - @presence do % >
< li >
< %= name % >
</ li >
< % end % >
</ ul >これで、アプリケーションを実行して、Loggedinユーザーとゲストユーザーの数を確認できるようになりました。
test/liveview_chat_web/live/message_live_test.exsにこれら2つのテストを追加することにより、テンプレートが適切に更新されたことをテストできます。
test "1 guest online" , % { conn: conn } do
{ :ok , view , _html } = live ( conn , "/" )
assert render ( view ) =~ "1 guest"
end
test "2 guests online" , % { conn: conn } do
{ :ok , _view , _html } = live ( conn , "/" )
{ :ok , view2 , _html } = live ( conn , "/" )
assert render ( view2 ) =~ "2 guests"
end Tailwindが初めての場合は、https://github.com/dwyl/learn-tailwindをご覧ください
lib/liveview_chat_web/templates/layout/root.html.heexの内容を次のように置き換えます。
<!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.0 " />
< meta name =" csrf-token " content = {csrf_token_value()} >
< %= live_title_tag assigns[:page_title] || "LiveviewChat", suffix: " · Phoenix Framework" % >
< script defer phx-track-static type =" text/javascript " src = {Routes.static_path(@conn, "/assets/app.js")} > </ script >
< script src =" https://cdn.tailwindcss.com " > </ script >
</ head >
< body >
< header class =" bg-slate-800 w-full min-h-[15%] pt-5 pb-1 mb-2 " >
< section >
< nav >
< div class =" text-white width-[10%] float-left ml-3 -mt-5 align-middle " >
< b > People in Chat: </ b >
< ul >
< %= for name < - @presence do % >
< li >
< %= name % >
</ li >
< % end % >
</ ul >
</ div >
< ul class =" float-right mr-3 " >
< %= if @loggedin do % >
< li >
< img width =" 42px " src = {@person.picture} class =" -mt-3 " />
</ li >
< li class =" text-white " >
< %= link "logout", to: "/logout" % >
</ li >
< % else % >
< li class =" bg-green-600 text-white rounded-xl px-4 py-2 w-full mb-2 font-bold " >
< %= link "Login", to: "/login" % >
</ li >
< % end % >
</ ul >
</ nav >
< h1 class =" text-3xl mb-4 text-center font-mono text-white " > LiveView Chat Example </ h1 >
</ section >
</ header >
< %= @inner_content % >
</ body >
</ html >そして、 lib/liveview_chat_web/templates/message/messages.html.heexの内容を次のものに置き換えます。
< ul id =' msg-list ' phx-update =" append " >
< %= for message < - @messages do % >
< li id = { "msg-#{message.id}"} class="px-5" >
< small class =" float-right text-xs align-middle " >
< %= message.inserted_at % >
</ small >
< b > < %= message.name % > : </ b >
< %= message.message % >
</ li >
< % end % >
</ ul >
< footer class =" fixed bottom-0 w-full bg-slate-300 pb-2 px-5 pt-2 " >
< .form let={f} for={@changeset} id="form" phx-submit="new_message" phx-hook="Form" >
< %= if @loggedin do % >
< %= text_input f, :name, id: "name", value: @person.givenName,
class: "hidden" % >
< % else % >
< %= text_input f, :name, id: "name", placeholder: "Name", autofocus: "true",
class: "border p-2 w-9/12 mb-2 mt-2 mr2" % >
< span class =" italic text-2xl ml-4 " > or </ span >
< span class =" bg-green-600 text-white rounded-xl px-4 py-2 mb-2 mt-3 float-right " >
< %= link "Login", to: "/login" % >
</ span >
< %= error_tag f, :name % >
< % end % >
< %= text_input f, :message, id: "msg", placeholder: "Message",
class: "border p-2 w-10/12 mb-2 mt-2 float-left" % >
< p class =" text-amber-600 " >
< %= error_tag f, :message % >
</ p >
< %= submit "Send", class: "bg-sky-600 text-white rounded-xl px-4 py-2 mt-2 float-right" % >
</ .form >
</ footer >これで、次のようなUI/レイアウトが必要です。

使用されているTailwindクラスのいずれかについて質問がある場合は、2分間グーグルで費やしてから、まだ立ち往生している場合は、問題を開いてください。
この例が便利だと思ったら、githubリポジトリをお願いします。
これがあなたが読みたいかもしれない他のいくつかのリポジトリを紹介します:
質問や提案はありますか?新しい問題を開くことをheしないでください!
ありがとう!