
ลอง ใช้: Phoenix-Chat .fly.dev
การสอนทีละขั้นตอน สำหรับการสร้างการทดสอบและ การปรับใช้ แอพแชทในฟีนิกซ์!
page_controller_test.exsuser_socket.jsexcoveralls เป็นการพึ่งพาการพัฒนา (การพัฒนา) เพื่อ mix.exscoveralls.jsonPresence เพื่อติดตามว่าใครออนไลน์ แอพแชทเป็นตัวอย่าง "Hello World" ของตัวอย่างเรียลไทม์
น่าเศร้า ที่แอพตัวอย่าง ส่วนใหญ่ แสดง พื้นฐาน บางอย่างแล้ว เพิกเฉยต่อส่วนที่เหลือ ... ? ♀
ดังนั้น ผู้เริ่มต้น มักจะ หลงทาง หรือ สับสน ว่าพวกเขาควร ทำ หรือเรียนรู้ ต่อไป !
บทเรียน น้อย มากที่พิจารณา การทดสอบการปรับใช้เอกสาร หรือ " การปรับปรุง " อื่น ๆ ซึ่งเป็นส่วนหนึ่งของ " โลกแห่งความจริง " ของการสร้างและใช้แอพ ดังนั้นนี่คือหัวข้อที่เรา จะ ครอบคลุม เพื่อ " เติมช่องว่าง "
เราเขียนบทช่วยสอน นี้ เป็น วิธี ที่ง่ายที่สุด ในการเรียนรู้ Phoenix Ecto และ Channels ด้วย ตัวอย่าง ที่เป็นประโยชน์ ที่ทุกคน สามารถติดตามได้
นี่คือตัวอย่าง/บทช่วยสอนที่เรา ต้องการ เมื่อเราเรียนรู้ Elixir Phoenix ... หากคุณพบว่ามีประโยชน์โปรดขอบคุณ!
การสอนทีละขั้นตอนง่ายๆแสดงวิธี:
mix phx.new chat "Generator" )Fly.io เพื่อให้คุณสามารถ แสดง การสร้างของคุณ!ในขั้นต้น เรา จงใจ ข้ามไฟล์การกำหนดค่าและ " Phoenix internals " เพราะคุณ ( ผู้เริ่มต้น ) ไม่จำเป็น ต้องรู้เกี่ยวกับพวกเขาเพื่อ เริ่มต้น แต่ไม่ต้องกังวลเราจะกลับไปหาพวกเขาเมื่อ จำเป็น เราชอบ " เพียงแค่เวลา " ( เมื่อคุณต้องการ ) การเรียนรู้เพราะมันชัดเจนและ ใช้งานได้ ในทันที ว่าทำไม เราถึงเรียนรู้บางสิ่งบางอย่าง
ตัวอย่างนี้สำหรับ ผู้เริ่มต้นที่สมบูรณ์ เป็นแอพ " ฟีนิกซ์แรกของฉัน "
เราพยายามที่จะ สมมติ ให้น้อยที่สุด แต่ถ้าคุณคิดว่าเรา " ข้ามขั้นตอน " หรือคุณรู้สึกว่า " ติดอยู่ " ไม่ว่าด้วยเหตุผลใดก็ตามหรือมีคำถาม ใด ๆ ( เกี่ยวข้องกับตัวอย่างนี้ ) โปรดเปิดปัญหาเกี่ยวกับ GitHub!
ทั้งชุมชน @dwyl และ Phoenix นั้นเป็น มิตรกับผู้เริ่มต้น อย่างมาก ดังนั้นอย่ากลัว/ขี้อาย
นอกจากนี้โดยการถามคำถามคุณกำลังช่วยเหลือทุกคนที่หรืออาจติดอยู่กับสิ่ง เดียวกัน !
คำแนะนำเหล่านี้แสดงวิธี สร้าง แอพแชท ตั้งแต่เริ่มต้น
brew install elixirหมายเหตุ : หากคุณติดตั้ง
Elixirบน Mac ของคุณแล้วและต้องการอัพเกรดเป็นเวอร์ชันล่าสุด Run:brew upgrade elixir
mix archive.install hex phx_new ความรู้ ทางไวยากรณ์ Elixir ขั้นพื้นฐานจะช่วยได้
โปรดดู: dwyl/ learn-elixir
ความรู้พื้นฐาน JavaScript นั้นเป็น ประโยชน์ ( แต่ไม่จำเป็นเนื่องจากรหัส "front-end" นั้นค่อนข้างพื้นฐานและมีความสามารถดี ) ดู: 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-RC2 ซึ่งเป็นตัวเลือกรุ่นที่สองสำหรับ
Phoenix 1.7ในขณะที่เขียนหากคุณติดตั้งฟีนิกซ์ เวอร์ชันที่เสถียรล่าสุด ไม่ใช่v1.7หากต้องการใช้เวอร์ชันนี้ให้ทำตามคำแนะนำอย่างเป็นทางการ (ไม่ต้องกังวลมันเป็นเพียงการเรียกใช้คำสั่งเดียว!)-> https://www.phoenixframework.org/blog/phoenix-1.7- ได้รับการเผยแพร่อย่างไรก็ตามหากคุณกำลังอ่านสิ่งนี้หลังจากเปิดตัว
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ด้วยการ "ตรวจสอบก่อนการบิน" ทั้งหมดให้ บินกัน เถอะ!
ก่อนที่ คุณจะพยายามสร้างแอพแชทตั้งแต่เริ่มต้นโคลนและเรียกใช้เวอร์ชันที่ทำงาน เสร็จแล้ว เพื่อรับทราบว่าจะคาดหวังอะไร
ในเทอร์มินัลของคุณเรียกใช้คำสั่งต่อไปนี้เพื่อโคลน repo:
git clone [email protected]:dwyl/phoenix-chat-example.git เปลี่ยนเป็นไดเรกทอรี phoenix-chat-example และติดตั้งทั้ง Elixir และ Node.js ขึ้นอยู่กับคำสั่งนี้:
cd phoenix-chat-example
mix setupเรียกใช้แอพฟีนิกซ์ด้วยคำสั่ง:
mix phx.serverหากคุณเปิดแอพ LocalHost: 4000 ในเว็บเบราว์เซอร์อีกสองรายการคุณสามารถดูข้อความแชทที่แสดงในทั้งหมดได้ทันทีที่คุณกดปุ่ม ENTER :

