
시도해보십시오 : Phoenix-Chat .fly.dev
피닉스에서 채팅 앱을 구축, 테스트 및 배포하기 위한 단계별 자습서 !
page_controller_test.exs 를 업데이트하십시오user_socket.js 에서 줄을 댓글을 달았습니다mix.exs 에 (개발) 종속성으로 excoveralls 추가하십시오coveralls.json 이라는 새 파일을 만듭니다Presence 추가합니다 채팅 앱은 실시간 예제의 "Hello World" 입니다.
안타깝게도 대부분의 예제 앱은 몇 가지 기본 사항을 보여주고 나머지를 무시합니다 ...?
따라서 초보자 는 종종 다음에 해야 할 일에 대해 잃어 버리거나 혼란스러워합니다 !
앱을 구축하고 실행하는 " 실제 세계 "의 일부인 테스트, 배포, 문서 또는 기타 " 향상 "을 고려하는 자습서는 거의 없습니다 . 그래서 이것들은 우리가 " 격차를 메우기 "를 다루는 주제입니다.
우리는 이 튜토리얼 Ecto 가장 쉽게 Phoenix 가장 쉬운 방법이 Channels 위해이 튜토리얼을 썼습니다.
이것은 우리가 Elixir , Phoenix 배울 때 우리가 원했던 예/튜토리얼입니다 ... 유용하다고 생각되면 감사합니다!
방법을 보여주는 간단한 단계별 자습서
mix phx.new chat 사용 "Generator"명령 사용 )Fly.io 에 배포하여 사람들에게 창조물을 보여줄 수 있습니다!처음에는 구성 파일과 " Phoenix Internals "를 의도적으로 건너 뜁니다 . 그러나 걱정하지 마십시오. 필요할 때 우리는 그들에게 돌아갈 것입니다. 우리는 왜 우리가 무언가를 배우고 있는지 명백 하고 실용적이기 때문에 " 정시 "( 필요할 때 ) 학습을 선호합니다.
이 예는 완전한 초보자를 위한 " 나의 첫 번째 피닉스 "앱입니다.
우리는 가능한 한 적게 가정 하려고 노력하지만, 우리가 " 단계를 건너 뛰 거나"어떤 이유로 든 " 고정 된 "느낌이 들거나 ( 이 예와 관련하여 ) Github에서 문제를 열어주십시오!
@dwyl과 Phoenix 커뮤니티는 모두 초보자에게 친숙하므로 두려워하거나 부끄러워하지 마십시오.
또한 질문을함으로써, 당신은 같은 일에 갇혀있을 수있는 모든 사람을 돕고 있습니다!
이 지침은 처음부터 채팅 앱을 만드는 방법을 보여줍니다.
brew install elixir참고 : Mac에 이미
Elixir설치되어 있고 최신 버전으로 업그레이드하려는 경우 Run :brew upgrade elixir
mix archive.install hex phx_new 기본 엘릭서 구문 지식이 도움이됩니다.
Dwyl/ Learn-Elixir를 참조하십시오
기본 JavaScript 지식은 유리 하지만 ( "프론트 엔드"코드가 매우 기본적이고 잘 구성되어 있기 때문에 필수적이지는 않습니다 ). 참조 : Dwyl/JavaScript-the-Good-Parts-Notes
최신 버전 의 Elixir 가 있는지 확인하십시오 ( 터미널에서 다음 명령을 실행 ) :
elixir -v당신은 다음과 같은 것을 볼 수 있어야합니다.
Erlang/OTP 25 [erts-13.1.1] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace]
Elixir 1.14.1 (compiled with Erlang/OTP 25)최신 버전의 피닉스 가 있는지 확인하십시오.
mix phx.new -v당신은 볼 수 있어야합니다 :
Phoenix installer v1.7.0-rc.2참고 :
Phoenix버전이 더 새롭 면이 문서를 자유롭게 업데이트하십시오! 우리는 최선을 다해 업데이트를 유지하기 위해 최선을 다하지만 ... 당신의 기여는 항상 환영합니다!
이 튜토리얼에서는 Phoenix 1.7의 두 번째 릴리스 후보 인
Phoenix 1.71.7-RC2를 사용하고 있습니다. 글을 쓰는 시점에서 피닉스를 설치하면 최신 안정 버전은v1.7이 아닙니다. 이 버전을 사용하려면 공식 안내서를 따르십시오 (걱정하지 마십시오. 하나의 명령을 실행 중입니다!)-> https://www.phoenixframework.org/blog/phoenix-1.7 reeleident그러나 릴리스 후이 글을 읽는 경우
v1.7설치되며 터미널에Phoenix installer v1.7.0표시됩니다.
PostgreSQL 이 실행 중인지 확인 ( 앱이 채팅 메시지를 저장할 수 있음 ) 다음 명령을 실행하십시오.
lsof -i :5432다음과 유사한 출력이 표시됩니다.
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
postgres 529 Nelson 5u IPv6 0xbc5d729e529f062b 0t0 TCP localhost:postgresql (LISTEN)
postgres 529 Nelson 6u IPv4 0xbc5d729e55a89a13 0t0 TCP localhost:postgresql (LISTEN) 이것은 PostgreSQL이 TCP 포트 5432 ( 기본 포트 )에서 " 듣기 "라는 것을 알려줍니다.
lsof 명령이 터미널에서 결과를 얻지 못하면 실행하십시오.
pg_isready다음을 인쇄해야합니다.
/tmp:5432 - accepting connections이 모든 "사전 비행 수표"가 수행되면서, Let 's Fly !
채팅 앱을 처음부터 구축하려고 시도하기 전에 완성 된 작업 버전을 복제하고 실행하여 무엇을 기대 해야하는지에 대한 아이디어를 얻으십시오.
터미널에서 다음 명령을 실행하여 Repo를 복제합니다.
git clone [email protected]:dwyl/phoenix-chat-example.git phoenix-chat-example 디렉토리로 변경 하고이 명령으로 Elixir 및 Node.js 종속성을 모두 설치하십시오.
cd phoenix-chat-example
mix setup명령으로 Phoenix 앱을 실행하십시오.
mix phx.server앱 LocalHost : 4000 두 개의 웹 브라우저에서 4000을 열면 Enter 키를 누르 자마자 채팅 메시지가 모두 표시되는 것을 볼 수 있습니다.

완성 된 Phoenix 채팅 앱이 컴퓨터에서 작동한다는 것을 확인 했으므로 이제 처음부터 제작할 차례입니다!
디렉토리 변경 :
cd ..그리고 건물을 시작하십시오!
LocalHost의 터미널 프로그램에서 다음 명령을 입력하여 앱을 작성하십시오.
mix phx.new chat --no-mailer --no-dashboard --no-gettext 디렉토리 구조 및 프로젝트 파일이 생성됩니다.
우리는 프로젝트가 Mailer 파일을 생성하고
Phoenix.LiveDashboard를 포함시키고gettext파일 (i18n의 경우)을 포함시키기를 원하지 않기 때문에--no-mailer--no-dashboard--no-gettext인수와 함께mix phx.new명령을 실행하고 있습니다.
" 의존성을 가져오고 설치하라는 요청? [YN]",
터미널에 Y를 입력 한 다음 Enter ( return ) 키를 입력하십시오.
당신은 볼 수 있어야합니다 : 
제안 된 명령을 실행하여 디렉토리를 chat 디렉토리로 변경하십시오.
cd chat이제 다음 명령을 실행합니다.
mix setup참고 :이 시점에서 이미 "앱"이 있습니다. 아직 아무것도 하지 않습니다 (아직) ...
터미널에서mix phx.server실행할 수 있습니다. 오류가 표시 되더라도 걱정하지 마십시오.
메시지, 이것은 아직 데이터베이스를 만들지 않았기 때문입니다.
우리는 6 단계에서 그것을 처리 할 것입니다!
현재 브라우저에서 http : // localhost : 4000을 엽니 다
그리고default"Phoenix에 오신 것을 환영합니다"홈페이지를 볼 수 있습니다.