ตอนนี้คุณได้ยืนยันแล้วว่าแอพแชทฟีนิกซ์ เสร็จแล้ว ทำงานบนเครื่องของคุณแล้วก็ถึงเวลาที่จะ สร้าง มันขึ้นมาตั้งแต่เริ่มต้น!
เปลี่ยนไดเรกทอรี:
cd ..และเริ่มสร้าง!
ในโปรแกรมเทอร์มินัลของคุณใน localhost ของคุณพิมพ์คำสั่งต่อไปนี้เพื่อสร้างแอพ:
mix phx.new chat --no-mailer --no-dashboard --no-gettext ที่จะสร้างโครงสร้างไดเรกทอรีและไฟล์โครงการ
เรากำลังเรียกใช้คำสั่ง
mix phx.newด้วย--no-mailer--no-dashboard--no-gettextอาร์กิวเมนต์เพราะเราไม่ต้องการให้โครงการของเราสร้างไฟล์ Mailer เพื่อรวมPhoenix.LiveDashboardและสร้างไฟล์gettext(สำหรับi18n)
เมื่อถูกขอให้ " ดึงและติดตั้งการพึ่งพา ? [yn]"
พิมพ์ y ในเทอร์มินัลของคุณตามด้วยปุ่ม ENTER ( return )
คุณควรเห็น: 
เปลี่ยนไดเรกทอรีเป็นไดเรกทอรี chat โดยเรียกใช้คำสั่งที่แนะนำ:
cd chatตอนนี้เรียกใช้คำสั่งต่อไปนี้:
mix setupหมายเหตุ : ณ จุดนี้มี "แอพ" อยู่แล้วมันไม่ได้ ทำ อะไรเลย (ยัง) ...
คุณ สามารถ เรียกใช้mix phx.serverในเทอร์มินัลของคุณ - ไม่ต้องกังวลหากคุณเห็นข้อผิดพลาด
ข้อความนี่เป็นเพราะเรายังไม่ได้สร้างฐานข้อมูลของเรา
เราจะดูแลสิ่งนั้นในขั้นตอนที่ 6!
สำหรับตอนนี้เปิด http: // localhost: 4000 ในเบราว์เซอร์ของคุณ
และคุณจะเห็นหน้าdefault"ยินดีต้อนรับสู่ Phoenix":