Ctrl + C 명령으로 터미널의 Phoenix 서버를 종료하십시오.
터미널 창에서 다음 명령을 실행하십시오.
mix test
다음과 유사한 출력이 표시됩니다.
Generated chat app
.....
Finished in 0.02 seconds (0.02s async, 0.00s sync)
5 tests, 0 failures
Randomized with seed 84184이제 우리는 모든 것이 작동한다는 것을 확인 했으므로 (모든 테스트 패스) 흥미로운 부분을 계속합시다!
채팅 앱에서 사용할 (WebSocket) 채널을 생성하십시오.
mix phx.gen.channel Room새 소켓 핸들러 유형
y설치를 확인하고[Enter]키를 누르라는 메시지가 표시됩니다.
이것은 세 가지 파일을 만듭니다.
* creating lib/chat_web/channels/room_channel.ex
* creating test/chat_web/channels/room_channel_test.exs
* creating test/support/channel_case.ex두 개의 파일을 추가로 작성하는 것 외에도 :
* creating lib/chat_web/channels/user_socket.ex
* creating assets/js/user_socket.js room_channel.ex 파일을 수신/전송하는 메시지와 room_channel_test.exs 채널과의 기본 상호 작용을 테스트합니다. 나중에 생성 된 socket 파일에 중점을 둡니다. ( 아직 걱정하지 마십시오. 아래 14 단계에서 테스트 파일을 살펴 보겠습니다 !)
우리는 앱에서 코드를 업데이트해야한다는 정보를 제공합니다.
Add the socket handler to your ` lib/chat_web/endpoint.ex ` , for example:
socket " /socket " , ChatWeb.UserSocket,
websocket: true,
longpoll: false
For the front-end integration, you need to import the ` user_socket.js `
in your ` assets/js/app.js ` file:
import " ./user_socket.js " 생성기는 우리에게 프론트 엔드에서 클라이언트 코드를 가져 오라고 요청합니다. 나중에 해보자. 지금은 lib/chat_web/endpoint.ex 파일을 열고 지침을 따르십시오.
그런 다음 /lib/chat_web/channels/user_socket.ex 라는 파일을 엽니 다
그리고 선을 변경하십시오.
channel "room:*" , ChatWeb.RoomChannel에게:
channel "room:lobby" , ChatWeb.RoomChannel여기에서 변경 사항을 확인하십시오.
이렇게하면 "room:lobby" 로 전송되는 메시지가 RoomChannel 로 라우팅되도록합니다.
이전의 "room.* 는 "room" 내의 하위 주제가 라우팅되었음을 의미했습니다. 그러나 현재로서는 하나의 하위 주제로 좁아 봅시다.
Phoenix 채널에 대한 자세한 내용은 ( 적극 권장 ) 읽기 : https://hexdocs.pm/phoenix/channels.html
/lib/chat_web/controllers/page_html/home.html.heex 파일을 엽니 다
그리고 다음 코드 를 복사합니다 ( 또는 유형 ).
<!-- The list of messages will appear here: -->
< div class =" mt-[4rem] " >
< ul id =" msg-list " phx-update =" append " class =" pa-1 " > </ ul >
</ div >
< footer class =" bg-slate-800 p-2 h-[3rem] fixed bottom-0 w-full flex justify-center " >
< div class =" w-full flex flex-row items-center text-gray-700 focus:outline-none font-normal " >
< input type =" text " id =" name " placeholder =" Name " required
class =" grow-0 w-1/6 px-1.5 py-1.5 " />
< input type =" text " id =" msg " placeholder =" Your message " required
class =" grow w-2/3 mx-1 px-2 py-1.5 " />
< button id =" send " class =" text-white bold rounded px-3 py-1.5 w-fit
transition-colors duration-150 bg-sky-500 hover:bg-sky-600 " >
Send
</ button >
</ div >
</ footer > 이것은 채팅 메시지를 입력하는 데 사용할 기본 형식입니다.
수업은 예를 들어 w-full 과 items-center 양식을 스타일링하기 위한 TailwindCSS 클래스입니다.
Phoenix에는 기본적으로 Tailwind가 포함되어 있으므로 App/Idea/"MVP"로 진행할 수 있습니다!
Tailwind처음 사용하는 경우 Dwyl/ Learn-Tailwind를 참조하십시오.사용 된
Tailwind클래스에 대해 궁금한 점이 있으면 공무원 (Superb!) 문서 : Tailwindcss.com/docs를 검색하거나 검색하는 데 2 분을 보내십시오.
home.html.heex 템플릿 파일은 다음과 같습니다 : /lib/chat_web/controllers/page_html/home.html.heex
lib/chat_web/components/layouts/root.html.heex 파일을 열고 <body> 태그를 찾으십시오. <body> 의 내용을 다음 코드로 바꾸십시오.
< body class =" bg-white antialiased min-h-screen flex flex-col " >
< header class =" bg-slate-800 w-full h-[4rem] top-0 fixed flex flex-col justify-center z-10 " >
< div class =" flex flex-row justify-center items-center " >
< h1 class =" w-4/5 md:text-3xl text-center font-mono text-white " >
Phoenix Chat Example
</ h1 >
</ div >
</ header >
< %= @inner_content % >
</ body > root.html.heex 템플릿 파일은 다음과 같아야합니다 : /lib/chat_web/components/layouts/root.html.heex
이 단계가 끝나면 Phoenix Server mix phx.server 실행하고 브라우저에서 앱을 보면 다음과 같습니다.

따라서 이미 기본 채팅 앱처럼 보이기 시작했습니다. 안타깝게도, 우리는 home.html.heex 의 사본을 변경했기 때문에 page_controller_test.exs 가 실패합니다.
명령 실행 :
mix test 1) test GET / (ChatWeb.PageControllerTest)
test/chat_web/controllers/page_controller_test.exs:4
Assertion with =~ failed
code: assert html_response(conn, 200) =~ "Peace of mind from prototype to production"
고맙게도 이것은 고치기 쉽습니다.
page_controller_test.exs 를 업데이트하십시오 test/chat_web/controllers/page_controller_test.exs 파일을 열고 줄을 교체하십시오.
assert html_response ( conn , 200 ) =~ "Peace of mind from prototype to production"와 함께:
assert html_response ( conn , 200 ) =~ "Phoenix Chat Example"이제 테스트를 다시 실행하면 다음과 같습니다.
mix test
샘플 출력 :
........
Finished in 0.1 seconds (0.09s async, 0.06s sync)
8 tests, 0 failures
Randomized with seed 275786
오픈 assets/js/app.js , 타협 및 라인 변경 :
import socket from "./user_socket.js" 라인이 밝혀 지면 앱은 socket.js 파일을 가져 와서 WebSocket 기능을 제공합니다.
그런 다음 파일 하단에 다음 JavaScript ( "Client") 코드를 추가하십시오.
/* Message list code */
const ul = document . getElementById ( 'msg-list' ) ; // list of messages.
const name = document . getElementById ( 'name' ) ; // name of message sender
const msg = document . getElementById ( 'msg' ) ; // message input field
const send = document . getElementById ( 'send' ) ; // send button
const channel = socket . channel ( 'room:lobby' , { } ) ; // connect to chat "room"
channel . join ( ) ; // join the channel.
// Listening to 'shout' events
channel . on ( 'shout' , function ( payload ) {
render_message ( payload )
} ) ;
// Send the message to the server on "shout" channel
function sendMessage ( ) {
channel . push ( 'shout' , {
name : name . value || "guest" , // get value of "name" of person sending the message. Set guest as default
message : msg . value , // get message text (value) from msg input field.
inserted_at : new Date ( ) // date + time of when the message was sent
} ) ;
msg . value = '' ; // reset the message input field for next message.
window . scrollTo ( 0 , document . documentElement . scrollHeight ) // scroll to the end of the page on send
}
// Render the message with Tailwind styles
function render_message ( payload ) {
const li = document . createElement ( "li" ) ; // create new list item DOM element
// Message HTML with Tailwind CSS Classes for layout/style:
li . innerHTML = `
<div class="flex flex-row w-[95%] mx-2 border-b-[1px] border-slate-300 py-2">
<div class="text-left w-1/5 font-semibold text-slate-800 break-words">
${ payload . name }
<div class="text-xs mr-1">
<span class="font-thin"> ${ formatDate ( payload . inserted_at ) } </span>
<span> ${ formatTime ( payload . inserted_at ) } </span>
</div>
</div>
<div class="flex w-3/5 mx-1 grow">
${ payload . message }
</div>
</div>
`
// Append to list
ul . appendChild ( li ) ;
}
// Listen for the [Enter] keypress event to send a message:
msg . addEventListener ( 'keypress' , function ( event ) {
if ( event . key === `Enter` && msg . value . length > 0 ) { // don't sent empty msg.
sendMessage ( )
}
} ) ;
// On "Send" button press
send . addEventListener ( 'click' , function ( event ) {
if ( msg . value . length > 0 ) { // don't sent empty msg.
sendMessage ( )
}
} ) ;
// Date formatting
function formatDate ( datetime ) {
const m = new Date ( datetime ) ;
return m . getUTCFullYear ( ) + "/"
+ ( "0" + ( m . getUTCMonth ( ) + 1 ) ) . slice ( - 2 ) + "/"
+ ( "0" + m . getUTCDate ( ) ) . slice ( - 2 ) ;
}
// Time formatting
function formatTime ( datetime ) {
const m = new Date ( datetime ) ;
return ( "0" + m . getUTCHours ( ) ) . slice ( - 2 ) + ":"
+ ( "0" + m . getUTCMinutes ( ) ) . slice ( - 2 ) + ":"
+ ( "0" + m . getUTCSeconds ( ) ) . slice ( - 2 ) ;
}잠시 시간을내어 JavaScript 코드를 읽고 그것이하는 일에 대한 이해를 확인하십시오.
바라건대 인라인 댓글이 자명하지만 불분명 한 것이 있으면 물어보십시오!
이 시점에서 app.js 파일은 다음과 같아야합니다 : /assets/js/app.js
user_socket.js 에서 줄을 댓글을 달았습니다 기본적으로 Phoenix Channel (Client)은 Generic Room : "topic:subtopic" 을 구독합니다. 우리는 이것을 사용하지 않기 때문에 user_socket.js 파일에 몇 줄을 댓글을 달아 브라우저/콘솔의 "unable to join: unmatched topic" 오류를 피할 수 있습니다. 편집기에서 파일을 열고 다음 줄을 찾으십시오.
let channel = socket . channel ( "room:42" , { } )
channel . join ( )
. receive ( "ok" , resp => { console . log ( "Joined successfully" , resp ) } )
. receive ( "error" , resp => { console . log ( "Unable to join" , resp ) } )라인을 언급하여 실행되지 않도록하십시오.
//let channel = socket.channel("room:42", {})
//channel.join()
// .receive("ok", resp => { console.log("Joined successfully", resp) })
// .receive("error", resp => { console.log("Unable to join", resp) }) user_socket.js 는 이제 다음과 같아야합니다 : /assets/js/user_socket.js
나중에 채팅 앱을 정리하기로 결정하면 파일에서 주석은 줄을
delete수 있습니다.
우리는 채널에 가입하고 메시지를받는 방법을 참조하기 위해 유지하고 있습니다.
앱을 실행중인 경우 name 과 message 필드를 채우고 Enter (또는 Send 누르십시오)를 클릭하십시오.
메시지는 다른 창에 나타납니다!