ปิดเซิร์ฟเวอร์ฟีนิกซ์ในเทอร์มินัลของคุณด้วยคำสั่ง ctrl + c
ในหน้าต่างเทอร์มินัลของคุณเรียกใช้คำสั่งต่อไปนี้:
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" แต่ตอนนี้ขอให้แคบลงเหลือเพียงหนึ่งหัวข้อย่อย?
สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับช่องฟีนิกซ์ ( เราขอแนะนำคุณ ) อ่าน: https://hexdocs.pm/phoenix/channels.html
เปิด The /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 ตามค่าเริ่มต้นเพื่อให้คุณสามารถทำงานได้กับแอพ/Idea/"MVP"!
หากคุณยังใหม่กับ
Tailwindโปรดดู: dwyl/ learn-tailwindหากคุณมีคำถามเกี่ยวกับคลาส
Tailwindใด ๆ ที่ใช้โปรดใช้เวลา 2 นาที googling หรือค้นหาเอกสารอย่างเป็นทางการ (ยอดเยี่ยม!): tailwindcss.com/docs แล้วถ้าคุณยังติดอยู่โปรดเปิดปัญหา
ไฟล์แม่แบบ 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
ในตอนท้ายของขั้นตอนนี้หากคุณเรียกใช้ฟีนิกซ์เซิร์ฟเวอร์ 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 โดยค่าเริ่มต้นช่องฟีนิกซ์ (ไคลเอนต์) จะสมัครสมาชิกห้องทั่วไป: "topic:subtopic" เนื่องจากเราจะไม่ใช้สิ่งนี้เราจึงสามารถหลีกเลี่ยงการเห็นข้อผิดพลาด "unable to join: unmatched topic" ในเบราว์เซอร์/คอนโซลของเราเพียงแค่แสดงความคิดเห็นสองสามบรรทัดในไฟล์ user_socket.js เปิดไฟล์ในตัวแก้ไขของคุณและค้นหาบรรทัดต่อไปนี้:
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 )
ข้อความควรปรากฏบน Windows ที่แตกต่างกัน!

ด้วยสิ่งนี้เสร็จแล้วเราสามารถดำเนินการต่อได้
หากเราไม่ ต้องการ บันทึก ประวัติการแชทเราสามารถ ปรับใช้ แอพนี้ ได้ทันที และเราจะทำ!
ในความเป็นจริงมันอาจเป็น " กรณีใช้ "/" ฟีเจอร์ " ที่จะมีการแชท " ชั่วคราว " โดยไม่มีประวัติ ใด ๆ ... ดู: 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 - คำสั่ง Mix เพื่อสร้างสคีมาใหม่ (ตารางฐานข้อมูล)Message - ชื่อเอกพจน์สำหรับบันทึกในข้อความ "คอลเลกชัน" ของเราmessages - ชื่อของคอลเลกชัน ( หรือตารางฐานข้อมูล )name:string - ชื่อของบุคคลที่ส่งข้อความเก็บไว้เป็น stringmessage: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 :

คุณสามารถดูสคีมาตารางได้โดย " คลิกขวา " ( ctrl + click ที่ Mac ) บนตาราง messages และเลือก "คุณสมบัติ":