이 작업을 수행하면 진행할 수 있습니다.
채팅 기록을 저장하고 싶지 않다면이 앱을 즉시 배포 할 수 있고 완료되었습니다!
실제로, " 사용 사례 "/" 기능 "은 " ephemeral "채팅을 역사 하지 않고 채팅하는 것이 될 수 있습니다 ... http://www.psstchat.com/ 참조.
그러나 우리는 대부분의 채팅 앱이 역사를 저장하여 "채널"에 가입하는
new사람들이 역사를 볼 수 있고 "결석 한"사람들이 역사를 "잡을 수"를 볼 수 있다고 가정 합니다.
터미널에서 다음 명령을 실행하십시오.
mix phx.gen.schema Message messages name:string message:string다음 출력이 표시됩니다.
* creating lib/chat/message.ex
* creating priv/repo/migrations/20230203114114_create_messages.exs
Remember to update your repository by running migrations:
$ mix ecto.migrate명확성을 위해 그 명령을 세분화합시다.
mix phx.gen.schema 믹스 명령을 새 스키마 (데이터베이스 테이블) 만들기Message - 우리의 메시지에서 레코드의 단일 이름 "collection"messages - 컬렉션의 이름 ( 또는 데이터베이스 테이블 )name:string - 메시지를 보내는 사람의 이름은 string 로 저장됩니다.message:string - 사람이 보낸 메시지도 string 로 저장됩니다. creating lib/chat/message.ex 라인은 메시지 데이터베이스 테이블의 "스키마"를 만듭니다.
또한 마이그레이션 파일이 생성됩니다 : 예 : creating priv/repo/migrations/20230203114114_create_messages.exs " 마이그레이션 "은 실제로 데이터베이스에서 데이터베이스 테이블을 생성합니다 .
터미널에서 다음 명령을 실행하여 messages 테이블을 만듭니다.
mix ecto.migrate컨텍스트 에 대해서는 hexdocs.pm/ecto_sql/ ecto.migration .html을 읽는 것이 좋습니다
터미널에 다음을 볼 수 있습니다.
11:42:10.130 [info] == Running 20230203114114 Chat.Repo.Migrations.CreateMessages.change/0 forward
11:42:10.137 [info] create table messages
11:42:10.144 [info] == Migrated 20230203114114 in 0.0s PostgreSQL GUI ( 예 : PGADMIN )를 열면 chat_dev 데이터베이스에서 메시지 테이블이 생성되었음을 알 수 있습니다.

messages 테이블에서 " 마우스 오른쪽 버튼 "( ctrl + click )으로 테이블 스키마를 볼 수 있고 "속성"을 선택할 수 있습니다.