หมายเหตุ : สำหรับส่วนที่ 7, 8 และ 9 เราจะออกมาว่ารหัสของเรา "จัดการ" เหตุการณ์ต่าง ๆ ที่สามารถเกิดขึ้นได้ในแอพแชทของเรา
ฟีนิกซ์บทคัดย่อลอจิกข้อความพื้นฐานในการสื่อสารกระบวนการของ 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"
ฟีนิกซ์กำหนดเส้นทางข้อความไปยังฟังก์ชั่น handle_in("shout", payload, socket) เพราะชื่อเหตุการณ์ตรงกับ 'Shout'
ในฟังก์ชั่นนี้เราจัดการเพย์โหลด (ซึ่งเป็นข้อความและข้อมูลอื่น ๆ ) และแทรกลงในฐานข้อมูลของเรา ประณีต!
เปิดไฟล์ 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 พารามิเตอร์เดียวเพื่อส่งคืนจำนวนระเบียนที่คงที่/สูงสุดเท่านั้น มันใช้ฟังก์ชั่น all ของ ECTO เพื่อดึงข้อมูลทั้งหมดจากฐานข้อมูล 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 ฟีนิกซ์รู้ว่าเรียกใช้ฟังก์ชันนี้เมื่อเซิร์ฟเวอร์ส่งข้อความภายใน
:after_joinผ่านกระบวนการช่องฟังก์ชั่น
join/3ของเราในlib/chat_web/channels/room_channel.exส่งสิ่งนั้น:after_join messageไปยังกระบวนการช่องทางเมื่อลูกค้าเชื่อมต่อกับหัวข้อ"room:lobby"สำเร็จ
เริ่มเซิร์ฟเวอร์ฟีนิกซ์ ( หากยังไม่ทำงาน ):
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 !
เปิด แอพแชทเว็บใน สองเบราว์ เซอร์ Windows : http: // localhost: 4000
( หากเครื่องของคุณมีเบราว์เซอร์เพียงอันเดียวเท่านั้นลองใช้แท็บ "Incognito" หนึ่งแท็บ )
คุณควรจะสามารถส่งข้อความระหว่างหน้าต่างเบราว์เซอร์ทั้งสอง: 
ยินดีด้วย! คุณมีแอพแชท ที่ใช้งานได้ ( พื้นฐาน ) ที่เขียนในฟีนิกซ์!
ประวัติการแชท (ข้อความ) ถูก บันทึกไว้ !
ซึ่งหมายความว่าคุณสามารถ รีเฟรช เบราว์เซอร์ หรือ เข้าร่วมในเบราว์เซอร์อื่นและคุณจะยังคงเห็นประวัติ!
การทดสอบอัตโนมัติเป็นหนึ่งในวิธี ที่ดีที่สุด ในการรับรอง ความน่าเชื่อถือ ในเว็บแอปพลิเคชันของคุณ
หมายเหตุ : หากคุณยังใหม่กับการทดสอบอัตโนมัติหรือ "การพัฒนาแบบทดสอบขับเคลื่อน" ("TDD") เราขอแนะนำให้อ่าน/ติดตามบทช่วยสอน "พื้นฐาน": github.com/dwyl/ learn-tdd
การทดสอบในฟีนิกซ์นั้นรวดเร็ว ( การทดสอบทำงานแบบขนาน! ) และเริ่มต้นได้ง่าย! เฟรมเวิร์กการทดสอบ ExUnit นั้นมี อยู่ในตัว ดังนั้นจึงไม่มี "การตัดสินใจ/การอภิปราย" เกี่ยวกับกรอบหรือสไตล์ที่จะใช้
หากคุณไม่เคยเห็นหรือเขียนการทดสอบด้วย ExUnit อย่ากลัวไวยากรณ์ควร คุ้นเคย ถ้าคุณได้เขียนการทดสอบอัตโนมัติประเภท ใด ในอดีต
เมื่อใดก็ตามที่คุณสร้างแอพฟีนิกซ์ใหม่หรือเพิ่มคุณสมบัติใหม่ ( เช่นช่อง ) ฟีนิกซ์ จะสร้าง การทดสอบใหม่ให้คุณ
เรา เรียกใช้ การทดสอบโดยใช้คำสั่ง 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 การทดสอบได้รับ socket จากฟังก์ชั่น setup ( ในบรรทัดที่ 6 ของไฟล์ ) และกำหนดผลลัพธ์ของการเรียกใช้ฟังก์ชัน push ให้กับตัวแปร ref push เพียง กด ข้อความ ( แผนที่ %{"hello" => "there"} ) บน socket ไปยัง หัวข้อ "ping"
ประโยคฟังก์ชั่น handle_in ซึ่งจัดการหัวข้อ "ping" :
def handle_in ( "ping" , payload , socket ) do
{ :reply , { :ok , payload } , socket }
end เพียงแค่ ตอบกลับ ด้วยน้ำหนักบรรทุกที่คุณส่งดังนั้นใน การทดสอบ ของเราเราสามารถใช้ macro assert_reply เพื่อยืนยันว่าการ ref เท่ากับ :ok, %{"hello" => "there"}
หมายเหตุ : หากคุณมีคำถามหรือต้องการความช่วยเหลือ ใด ๆ ในการทำความเข้าใจกับการทดสอบอื่น ๆ โปรดเปิดปัญหาเกี่ยวกับ GitHub เรายินดีที่จะขยายสิ่งนี้ต่อไป!
( เราแค่พยายามรักษาบทช่วยสอนนี้ไว้อย่างสมเหตุสมผล "สั้น ๆ " ดังนั้นผู้เริ่มต้นจึงไม่ได้ "ครอบงำ" โดยอะไร ... )
บ่อยครั้งที่ เราสามารถเรียนรู้ มากมาย เกี่ยวกับแอปพลิเคชัน ( หรือ API ) จากการอ่านการทดสอบและดูว่า "ช่องว่าง" ในการทดสอบอยู่ที่ไหน
โชคดีที่ เราสามารถบรรลุเป้าหมายนี้ได้เพียงไม่กี่ขั้นตอน:
excoveralls เป็นการพึ่งพาการพัฒนา (การพัฒนา) เพื่อ mix.exs เปิดไฟล์ 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%
----------------
อย่างที่เราสามารถทำได้ที่นี่มีเพียง 80% ของบรรทัดของรหัสใน /lib เท่านั้นที่ "ครอบคลุม" โดยการทดสอบที่เราเขียน
หากต้องการ ดู ความครอบคลุมในเว็บเบราว์เซอร์ที่เรียกใช้ดังต่อไปนี้:
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/ การเริ่มต้น
เมื่อคุณ ปรับใช้แล้ว คุณจะสามารถดู/ใช้แอพของคุณในเบราว์เซอร์เว็บ/มือถือใด ๆ
เช่น: Phoenix-Chat .fly.dev/