참고 : 섹션 7, 8 및 9의 경우, 우리의 코드가 채팅 앱에서 발생할 수있는 다양한 이벤트를 "처리"하는 방법을 살펴볼 것입니다.
Phoenix는 Elixir의 프로세스 커뮤니케이션에서 기본 메시지 통과 논리의 대부분을 초록합니다 (Elixir 프로세스가 어떻게 통신하는지에 대한 자세한 정보는 여기에서 읽습니다).
피닉스에서 클라이언트에서 전송 된 이벤트/메시지는 이벤트 이름에 따라 해당 핸들러 기능으로 자동 라우팅되므로 메시지 처리가 원활하고 간단합니다!.
lib/chat_web/channels/room_channel.ex 파일을 열고 기능 내부 def handle_in("shout", payload, socket) do .
Chat.Message . changeset ( % Chat.Message { } , payload ) |> Chat.Repo . insert 기능이 다음과 같이 보이도록합니다.
def handle_in ( "shout" , payload , socket ) do
Chat.Message . changeset ( % Chat.Message { } , payload ) |> Chat.Repo . insert
broadcast socket , "shout" , payload
{ :noreply , socket }
end 이전에 발견 된 경우 assets/js/app.js 파일에서 sendMessage() 함수를 사용하여 "Shout"이벤트에서 메시지를 서버로 푸시했습니다 .
Phoenix는 이벤트 이름이 'Shout'과 일치하기 때문에 메시지를 Server-Side handle_in("shout", payload, socket) 기능으로 라우팅합니다.
이 기능에서는 페이로드 (메시지 텍스트 및 기타 데이터)를 처리하고 데이터베이스에 삽입합니다. 정돈된!
lib/chat/message.ex 파일을 열고 Ecto.Query 가져옵니다.
defmodule Chat.Message do
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query # add Ecto.Query
그런 다음 새 기능을 추가하십시오.
def get_messages ( limit \ 20 ) do
Chat.Message
|> limit ( ^ limit )
|> order_by ( desc: :inserted_at )
|> Chat.Repo . all ( )
end 이 함수는 단일 매개 변수 limit 허용하여 고정/최대 레코드 수만 반환합니다. Ecto의 all 기능을 사용하여 데이터베이스에서 모든 레코드를 가져옵니다. Message 는 레코드를 얻으려는 스키마/테이블의 이름이며 제한은 가져올 최대 레코드 수입니다.
/lib/chat_web/channels/room_channel.ex 파일에서 새 기능을 만듭니다.
@ impl true
def handle_info ( :after_join , socket ) do
Chat.Message . get_messages ( )
|> Enum . reverse ( ) # revers to display the latest message at the bottom of the page
|> Enum . each ( fn msg -> push ( socket , "shout" , % {
name: msg . name ,
message: msg . message ,
inserted_at: msg . inserted_at ,
} ) end )
{ :noreply , socket } # :noreply
end 파일 상단에서 join 기능을 다음과 같이 업데이트합니다.
def join ( "room:lobby" , payload , socket ) do
if authorized? ( payload ) do
send ( self ( ) , :after_join )
{ :ok , socket }
else
{ :error , % { reason: "unauthorized" } }
end
end참고 : 섹션 7과 마찬가지로 Phoenix는 서버가 채널 프로세스를 통해 내부 메시지를
:after_join때이 기능을 호출하는 것을 알고 있습니다.
lib/chat_web/channels/room_channel.ex의join/3함수는 다음을 보냅니다. 다음:after_join message클라이언트가"room:lobby"주제에 성공적으로 연결할 때 채널 프로세스로 메시지를 보냅니다.
Phoenix 서버를 시작하십시오 ( 아직 실행되지 않은 경우 ) :
mix phx.server참고 : 컴파일하는 데 몇 초가 걸립니다 .
터미널에서 다음을 볼 수 있습니다.
[info] Running ChatWeb.Endpoint with cowboy 2.8.0 at 0.0.0.0:4000 (http)
[info] Access ChatWeb.Endpoint at http://localhost:4000
webpack is watching the files… 이것은 우리의 코드가 컴파일 된 ( 예상대로 ) TCP 포트 4000 에서 실행 중임을 알려줍니다!
두 개의 별도 브라우저 창 에서 채팅 웹 앱을 엽니 다 : http : // localhost : 4000
( 컴퓨터에 하나의 브라우저 만있는 경우 하나의 "incognito"탭을 사용해보십시오 )
두 브라우저 Windows 사이에 메시지를 보낼 수 있어야합니다. 
축하해요! 피닉스로 작성된 작업 ( 기본 ) 채팅 앱이 있습니다!
채팅 (메시지) 이력은 저장 됩니다!
즉, 브라우저를 새로 고침 하거나 다른 브라우저에 가입 할 수 있으며 여전히 역사를 볼 수 있습니다!
자동 테스트는 웹 응용 프로그램의 안정성을 보장하는 가장 좋은 방법 중 하나입니다.
참고 : 자동화 된 테스트 또는 "테스트 구동 개발"( "TDD")을 완전히 익숙하지 않은 경우 "기본"자습서를 읽거나 팔로우하는 것이 좋습니다 .
피닉스의 테스트는 빠르며 ( 테스트는 병렬로 실행됩니다! ) 시작하기 쉽습니다! ExUnit 테스트 프레임 워크가 내장되어 있으므로 어떤 프레임 워크 나 스타일을 사용할 프레임 워크 또는 스타일에 대한 "결정/토론"이 없습니다.
ExUnit 으로 테스트를 본 적이 없다면 두려워하지 마십시오. 과거에 어떤 자동 테스트를 작성한 경우 구문은 친숙 해야합니다.
새로운 Phoenix 앱을 만들거나 채널과 같은 새로운 기능을 추가 할 때마다 Phoenix는 새로운 테스트를 생성합니다 .
mix test 명령을 사용하여 테스트를 실행합니다 .
... ... ..
Finished in 0.1 seconds ( 0.05 s async , 0.06 s sync )
8 tests , 0 failures
Randomized with seed 157426이 경우 이러한 테스트 중 어느 것도 실패하지 않습니다. ( 8 개의 테스트, 0 실패 )
/room_channel_test.exs 파일에서 무슨 일이 일어나고 있는지 이해하는 데 잠시 시간을 내야합니다. 아직 열지 않은 경우 테스트 설명 및 코드를 읽으 십시오 .
약간의 맥락을 위해서는 https://hexdocs.pm/phoenix/ testing_channels .html을 읽는 것이 좋습니다.
/test/chat_web/channels/room_channel_test.exs의 첫 번째 테스트를 살펴 보겠습니다.
test "ping replies with status ok" , % { socket: socket } do
ref = push socket , "ping" , % { "hello" => "there" }
assert_reply ref , :ok , % { "hello" => "there" }
end 테스트는 setup 함수 ( 파일의 6 행 )에서 socket 가져오고 push 함수를 변수 ref 푸시로 호출 한 결과를 할당합니다. push 단지 메시지 ( 맵 %{"hello" => "there"} )를 socket "ping" 주제 로 푸시합니다 .
"ping" 주제를 처리하는 handle_in 함수 조항 :
def handle_in ( "ping" , payload , socket ) do
{ :reply , { :ok , payload } , socket }
end 보낸 페이로드로 단순히 답장을 보내 므로 테스트 에서 assert_reply 매크로를 사용하여 ref 이 다음과 같다고 주장 할 수 있습니다 :ok, %{"hello" => "there"}
참고 : 질문이 있거나 다른 테스트를 이해하는 데 도움 이 필요한 경우 GitHub에서 문제를 열어주십시오.
( 우리는이 튜토리얼을 합리적으로 "간단한"상태로 유지하려고 노력하고 있으므로 초보자는 무엇이든 "압도적"이 아닙니다 ...)
종종 우리는 종종 테스트를 읽고 테스트의 "갭"이 어디에 있는지 확인함으로써 응용 프로그램 ( 또는 API )에 대해 많은 것을 배울 수 있습니다.
고맙게도 우리는 몇 가지 단계만으로 이것을 달성 할 수 있습니다.
mix.exs 에 (개발) 종속성으로 excoveralls 추가하십시오 mix.exs 파일을 열고 "deps"기능을 찾으십시오.
defp deps do
마지막 줄의 끝에 쉼표를 추가 한 다음 목록 끝에 다음 줄을 추가하십시오.
{ :excoveralls , "~> 0.15.2" , only: [ :test , :dev ] } # tracking test coverage 또한 def project do 섹션 ( mix.exs 상단 )을 찾아 다음 줄을 목록에 추가하십시오.
test_coverage : [ tool : E xCoveralls ] ,
preferred_cli_env: [
coveralls : :test ,
"coveralls.detail": :test ,
"coveralls.post": :test ,
"coveralls.html": :test
] 그런 다음 방금 추가 한 excoveralls mix.exs 의존성을 설치하십시오 .
mix deps.get당신은 볼 수 있어야합니다 :
Resolving Hex dependencies...
Dependency resolution completed:
* Getting excoveralls (Hex package)
... etc.coveralls.json 이라는 새 파일을 만듭니다 채팅 프로젝트의 "루트"( 기본 디렉토리 )에서 coveralls.json 이라는 새 파일을 작성하고 다음을 복사합니다 .
{
"coverage_options" : {
"minimum_coverage" : 100
},
"skip_files" : [
" test/ " ,
" lib/chat/application.ex " ,
" lib/chat_web.ex " ,
" lib/chat_web/telemetry.ex " ,
" lib/chat_web/components/core_components.ex " ,
" lib/chat_web/channels/user_socket.ex "
]
}
이 파일은 매우 기본적이며 coveralls 앱에 minimum_coverage 100% ( 예 : 모든 것이 테스트됩니다 1 )를 요구하고 적용 범위 확인을 위해 test/ 디렉토리의 파일을 무시하도록 지시합니다. 또한 application.ex , telemetry.ex , core_components.ex 및 user_socket.ex 와 같은 파일도 프로젝트 기능과 관련이 없기 때문에 무시합니다.
1 우리는 모든 코드 에 대한 테스트를 작성하기 위해 전제로 약간의 시간을 투자하는 것이 나중에 버그가 적을 가치가 있다고 생각합니다.
버그 는 비싸고 테스트 는 싸고 자신감 / 신뢰성은 귀중합니다 .
적용 범위로 테스트를 실행하려면 다음 명령을 터미널로 복사합니다.
MIX_ENV = test mix do coveralls . json
Windows 사용 :
$ env :MIX_ENV = "test" ; mix do coveralls . json
당신은 볼 수 있어야합니다 :
Randomized with seed 527109
----------------
COV FILE LINES RELEVANT MISSED
100.0% lib/chat.ex 9 0 0
100.0% lib/chat/message.ex 26 4 0
100.0% lib/chat/repo.ex 5 0 0
70.0% lib/chat_web/channels/room_channel.ex 46 10 3
100.0% lib/chat_web/components/layouts.ex 5 0 0
100.0% lib/chat_web/controllers/error_html.ex 19 1 0
100.0% lib/chat_web/controllers/error_json.ex 15 1 0
100.0% lib/chat_web/controllers/page_controller 9 1 0
100.0% lib/chat_web/controllers/page_html.ex 5 0 0
100.0% lib/chat_web/endpoint.ex 49 0 0
66.7% lib/chat_web/router.ex 27 3 1
[TOTAL] 80.0%
----------------
우리가 여기서 할 수 있듯이, /lib 의 코드 라인의 80% 만이 우리가 작성한 테스트에 의해 "커버"되고 있습니다.
웹 브라우저의 적용 범위를 보려면 다음을 실행하십시오.
MIX_ENV = test mix coveralls . html ; open cover / excoveralls . html 기본 웹 브라우저에서 커버리지 보고서 (HTML)가 열립니다.

test/chat_web/channels/room_channel_test.exs 파일을 열고 다음 테스트를 추가하십시오.
test ":after_join sends all existing messages" , % { socket: socket } do
# insert a new message to send in the :after_join
payload = % { name: "Alex" , message: "test" }
Chat.Message . changeset ( % Chat.Message { } , payload ) |> Chat.Repo . insert ( )
{ :ok , _ , socket2 } = ChatWeb.UserSocket
|> socket ( "person_id" , % { some: :assign } )
|> subscribe_and_join ( ChatWeb.RoomChannel , "room:lobby" )
assert socket2 . join_ref != socket . join_ref
end 마지막으로, lib/chat_web/router.ex 내부에서 다음 코드에 주석하십시오.
pipeline :api do
plug :accepts , [ "json" ]
end 이 프로젝트에서 :api 사용하지 않기 때문에 테스트 할 필요가 없습니다.
이제 MIX_ENV=test mix do coveralls.json 볼 수 있습니다.
Randomized with seed 15920
----------------
COV FILE LINES RELEVANT MISSED
100.0% lib/chat.ex 9 0 0
100.0% lib/chat/message.ex 26 4 0
100.0% lib/chat/repo.ex 5 0 0
100.0% lib/chat_web/channels/room_channel.ex 46 10 0
100.0% lib/chat_web/components/layouts.ex 5 0 0
100.0% lib/chat_web/controllers/error_html.ex 19 1 0
100.0% lib/chat_web/controllers/error_json.ex 15 1 0
100.0% lib/chat_web/controllers/page_controller 9 1 0
100.0% lib/chat_web/controllers/page_html.ex 5 0 0
100.0% lib/chat_web/endpoint.ex 49 0 0
100.0% lib/chat_web/router.ex 27 2 0
[TOTAL] 100.0%
----------------
이 테스트는 subscribe_and_join 전에 메시지를 생성하므로 데이터베이스에 채팅에 가입하는 Clien에게 보내는 메시지가 있습니다.
이렇게하면 :after_join 적어도 하나의 메시지가 있고 Enum.each 적어도 한 번은 호출됩니다.
그와 함께 우리의 앱은 완전히 테스트되었습니다!
기본 인증을 지원하기 위해이 프로젝트를 확장 할 수 있습니다. 인증이 쉽게 구현되는 방법을 이해 하려면 쉽게/빠른 방법을 참조하십시오 : Auth.md
Presence 추가합니다 Phoenix 사용하는 데있어 가장 큰 장점 중 하나는 프로세스와 채널을 쉽게 추적 할 수 있다는 것입니다.
이것은 온라인인지 아닌지를 쉽게 보여줄 수있는 길을 열어줍니다!
이 기능을 개발하는 데 관심이 있다면, 우리는 당신을 위해만 presence.md 를 만들었습니다. ?
지속적인 통합을 사용하면 테스트 실행을 자동화하여 앱이 예상 대로 작동하는지 확인/확인할 수 있습니다 ( 배포 전 ). 이렇게하면 실수로 앱을 " 파괴 "하지 않습니다.
고맙게도 단계는 매우 간단합니다.
예를 들어 ci.yml 다음을 참조하십시오.
.github/workflows/ci.yml
fly.io에 배포하는 데 몇 분이 걸리며 공식 안내서를 따르는 것이 좋습니다 : fly.io/docs/elixir/
배포 된 후에는 웹/모바일 브라우저에서 앱을보고/사용할 수 있습니다.
EG : Phoenix-Chat .fly.dev/