หากคุณพบว่าตัวอย่างนี้มีประโยชน์โปรด rep ที่เก็บ GitHub เพื่อให้เรา ( และอื่น ๆ ) รู้ว่าคุณชอบมัน!
หากคุณต้องการเรียนรู้เพิ่มเติมเกี่ยวกับฟีนิกซ์และความมหัศจรรย์ของ LiveView ให้พิจารณาอ่านบทช่วยสอนเริ่มต้นของเรา: 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 ได้รับการอัปเดตล่าสุดเมื่อวันที่ 20 กุมภาพันธ์ 2018 และใช้ Phoenix 1.3 ดู: ปัญหา/40
มีความแตกต่างค่อนข้างน้อย (การเปลี่ยนแปลงที่ทำลาย) ระหว่างฟีนิกซ์ 1.3 และ 1.6 ( เวอร์ชันล่าสุด )
บทช่วยสอนของเราใช้ฟีนิกซ์ 1.6.2 (ล่าสุดเมื่อเดือนตุลาคม 2564) ความหวังของเราคือโดยการเขียน ( และการบำรุงรักษา ) การสอนที่เน้นการเรียนรู้แบบทีละขั้นตอนเรามีส่วนร่วมในชุมชนยาอายุวัฒนะ/ฟีนิกซ์โดยไม่ต้องซ้อน PRS ใน repo ของคริส