이 예제가 유용하다는 것을 알게되면 Github 리포지토리를 보내주십시오.
더 많은 Phoenix와 LiveView 의 Magic을 배우려면 초보자의 튜토리얼을 읽어보십시오 : github.com/dwyl/ Phoenix-LiveView-Counter-Tutorial
LiveView를 사용하는 채팅 애플리케이션 버전의 경우 github.com/dwyl/ Phoenix-LiveView-Chat-example을 읽을 수 있습니다
우리와 함께 배우 주셔서 감사합니다! ☀️
이 repo는 @chrismccord의 간단한 채팅 예제에서 영감을 받았습니다 : https://github.com/chrismccord/phoenix_chat_example ❤️
Chris의 예제를 작성할 당시 2018 년 2 월 20 일에 마지막으로 업데이트되었으며 Phoenix 1.3을 참조하십시오 : 문제/40 참조.
Phoenix 1.3과 1.6 ( 최신 버전 ) 사이에는 상당히 차이가 있습니다.
우리의 튜토리얼은 Phoenix 1.6.2 (2021 년 10 월 기준)를 사용합니다. 우리의 희망은 단계별 초보자에 중점을 둔 튜토리얼을 작성하고 유지함으로써 Chris의 Repo에 PR을 쌓지 않고 Elixir/Phoenix 커뮤니티에 기여하기를 바랍니다.