🔁 ทวนก่อนเริ่ม — บทนี้ใช้คำพวกนี้ตลอด ถ้าข้อไหนไม่คุ้นให้กลับไปบทนั้นก่อน
- route = บรรทัดใน
config/routes.rbที่บอกว่า URL ไหนไปหาใคร — พนักงานต้อนรับหน้าร้าน (บท 101) - controller · action = class ที่รับงาน กับ method ในนั้น — หัวหน้าเชฟของแผนก กับเมนูแต่ละอย่าง (บท 102)
@ตัวแปร= ของที่ controller วางไว้ให้ view หยิบไปใช้ (บท 102)- view ·
<%= %>= ไฟล์ HTML ที่แทรกค่าจาก Ruby ได้ — การจัดจานก่อนเสิร์ฟ (บท 103) - params = ของที่ผู้ใช้ส่งมากับ URL เช่น
?name=Nan— รายละเอียดในใบสั่ง (บท 102)
🍜 แอปตัวอย่าง: wongnok_blog คือแอปบล็อก (เว็บเขียนบทความ) แบบในสไลด์ ·
ของหลักคือบทความ — ภาษาอังกฤษ post (Post = บทความหนึ่งชิ้น · posts = หลายชิ้น)
· วันนี้เรายังไม่แตะบทความ เพราะต้องมี database ก่อน (รอ Day 2) — ทำแค่หน้าทักทายกับหน้าแนะนำร้าน
🧰 เวอร์ชันที่ใช้ตรวจ: Ruby 4.0.9 · Rails 8.1.4 · PostgreSQL 17 (สไลด์ใช้ Ruby 4.0.6 · Rails 8.1.3 — เรื่องในบทนี้ไม่ต่างกัน)
ผลรันทุกก้อนในบทนี้รันจริงแล้วก็อปมา · ค่าที่เปลี่ยนทุกครั้ง เช่น PID เวลา และรหัส commit
ของคุณจะไม่ตรงกับของผม — เป็นเรื่องปกติ · path ยาว ๆ ของโฟลเดอร์ทดลองในเครื่องผมย่อเป็น …
แผนที่ของ Lab: ใบสั่งหนึ่งใบเดินผ่านสามคน
ทุกหน้าเว็บที่เราจะทำวันนี้ ใช้ไฟล์ 3 ไฟล์เสมอ: route · controller · view
พอผู้ใช้เปิด /hello ใบสั่งจะเดินผ่านสามไฟล์นี้ตามลำดับ แล้วได้ HTML กลับไป
สังเกตว่าสามไฟล์นี้ไม่ได้เรียกกันด้วยโค้ด — มันเชื่อมกันด้วยชื่อล้วน ๆ
คำว่า greetings ใน route ทำให้ Rails ไปหา class GreetingsController
ในไฟล์ greetings_controller.rb · คำว่า index ทำให้ Rails ไปหา view ชื่อ index.html.erb
ในโฟลเดอร์ greetings/ (กฎตั้งชื่อชุดนี้อยู่ใน บท 99)
greetings แปลว่า "คำทักทาย" — เป็นชื่อแผนกที่เราตั้งเอง (ใช้รูปพหูพจน์ตามกฎของ Rails) ไม่ใช่คำพิเศษ
ใน Go ที่เคยเรียน คุณส่งตัวฟังก์ชันเข้าไปตรง ๆ:
// Go + Gin — ส่งตัวฟังก์ชัน helloHandler เข้าไปเลย
r.GET("/hello", helloHandler)
ถ้าพิมพ์ helloHandler ผิด Go ไม่ยอม build ตั้งแต่ก่อนรัน
· แต่ใน Rails สิ่งที่ส่งคือข้อความ "greetings#index" — สะกดผิดก็ไม่มีใครฟ้อง
จนกว่าจะมีคนเปิดหน้านั้นจริง ๆ · นั่นคือเหตุผลที่ครึ่งหลังของ Lab ให้ฝึกอ่านหน้า error
พนักงานต้อนรับ (route) ไม่ได้เดินพาใบสั่งไปส่งเชฟด้วยมือ
เขาแค่เขียนชื่อแผนกกับชื่อเมนูลงใบสั่ง แล้ววางไว้
ร้านแฟรนไชส์มีกฎว่าแผนก "greetings" ต้องนั่งที่โต๊ะป้าย greetings_controller.rb
และเมนู "index" ต้องจัดจานตามแบบในแฟ้ม greetings/index.html.erb
ทุกคนทำตามกฎตำแหน่งเดียวกัน ใบสั่งจึงถึงมือคนที่ถูกเสมอ — โดยไม่มีใครต้องบอกทาง
ที่ไม่เหมือนกัน: พนักงานจริงเห็นใบสั่งเขียนว่า "แผนก page" (ขาด s)
ก็คงเดาออกว่าหมายถึง "pages" แล้วส่งให้ถูกแผนก
Rails ไม่เดาให้ — ชื่อต้องตรงเป๊ะทุกตัวอักษร ไม่งั้นได้หน้าแดงทันที
(เราจะทำพังแบบนี้ให้ดูในหัวข้อ "พังแบบที่ 1")
- convention over configuration
- แนวคิดของ Rails ที่ว่า "ตั้งชื่อตามกฎ แล้วไม่ต้องเขียนบอกว่าอะไรอยู่ที่ไหน" · ถาม senior ได้ว่า "ตรงนี้ Rails หาไฟล์เจอได้ยังไง ใช้ convention ข้อไหน" — คำถามนี้ช่วยให้เข้าใจโค้ดที่ดูเหมือน "เกิดขึ้นเอง"
"controller#action"- รูปแบบที่ route ใช้ชี้ไปหางาน · ซ้ายของ
#คือชื่อ controller (ตัวเล็ก พหูพจน์ ไม่มีคำว่า Controller) · ขวาคือชื่อ method · พิมพ์บอก AI ได้ว่า "add a route GET /about to pages#about" AI จะเข้าใจทันทีว่าต้องสร้างไฟล์อะไรบ้าง
ขั้น 1 — สร้างร้าน: rails new แล้ว commit เอง
เปิด terminal ไปที่โฟลเดอร์ที่อยากเก็บงาน แล้วพิมพ์คำสั่งเดียวกับสไลด์ · คำสั่งนี้คือ "ขอครัวแฟรนไชส์สำเร็จรูปหนึ่งชุด"
$ rails new wongnok_blog --database=postgresql --css=tailwind
create
create README.md
create Rakefile
create .ruby-version
create config.ru
create .gitignore
create .gitattributes
create Gemfile
run git init -b main from "."
Initialized empty Git repository in …/wongnok_blog/.git/
… (อีกราว 170 บรรทัด) …
Compile initial Tailwind build
run rails tailwindcss:build from "."
≈ tailwindcss v4.3.3
Done in 47ms
…
force config/cable.yml
wongnok_blog— ชื่อแอป (และชื่อโฟลเดอร์ที่ได้)--database=postgresql— ใช้ PostgreSQL เป็นตู้เก็บแฟ้ม (ถ้าไม่ใส่ Rails จะใช้ SQLite ซึ่งเป็น database แบบไฟล์เดียว)--css=tailwind— ติดตั้ง Tailwind (ชุดคลาส CSS สำเร็จรูป) ไว้แต่งหน้าเว็บ · วันนี้ยังไม่ได้ใช้- บรรทัด
create …คือไฟล์ที่ Rails สร้างให้ · บรรทัดrun …คือคำสั่งที่มันรันให้เอง - บรรทัดอื่น ๆ เช่น
tailwindcss v4.3.3หรือforce …— ไม่ต้องอ่าน แค่ดูว่าจบโดยไม่มีคำว่าerror
เครื่องผมใช้เวลาราว 8 วินาที เพราะ gem (ของสำเร็จรูปที่แอปต้องใช้) ลงไว้ครบแล้ว — ครั้งแรกของคุณอาจนานเป็นนาที
ดูบรรทัด git init — Rails สร้าง git repo (ที่เก็บประวัติโค้ด) ให้ แต่ยังไม่ commit:
$ cd wongnok_blog
$ git log --oneline
fatal: your current branch 'main' does not have any commits yet
ให้ commit เองทันที ก่อนแตะอะไร — เหมือนถ่ายรูปครัวไว้ก่อนรื้อ พอทำพังในครึ่งหลังจะได้รู้ว่าตัวเองแก้อะไรไป และย้อนกลับได้:
$ git add . && git commit -m "rails new"
$ git log --oneline
7a44a5e rails new
git add . = เลือกทุกไฟล์ · && = ถ้าคำสั่งแรกสำเร็จ ค่อยทำคำสั่งถัดไป ·
git commit -m "…" = บันทึกพร้อมข้อความ · รหัส 7a44a5e ของคุณจะต่างไป
ขั้น 2 — เปิดร้าน: เตรียม database แล้ว bin/rails server
วันนี้เรายังไม่เก็บบทความลง database เลย แต่ Rails ในโหมดพัฒนาต้องคุยกับ database ตอนมีคนเข้าเว็บครั้งแรก (มันเช็คว่าตู้เก็บแฟ้มพร้อมไหม) · ถ้าไม่มีตู้ หน้าเว็บจะพังตั้งแต่หน้าแรก
ต้องมี PostgreSQL เปิดอยู่ในเครื่องก่อน (ยังไม่มี → เปิด container แบบบท 31 ตามที่บท 98แนะนำ) · แล้วบอกแอปว่าตู้เก็บแฟ้มอยู่ที่ไหน ด้วยวิธีเดียวกับบท 98 จากนั้นสั่งสร้าง database:
$ export DATABASE_URL=postgres://postgres:sandbox@127.0.0.1:55432/a105
$ bin/rails db:prepare
Created database 'a105'
export ชื่อ=ค่า— ให้ terminal แท็บนี้จำค่านี้ไว้ โปรแกรมที่รันจากแท็บนี้จะอ่านได้ (แท็บอื่นไม่รู้)DATABASE_URLคือที่อยู่ของตู้เก็บแฟ้ม — หน้าตาเหมือน DSN (สตริงบอกที่อยู่ database) ที่เคยใช้กับ goose ในบท 31 อ่านทีละท่อน:postgres://ชนิด database ·postgres:sandboxชื่อผู้ใช้:รหัสผ่าน ·127.0.0.1:55432เครื่อง:พอร์ต ·/a105ชื่อ database- พอร์ต 55432 รหัส
sandboxและชื่อa105เป็นของเครื่องตรวจ — ของคุณใส่ตาม PostgreSQL ที่คุณเปิดไว้ (ส่วนใหญ่พอร์ตคือ 5432) bin/rails db:prepare= "ถ้ายังไม่มี database ให้สร้าง" · ได้Created database '…'คือผ่าน
เปิดร้าน (พิมพ์ในโฟลเดอร์ wongnok_blog):
$ bin/rails server
=> Booting Puma
=> Rails 8.1.4 application starting in development
=> Run `bin/rails server --help` for more startup options
Puma starting in single mode...
* Puma version: 8.0.2 ("Into the Arena")
* Ruby version: ruby 4.0.9 (2026-10-23 revision 3e93b23afc) +PRISM [arm64-darwin25]
* Min threads: 3
* Max threads: 3
* Environment: development
* PID: 15190
* Listening on http://127.0.0.1:3000
* Listening on http://[::1]:3000
Use Ctrl-C to stop
Puma= โปรแกรมที่รับ request จริง ๆ (ประตูหน้าร้าน) · Rails ใช้ตัวนี้เป็นค่าเริ่มต้นdevelopment= โหมดพัฒนา — แก้ไฟล์แล้วเห็นผลทันทีโดยไม่ต้องปิดเปิดร้าน และหน้า error จะบอกละเอียดPID: 15190= เลขประจำตัวของโปรแกรมนี้ในเครื่อง · ของคุณจะเป็นเลขอื่นListening on http://127.0.0.1:3000= ร้านเปิดแล้วที่ประตูหมายเลข 3000Use Ctrl-C to stop= จะปิดร้านให้กด Ctrl กับ C พร้อมกันใน terminal นี้- บรรทัดที่เหลือ (
threads·Puma version·[::1]) — ไม่ต้องสนใจ ตอนนี้
terminal นี้จะ "ค้าง" อยู่แบบนี้ตลอด — ไม่ใช่เสีย มันกำลังรอลูกค้า และจะพิมพ์บันทึกทุกใบสั่งที่เข้ามา
· เปิด terminal แท็บใหม่ไว้พิมพ์คำสั่งอื่น · แล้วเปิด http://localhost:3000 ในเบราว์เซอร์
ต้องเห็นหน้าต้อนรับของ Rails ที่มีโลโก้ตัวใหญ่
ActiveRecord::NoDatabaseError
แปลว่าข้าม bin/rails db:prepare ไป หรือ DATABASE_URL ใน terminal ที่เปิด server ไม่ได้ตั้งไว้
· ผมลองเปิด server ก่อนสร้าง database แล้วเปิดหน้าแรก ได้ status 500 และ terminal ของ server ขึ้นข้อความนี้:
ActiveRecord::NoDatabaseError (Database not found: a105. Available database configurations can be found in config/database.yml.
To resolve this error:
- Create the database by running:
bin/rails db:create
- Verify that config/database.yml contains the correct database name.
)
ทางแก้: กด Ctrl-C ปิด server → bin/rails db:prepare → เปิด server ใหม่
· export มีผลแค่ใน terminal แท็บที่พิมพ์ — แท็บใหม่ต้องพิมพ์ซ้ำ
ขั้น 3 — เมนูแรก /hello (ทำไปพร้อมกัน)
ก่อนเขียนอะไร ลองเปิด http://localhost:3000/hello ดูก่อน — จะได้หน้าแดงหน้าแรกของวัน:
Routing Error
No route matches [GET] "/hello"
แปลตรง ๆ: "ไม่มี route ไหนรับ GET /hello" — พนักงานต้อนรับไม่มีกฎสำหรับใบสั่งนี้
· ไม่ต้องตกใจ นี่คือสิ่งที่ควรเห็น เพราะเรายังไม่ได้เขียน route · status ที่ได้คือ 404 (หาไม่เจอ)
3.1 · route: บอกพนักงานต้อนรับ
เปิด config/routes.rb แล้วเพิ่มบรรทัดเดียวก่อน end บรรทัดสุดท้าย:
# config/routes.rb
Rails.application.routes.draw do
# … บรรทัดเดิมที่ Rails เขียนไว้ ปล่อยไว้ตามเดิม …
get "/hello", to: "greetings#index"
end
Rails.application.routes.draw do…end— Rails เขียนไว้ให้แล้ว แปลว่า "เริ่มเขียนกฎของพนักงานต้อนรับ … จบ" ·do…endคือ block (ก้อนโค้ดที่ส่งให้ method — บท 93) · ไม่ต้องแก้ แค่เขียนของเราไว้ข้างในget— รับเฉพาะการ "ขอดูหน้า" (HTTP GET) · มันคือ method ของ Ruby ตัวหนึ่ง ไม่ใช่คำพิเศษ"/hello"— URL ที่จะรับto: "greetings#index"— "ส่งให้ controller ชื่อ greetings ทำเมนู index" ·to:คือ key ของ hash ที่ละปีกกาไว้ (บท 97)
3.2 · controller: หัวหน้าเชฟแผนก greetings
สร้างไฟล์ใหม่ app/controllers/greetings_controller.rb:
# app/controllers/greetings_controller.rb
class GreetingsController < ApplicationController
def index
@name = "PEA"
end
end
class GreetingsController— ชื่อต้องตรงกับไฟล์:greetings_controller.rb↔GreetingsController< ApplicationController— "สืบทอดความสามารถจาก" controller แม่ที่ Rails เตรียมไว้ (บท 95) · ได้params·renderและอื่น ๆ มาฟรีdef index— action ชื่อ index ต้องตรงกับขวาของ#ใน route@name = "PEA"— ตัวแปรที่มี@นำหน้า view จะหยิบไปใช้ได้ · ตัวแปรธรรมดา (ไม่มี@) view มองไม่เห็นendสองตัว — ตัวแรกปิดdefตัวที่สองปิดclass· ลืมตัวใดตัวหนึ่ง = พัง (พังแบบที่ 3)
ถ้าเทียบกับ Go มันคือ handler หนึ่งตัว · แต่ใน Gin ท้าย handler คุณต้องสั่ง c.HTML(…) เอง
พร้อมบอกชื่อไฟล์ template กับข้อมูลที่จะส่งไป
ใน Rails ไม่ต้องสั่ง — พอ method จบ Rails ไปหา view ที่ชื่อตรงกับ action ให้เอง และ view เห็น @ตัวแปร ทุกตัวเอง
3.3 · view: คนจัดจาน
สร้างโฟลเดอร์ app/views/greetings/ แล้วสร้างไฟล์ index.html.erb ในนั้น ใส่สองบรรทัดนี้:
<h1>Hello <%= @name %></h1>
<p>หน้านี้มาจาก GreetingsController#index</p>
<%= @name %>— "เอาค่าของ@nameมาวางตรงนี้" · ได้PEA- ชื่อไฟล์อ่านว่า: index (ชื่อ action) . html (ส่งเป็น HTML) . erb (มี Ruby ปน)
ไม่ต้องปิดเปิด server ใหม่ — โหมด development โหลดไฟล์ใหม่ให้เอง · กลับไปกด refresh หน้า /hello
· ผมดึง HTML ด้วย curl (โปรแกรมเปิดเว็บใน terminal) แล้วตัดมาเฉพาะส่วนเนื้อหา:
$ curl -s http://localhost:3000/hello
… ส่วนหัว <head> ของ layout …
<main class="container mx-auto mt-28 px-5 flex">
<!-- BEGIN app/views/greetings/index.html.erb
--><h1>Hello PEA</h1>
<p>หน้านี้มาจาก GreetingsController#index</p>
<!-- END app/views/greetings/index.html.erb -->
</main>
<main …>ข้างนอกไม่ได้มาจากเรา — มาจาก layout (จานรองแบบเดียวกันทุกจาน) ที่rails newสร้างให้ (บท 103) · คำยาว ๆ ในclass="…"เป็นคลาสของ Tailwind ไว้จัดหน้า ไม่ต้องสนใจ- comment
BEGIN app/views/…Rails แปะให้เองในโหมด development — กด View Source ในเบราว์เซอร์แล้วรู้ทันทีว่าส่วนนี้มาจากไฟล์ไหน
แล้วดู terminal ที่เปิด server ไว้ — จะเห็นบันทึกว่าใบสั่งเดินผ่านใครบ้าง:
Started GET "/hello" for ::1 at 2026-11-03 21:15:59 +0700
Processing by GreetingsController#index as */*
Rendering layout layouts/application.html.erb
Rendering greetings/index.html.erb within layouts/application
…
Completed 200 OK in 18ms (Views: 6.6ms | …)
Started GET "/hello"— ใบสั่งGET /helloเข้ามา ·for ::1= มาจากเครื่องตัวเอง (localhost) · ตามด้วยเวลาProcessing by GreetingsController#index— ส่งให้แผนก greetings เมนู index ·as */*= ผู้ขอไม่ได้ระบุว่าอยากได้แบบไหน (curl เป็นแบบนี้ · เบราว์เซอร์จะเป็นas HTML)Rendering …— จัดจานด้วยgreetings/index.html.erbวางบน layoutCompleted 200 OK in 18ms— เสิร์ฟสำเร็จ ใช้เวลา 18 มิลลิวินาที (ตัวเลขเวลาของคุณจะต่าง)
ทุกครั้งที่งง ให้ดู terminal นี้ก่อน — มันบอกเส้นทางจริงที่ใบสั่งเดินผ่าน ไม่ใช่เส้นทางที่เราคิดว่าเขียนไว้
terminal ที่เปิด server คือสมุดบันทึกที่แขวนไว้หน้าครัว ใบสั่งทุกใบที่เข้ามาถูกจดลงไป:
มาจากใคร ส่งให้แผนกไหน เมนูอะไร จัดจานด้วยแบบไหน เสิร์ฟทันไหม
ถ้าลูกค้าบอกว่า "ได้จานผิด" ให้เปิดสมุดนี้ก่อน — จะเห็นทันทีว่าใบสั่งไปผิดแผนกตั้งแต่ต้น หรือไปถูกแต่จัดจานพัง
ที่ไม่เหมือนกัน: สมุดจริงมีคนเลือกจดแค่เรื่องสำคัญ แต่ Rails จดทุกใบอัตโนมัติ
ไม่ว่าสำคัญหรือไม่ · เปิดเว็บไปสักพักสมุดจะยาวมาก ให้มองหาบรรทัด Started ของ URL ที่เราสนใจ แล้วอ่านลงมา
ฝั่ง Go ก็มีของคล้ายกัน: ตอนใช้ gin.Default() Gin พิมพ์บรรทัด [GIN] หนึ่งบรรทัดต่อหนึ่ง request
· ของ Rails ละเอียดกว่า เพราะบอกด้วยว่าไปถึง controller ไหน และใช้ view ไฟล์ไหน
- server log (development log)
- ข้อความที่ server พิมพ์ทุกครั้งที่มี request เข้ามา (Rails เขียนลงไฟล์
log/development.logด้วย) · เวลาขอความช่วยเหลือ ส่งบรรทัดตั้งแต่Started …จนถึงCompleted …ของ request นั้นไปทั้งก้อน — พิมพ์บอก AI ได้ว่า "here is my Rails server log for GET /about, why does it render the wrong template?"
ภารกิจ: เมนูที่สอง /about — ทำเอง
ตอนนี้คุณทำครบ loop มาแล้วหนึ่งรอบ · รอบนี้ทำเองทั้งหมด แล้วเพิ่มการรับชื่อจาก URL (ข้อ 2–3 ของ Lab ในสไลด์)
🎯 ภารกิจ Lab 1 · หน้า /about (~10 นาที)
- เพิ่ม route: URL
/aboutให้ไปที่ controllerpagesactionabout - สร้างไฟล์ controller ที่ชื่อตรงกฎ · ใน action ให้ตั้ง
@nameเป็นparams[:name]ถ้ามี ถ้าไม่มีให้เป็น"PEA"(ใช้||แบบ บท 94) - สร้างไฟล์ view ที่ชื่อตรงกฎ · มีหัวข้อ
About wongnok_blogและบรรทัดHelloตามด้วยชื่อ - เปิดสามแบบ แล้วจดว่าเห็นอะไร:
/about·/about?name=Nan·/about?name=(มี=แต่ไม่มีค่า) - ทำได้แล้ว commit:
git add . && git commit -m "add /hello and /about"
ควรเห็น: /about → Hello PEA · /about?name=Nan → Hello Nan
ถ้าเจอ error แปลว่า…
Routing Error·No route matches [GET] "/about"→ ยังไม่ได้เพิ่ม route หรือพิมพ์ URL ใน route ผิดuninitialized constant PageController→ ใน route เขียนpageขาด s (ดูพังแบบที่ 1)No view template for interactive request→ ชื่อไฟล์หรือโฟลเดอร์ view ไม่ตรง (ดูพังแบบที่ 2)
① route — เพิ่มใน config/routes.rb ใต้บรรทัด /hello:
get "/about", to: "pages#about"
② controller — ไฟล์ใหม่ app/controllers/pages_controller.rb:
class PagesController < ApplicationController
def about
@name = params[:name] || "PEA"
end
end
③ view — ไฟล์ใหม่ app/views/pages/about.html.erb (สร้างโฟลเดอร์ pages/ ก่อน):
<h1>About wongnok_blog</h1>
<p>Hello <%= @name %></p>
params[:name]— หยิบค่าnameจาก URL ·:nameคือ symbol (ป้ายชื่อ บท 92)|| "PEA"— "ถ้าทางซ้ายไม่มีค่า (nil) ให้ใช้ทางขวา"- ชื่อ
pagesเป็นพหูพจน์ → classPagesController→ ไฟล์pages_controller.rb→ โฟลเดอร์ viewpages/
ผลจริงจาก curl (ตัดมาเฉพาะสองบรรทัดที่เราเขียน):
$ curl -s http://localhost:3000/about
<h1>About wongnok_blog</h1>
<p>Hello PEA</p>
$ curl -s "http://localhost:3000/about?name=Nan"
<h1>About wongnok_blog</h1>
<p>Hello Nan</p>
$ curl -s "http://localhost:3000/about?name="
<h1>About wongnok_blog</h1>
<p>Hello </p>
แบบที่สามได้ Hello เฉย ๆ ไม่ใช่ Hello PEA — เพราะ ?name= ส่งมาเป็น
ข้อความว่าง "" ไม่ใช่ nil · และใน Ruby "" นับเป็น "จริง"
|| เลยไม่หยิบ "PEA" (บท 94)
ถ้าอยากให้ค่าว่างได้ PEA ด้วย ใช้ presence ของ Active Support
(ของที่ Rails เติมให้ Ruby — แปลว่า "ถ้าว่างให้ถือว่าไม่มี"):
@name = params[:name].presence || "PEA"
# ผลจริงหลังแก้
/about <p>Hello PEA</p>
/about?name=Nan <p>Hello Nan</p>
/about?name= <p>Hello PEA</p>
commit แล้วดูประวัติ (รหัสของคุณจะต่างไป):
$ git log --oneline
f6575cd add /hello and /about
7a44a5e rails new
ทำพังโดยตั้งใจ — อ่าน "ใบแจ้งปัญหาของครัว"
เวลาโค้ดพังในโหมด development Rails จะไม่ส่งหน้าว่าง ๆ มาให้ แต่ส่งหน้าสีแดงที่บอกรายละเอียด
ว่าพังเพราะอะไร ที่ไฟล์ไหน บรรทัดไหน
มือใหม่มักเห็นตัวหนังสือเยอะแล้วปิดทิ้ง · จริง ๆ ส่วนที่ต้องอ่านมีแค่ 3 จุดบนสุด
นี่คือหน้าที่ได้จาก "พังแบบที่ 4" ข้างล่าง (พิมพ์ params ผิดเป็น parms) — เขียนตามที่เห็นบนจอจริง
ติดเลขไว้ทีละส่วน:
วิธีอ่าน 3 จังหวะ (ใช้ได้กับทุกหน้าแดง):
- ① ชื่อปัญหา + เกิดที่ไหน —
NameErrorคือชนิดของ error ·in PagesController#aboutคือแผนกกับเมนูที่กำลังทำงานตอนพัง - ② ประโยคเต็ม — อ่านเป็นภาษาคน: "ไม่รู้จักตัวแปรหรือ method ชื่อ
parmsใน PagesController" · ถ้ามี ③ Did you mean? ให้ดูด้วย มักเฉลยให้เลย — ดูแค่ชื่อแรก (params) พอ สองชื่อล่างเป็นของข้างในที่ไม่ต้องสนใจ - ④⑤ หาไฟล์ของเรา — มองหา path ที่ขึ้นต้นด้วย
app/กับเลขหลัง:(ที่นี่คือpages_controller.rb:3= บรรทัด 3) · ถ้าไม่มีไฟล์ของเราเลย แปลว่าปัญหาไม่ได้อยู่ในโค้ดของ method แต่อยู่ที่ชื่อ (route · ชื่อไฟล์ · ชื่อ class)
ส่วน ⑥ Request คือสำเนาใบสั่ง — ดูได้ว่า params ที่ส่งมามีอะไร (ครั้งนี้ไม่มี จึงเป็น None)
· ส่วน Framework Trace ยาว ๆ คือโค้ดข้างในของ Rails เอง มือใหม่ข้ามได้เลย
หน้าแดงคือใบแจ้งปัญหาที่ครัวส่งออกมาแทนจานอาหาร:
หัวกระดาษ (①) บอกว่าปัญหาประเภทไหน เกิดที่แผนกไหน เมนูอะไร ·
ประโยคตัวแดง (②) บอกว่าขาดอะไร · Did you mean? (③) คือเชฟเขียนโน้ตว่า "หรือหมายถึงอันนี้?" ·
กรอบโค้ด (④) คือรูปถ่ายจุดเกิดเหตุ · trace (⑤) คือเส้นทางที่ใบสั่งเดินผ่านมา ·
Request (⑥) คือสำเนาใบสั่งของลูกค้า
ที่ไม่เหมือนกัน: ใบแจ้งที่คนเขียนจะเลือกเขียนแค่เรื่องที่สำคัญ
แต่ Rails แนบทุกอย่างมาหมด รวมถึงโค้ดข้างในของ Rails เอง —
บางครั้งรูปถ่ายจุดเกิดเหตุ (④) เป็นโค้ดของ Rails ไม่ใช่ของเรา (เห็นในพังแบบที่ 1)
และใบแจ้งนี้ส่งถึงลูกค้า (เบราว์เซอร์) ไม่ใช่ถึงเจ้าของร้าน —
terminal ที่เปิด server ก็มีสำเนาเดียวกันอยู่ บางเรื่องอ่านใน terminal ง่ายกว่า (เห็นในพังแบบที่ 3)
ก่อนเริ่มทำพัง: ทุกแบบให้แก้กลับจนหน้า /about ขึ้นปกติก่อนลองแบบถัดไป
· ถ้าจำไม่ได้ว่าแก้อะไรไป ใช้ git ที่ commit ไว้:
$ git diff # ดูว่าแก้อะไรไปจาก commit ล่าสุด
diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb
…
- @name = params[:name] || "PEA"
+ @name = parms[:name] || "PEA"
…
$ git restore app/controllers/pages_controller.rb # ย้อนไฟล์นี้กลับไปเหมือนตอน commit
$ git status
On branch main
nothing to commit, working tree clean
บรรทัดที่ขึ้นต้นด้วย - คือของเดิม · + คือของใหม่ที่เราแก้
พังแบบที่ 1 · route ชี้ไปหา controller ที่ไม่มี
ใน config/routes.rb ลบ s ออกหนึ่งตัว:
get "/about", to: "page#about" # ← page ขาด s
เปิด /about ได้ status 500 และหน้านี้:
ActionDispatch::MissingController in PageController#about
uninitialized constant PageController
Extracted source (around line #101):
rescue NameError => error
if error.missing_name == const_name || const_name.start_with?("#{error.missing_name}::")
raise MissingController.new(error.message, error.name)
…
MissingController= "หา controller ไม่เจอ"uninitialized constant PageController= "ไม่รู้จักชื่อPageController" · constant คือชื่อที่ขึ้นต้นด้วยตัวใหญ่ ชื่อ class ก็เป็น constant- เบาะแสอยู่ที่ชื่อ: Rails เอาคำว่า
pageจาก route มาต่อเป็นPageController(ไม่มี s) — ผิดตั้งแต่ใบสั่ง - กรอบโค้ดเป็นโค้ดของ Rails (ไม่ใช่ไฟล์ใน
app/) → ตามจังหวะที่ 3: ปัญหาอยู่ที่ชื่อ ไม่ใช่โค้ดใน method
แก้: ใส่ s กลับเป็น "pages#about"
พังแบบที่ 2 · ชื่อไฟล์ view ผิด
เปลี่ยนชื่อไฟล์ app/views/pages/about.html.erb เป็น abuot.html.erb (สลับตัวอักษร) แล้วเปิด /about:
No view template for interactive request
PagesController#about is missing a template for request formats: text/html
NOTE: Rails usually expects a controller action to render a view template with the same name.
For example, a PagesController#about action defined in app/controllers/pages_controller.rb should have a corresponding view template
in a file named app/views/pages/about.html.erb.
…
- หัวข้อ: "ไม่มี view สำหรับหน้านี้" · interactive request = คำขอจากคนที่เปิดเบราว์เซอร์ดูอยู่
- ประโยคแดง: "
PagesController#aboutไม่มี template (ไฟล์ view) แบบ HTML" — เชฟทำเสร็จแล้ว แต่หาแบบจัดจานไม่เจอ - ย่อหน้า NOTE เฉลยชื่อไฟล์ที่ถูกให้เลย:
app/views/pages/about.html.erb— เอาไปเทียบกับชื่อไฟล์ของเราทีละตัวอักษร - ตัวนี้ทดสอบด้วยเบราว์เซอร์ (ได้ status 406) · ถ้าใช้
curlเฉย ๆ จะได้แค่หน้าว่าง ไม่มีข้อความให้อ่าน
แก้: เปลี่ยนชื่อไฟล์กลับเป็น about.html.erb
พังแบบที่ 3 · ลืม end
ใน pages_controller.rb ลบ end ของ def about ออก:
class PagesController < ApplicationController
def about
@name = params[:name] || "PEA"
end # ← เหลือ end ตัวเดียว
เปิด /about ได้ status 500 · ตัวนี้หัวกระดาษหลอกตา:
ArgumentError
Reported error must be an Exception, got: #<SyntaxError:"…/app/controllers/pages_controller.rb:4: syntax errors found\n> 1 | class PagesController < Application ...\n | ^~~~~ expected an `end` to close the `class` statement\n 2 | def about\n 3 | @name = params[:name] || \"PEA\"\n> 4 | end\n | ^ unexpected end-of-input, assuming it is closing the parent top level context\n">
หัวบอกว่า ArgumentError ซึ่งไม่ใช่ตัวปัญหาจริง — มันคือ error ที่เกิดซ้อนขึ้นตอน Rails พยายามรายงานปัญหาจริง
· ปัญหาจริงซ่อนอยู่ในประโยคแดง: หาคำว่า SyntaxError กับ syntax errors found
· และ \n ที่เห็นคือ "ขึ้นบรรทัดใหม่" ที่ถูกพิมพ์ติดกันจนอ่านยาก
ตรงนี้ terminal ที่เปิด server อ่านง่ายกว่ามาก — ข้อความเดียวกันแต่ขึ้นบรรทัดให้:
Caused by: SyntaxError (…/app/controllers/pages_controller.rb:4: syntax errors found
> 1 | class PagesController < Application ...
| ^~~~~ expected an `end` to close the `class` statement
2 | def about
3 | @name = params[:name] || "PEA"
> 4 | end
| ^ unexpected end-of-input, assuming it is closing the parent top level context
)
SyntaxError= เขียนผิดไวยากรณ์ Ruby อ่านไฟล์ไม่จบ (เหมือน Go ที่ build ไม่ผ่าน)pages_controller.rb:4= ไฟล์ไหน บรรทัดไหนexpected an `end` to close the `class` statement= "ขาดendของclass"unexpected end-of-input= "ไฟล์จบไปก่อนที่ Ruby จะเจอendครบ" · ท่อนหลังจากนั้นไม่ต้องสนใจ- แปลกไหม? เราลบ end ของ
defแต่ Ruby บอกว่าขาด end ของclass— เพราะ Ruby จับคู่endกับตัวเปิดที่ใกล้ที่สุด end ที่เหลือจึงไปปิดdefแล้วclassเลยไม่มีคู่ · บรรทัดที่ Ruby ชี้อาจไม่ใช่บรรทัดที่เราลืม ให้ไล่ดูคู่def/endทั้งไฟล์
อีกทางที่ไม่ต้องเปิดเว็บ: ให้ Ruby ตรวจไวยากรณ์ของไฟล์เดียว (-c = check)
$ ruby -c app/controllers/pages_controller.rb
ruby: app/controllers/pages_controller.rb:4: syntax errors found (SyntaxError)
> 1 | class PagesController < Application ...
| ^~~~~ expected an `end` to close the `class` statement
2 | def about
3 | @name = params[:name] || "PEA"
> 4 | end
| ^ unexpected end-of-input, assuming it is closing the parent top level context
# แก้แล้วรันอีกที
$ ruby -c app/controllers/pages_controller.rb
Syntax OK
แก้: ใส่ end กลับใต้บรรทัด @name = …
พังแบบที่ 4 · พิมพ์ชื่อตัวแปรผิด
ใน controller เปลี่ยน params เป็น parms — ได้หน้า NameError ที่เราแยกส่วนดูไปแล้วข้างบน (status 500)
· ตัวนี้อ่านง่ายที่สุด: กรอบโค้ดชี้บรรทัด 3 ในไฟล์ของเรา และ Did you mean? params เฉลยให้เลย
เทียบกับ Go: ถ้าพิมพ์ชื่อตัวแปรผิดแบบเดียวกัน Go ไม่ยอม build ตั้งแต่แรก
· ผมเขียนสองบรรทัดนี้ไว้ใน func main() ของไฟล์ main.go (มี package main กับ import "fmt" ครบ) แล้วสั่ง go build:
params := map[string]string{"name": "Nan"}
fmt.Println(parms["name"]) // พิมพ์ผิด: parms
$ go build main.go
# command-line-arguments
./main.go:6:2: declared and not used: params
./main.go:7:14: undefined: parms
บรรทัด # command-line-arguments คือ Go บอกว่ากำลัง build ไฟล์ที่เราสั่งตรง ๆ — ไม่ต้องสนใจ ·
error บรรทัดล่างคือตัวที่ตรงกับของเรา: undefined: parms = "ไม่รู้จักชื่อ parms" ·
บรรทัดบนเป็นโบนัสของ Go — มันเห็นว่า params ประกาศแล้วไม่ได้ใช้ (เพราะเราพิมพ์ผิดไปใช้ parms) ก็ฟ้องด้วย
Ruby ไม่มีขั้น build แบบนั้น — ไฟล์ที่พิมพ์ผิดยังโหลดได้ปกติ จะพังก็ต่อเมื่อบรรทัดนั้นถูกรันจริง · ทีนี้ลองผิดอีกที่หนึ่ง: แก้ controller กลับให้ถูก แล้วไปพิมพ์ผิดใน view แทน
ไฟล์ app/views/pages/about.html.erb — พิมพ์ @name ผิดเป็น @nmae (controller ถูกต้องแล้ว):
<h1>About wongnok_blog</h1>
<p>Hello <%= @nmae %></p>
เปิด /about?name=Nan — จะได้หน้าแดงแบบไหน?
ไม่มีหน้าแดงเลย — ได้ status 200 กับหน้าที่ชื่อหายไปเงียบ ๆ:
$ curl -s -H 'Accept: text/html' "http://localhost:3000/about?name=Nan"
…
<p>Hello </p>
(-H 'Accept: text/html' = ให้ curl บอกว่า "ขอเป็น HTML" เหมือนเบราว์เซอร์)
ตัวแปรที่มี @ นำหน้า ถ้าไม่เคยถูกตั้งค่า Ruby ไม่ฟ้อง แต่ให้ค่าเป็น nil (ไม่มีค่า)
และ <%= nil %> พิมพ์ออกมาเป็นความว่างเปล่า (บท 94)
· ต่างจาก parms ในพังแบบที่ 4 ที่ไม่มี @ — ชื่อแบบนั้นถ้าไม่รู้จัก Ruby ฟ้อง NameError ทันที
เทียบกับ Go: template ของ Go ที่อ้างชื่อ field ผิด ({{.Nmae}}) ฟ้อง — ผมลองแล้วได้
can't evaluate field Nmae in type main.Page
บทเรียน: ถ้าหน้าเว็บขึ้นปกติแต่ข้อมูลบางส่วนหายไปเฉย ๆ
ให้สงสัยชื่อ @ตัวแปร สะกดไม่ตรงกันระหว่าง controller กับ view ก่อนอย่างอื่น
เชฟ (controller) วางวัตถุดิบบนถาดที่ติดป้าย @name
· คนจัดจาน (view) เดินไปหยิบถาดป้าย @nmae — ไม่มีถาดนี้ ก็เลยจัดจานโดยไม่ใส่อะไรตรงนั้นแล้วเสิร์ฟไป
ไม่มีใครส่งใบแจ้งปัญหาออกมาเลย
ที่ไม่เหมือนกัน: คนจัดจานจริงหาถาดไม่เจอก็คงตะโกนถามเชฟ
แต่ Ruby ถือว่า "ถาด @ ที่ไม่มี" = ถาดเปล่า (nil) ซึ่งถูกกติกา จึงเงียบ
ส่วนชื่อที่ไม่มี @ (เช่น parms) ไม่ใช่ถาด แต่เป็น "คำสั่ง" ที่ Ruby ต้องรู้จัก — ไม่รู้จักจึงหยุดครัวทันที
สรุปการพังทั้งหมดของวันนี้
| ทำอะไรผิด | หัวหน้าแดงที่เห็น | status | ไปแก้ที่ไหน |
|---|---|---|---|
| ยังไม่มี route | Routing ErrorNo route matches [GET] "/hello" | 404 | config/routes.rb |
route ชี้ controller ที่ไม่มี (page#about) | ActionDispatch::MissingControlleruninitialized constant PageController | 500 | ชื่อใน route หรือชื่อไฟล์/ชื่อ class ของ controller |
| ชื่อไฟล์ view ผิด | No view template for interactive request | 406 | ชื่อไฟล์ใน app/views/… (หน้า error บอกชื่อที่ถูกให้) |
ลืม end | ArgumentError ที่ข้างในมี SyntaxError | 500 | คู่ def/end ในไฟล์ที่บอก · ตรวจด้วย ruby -c |
พิมพ์ชื่อผิดใน controller (parms) | NameError + Did you mean? | 500 | บรรทัดที่กรอบโค้ดชี้ |
พิมพ์ @ตัวแปร ผิดใน view | ไม่มี — ค่าหายเงียบ ๆ | 200 | เทียบชื่อ @… ใน controller กับ view |
- exception · error class
- ชื่อชนิดของ error เช่น
NameError·SyntaxError·ActionDispatch::MissingController(ส่วนหน้า::คือชื่อกล่องที่มันอยู่) · เวลาถามใคร ให้ก็อปชื่อนี้ไปทั้งก้อน — พิมพ์บอก AI ได้ว่า "Rails 8 dev error page: ActionDispatch::MissingController uninitialized constant PageController — what file should I check?" - stack trace (backtrace)
- รายการเส้นทางว่าโค้ดไหนเรียกโค้ดไหนมาจนถึงจุดที่พัง · บรรทัดบนสุดคือจุดที่พัง · Application Trace = เฉพาะไฟล์ของเรา (อ่านอันนี้) · Framework Trace = โค้ดของ Rails · ส่งให้ senior ดู ให้ส่งชื่อ error + ประโยคแดง + 3–5 บรรทัดแรกของ Application Trace ไม่ต้องส่งทั้งกอง
- development error page · web console
- หน้าแดงนี้เป็นของโหมด development (คำว่า development ที่เห็นตอนเปิด server) ·
กล่องดำ
>>ล่างจอคือ web console — พิมพ์ Ruby ได้ ณ จุดที่พัง คล้าย console ใน บท 104 · ถาม senior ได้ว่า "ใช้ web console ดูค่า params ตรงจุดที่พังยังไง"
- instance variable (
@ตัวแปร) - ตัวแปรที่มี
@นำหน้า เป็นของ object ตัวนั้น · ใน Rails คือทางเดียวที่ controller ส่งของให้ view · ถ้าอ่านตัวที่ไม่เคยตั้งค่า จะได้nilไม่ใช่ error · พิมพ์บอก AI ได้ว่า "my view shows blank — check if the instance variable names match between controller and view" NameError· "Did you mean?"- error เมื่อ Ruby ไม่รู้จักชื่อที่เราพิมพ์ (ตัวแปรธรรมดา หรือ method) · ท่อน Did you mean? คือ Ruby ลองหาชื่อที่สะกดใกล้กันมาเสนอ · คุยกับเพื่อนได้ว่า "ได้ NameError บรรทัด 3 มันเสนอว่า params น่าจะพิมพ์ผิด"
ของแถมจากสไลด์ (ข้อ 4 และ 6 ของ Lab) — ทำต่อถ้ายังมีแรง
ข้อ 4 · เพิ่ม /about/:id แล้วสลับลำดับ — :id คือช่องว่างใน URL ที่รับค่าอะไรก็ได้ (บท 101)
· ผมเพิ่ม action ชื่อ member (ชื่อนี้ตั้งเอง สไลด์ไม่ได้กำหนด) แล้วลองสองแบบ:
# แบบ A
get "/about", to: "pages#about"
get "/about/:id", to: "pages#member"
# แบบ B — สลับสองบรรทัด
get "/about/:id", to: "pages#member"
get "/about", to: "pages#about"
# action ที่เพิ่มใน PagesController กับ view ของมัน
def member
@id = params[:id]
end
# app/views/pages/member.html.erb มีบรรทัดเดียว: <p>id = <%= @id %></p>
# ผลจริง (ทั้งสองแบบได้เหมือนกัน)
/about <p>Hello PEA</p>
/about/7 <p>id = 7</p>
"/about/:id"— URL ที่ขึ้นต้นด้วย/about/แล้วตามด้วยอะไรก็ได้ ค่านั้นจะไปอยู่ในparams[:id]def member…@id = params[:id]— หยิบค่าจาก URL วางบนถาดชื่อ@idให้ view- บรรทัด
/about/7 <p>id = 7</p>— อ่านว่า "เปิด/about/7แล้วได้ HTML บรรทัดนี้"
ไม่มีอะไรเปลี่ยน — เพราะ /about/:id ต้องมีอะไรต่อท้าย /about/ เสมอ
สองเส้นนี้จึงไม่มีวันแย่ง URL เดียวกัน · ลำดับมีผลจริงเฉพาะเมื่อสองเส้นรับ URL เดียวกันได้ — ดู quiz ข้อ 4
ข้อ 6 · เปิด console (ครัวทดลองหลังร้าน บท 104) แล้วลองสองคำสั่ง:
$ bin/rails console
Loading development environment (Rails 8.1.4)
wongnok-blog(dev):001> 3.days.ago
=> 2026-10-31 14:17:32.615684000 UTC +00:00
wongnok-blog(dev):002> "post".pluralize
=> "posts"
wongnok-blog(dev):003> exit
3.days.ago= "3 วันที่แล้ว" —daysกับagoเป็น method ที่ Active Support เติมให้ตัวเลข (บท 90)- ผมรันตอน 3 พ.ย. 21:17 เวลาไทย แต่ได้ 31 ต.ค. 14:17 UTC — เป็นเวลาเดียวกัน แค่บอกเป็นโซน UTC (ช้ากว่าไทย 7 ชั่วโมง) เพราะแอปใหม่ตั้งโซนเวลาเป็น UTC ไว้ · เวลาของคุณจะเป็นตอนที่คุณรัน
"post".pluralize→"posts"= กฎเอกพจน์/พหูพจน์ที่ Rails ใช้เดาชื่อตาราง (บท 99)
Checklist ท้ายวัน — ควรเข้าใจอะไรแล้ว
อ่านทีละข้อ ถ้าข้อไหนอธิบายให้เพื่อนฟังไม่ได้ ให้กลับไปบทนั้น — ไม่ต้องจำ syntax ได้หมด แค่รู้ว่าเปิดดูที่ไหน
- ☐ Ruby คือภาษา Rails คือครัวสำเร็จรูปที่เขียนด้วย Ruby · MVC แบ่งงานเป็นห้องเก็บวัตถุดิบ (Model) · คนจัดจาน (View) · หัวหน้าเชฟ (Controller) โดยมีพนักงานต้อนรับ (routes) ยืนหน้าร้าน (บท 90)
- ☐ อ่านโค้ด Ruby สั้น ๆ ออก: ตัวแปร · string กับ
#{}(91) · array · hash · symbol ·map(92) ·defกับ block (93) - ☐ ใน Ruby มีแค่
nilกับfalseที่นับเป็นเท็จ —0กับ""เป็นจริง (94) - ☐ class ·
@ตัวแปร·<สืบทอด (95) · error ใน Ruby ใช้rescueไม่ได้คืนerrแบบ Go (96) - ☐ อ่าน
get "/about", to: "pages#about"ออกว่าเป็นการเรียก method ที่ละวงเล็บกับปีกกาไว้ (97) - ☐ สร้างแอปด้วย
rails newได้ รู้ว่าไม่ได้ commit ให้ และรู้ว่าโฟลเดอร์app/config/มีไว้ทำอะไร (98) - ☐ จาก
pagesเดาได้ว่า class ชื่อPagesControllerไฟล์ชื่อpages_controller.rbview อยู่app/views/pages/(99) - ☐ เล่าได้ว่า request หนึ่งใบเดินผ่าน route → controller → view แล้วกลับเป็น HTML (100)
- ☐ ทำหน้าใหม่ได้เองครบ 3 ไฟล์ และส่งค่าจาก URL ไปถึงหน้าเว็บผ่าน
paramsกับ@ตัวแปร(101–103) - ☐ เปิด
bin/rails consoleลองโค้ดได้ และรู้ว่าdebuggerหยุดโค้ดกลางทางได้ (104) - ☐ เจอหน้าแดงแล้วอ่าน 3 จังหวะ: ชื่อ error → ประโยคแดง → หาไฟล์ใน
app/(บทนี้)
Day 2 จะได้อะไรต่อ
ตอนนี้ทุกอย่างบนหน้าเว็บของเราเขียนตายตัวไว้ในโค้ด — "PEA" มาจากสตริงใน controller ไม่ได้มาจากที่ไหน
· ตู้เก็บแฟ้ม (database) ที่สร้างด้วย db:prepare ยังว่างอยู่
- บันทึกบทความลง database จริง — สร้าง
Post(แม่พิมพ์ของบทความหนึ่งชิ้น) ที่คุยกับตารางpostsได้ ผ่าน Active Record (ห้องเก็บวัตถุดิบ + คนดูแลสต็อก ที่รู้ว่าแฟ้มอยู่ลิ้นชักไหน) - migration — ใบสั่งช่างมาต่อเติมตู้ เพิ่มลิ้นชัก
postsลงใน database resources :posts— route บรรทัดเดียวที่สร้างเส้นทางมาตรฐานของบทความให้ครบชุด (ผมลองแล้วได้ 8 เส้น: ดูรายการ · ดูทีละชิ้น · ฟอร์มสร้าง · บันทึก · ฟอร์มแก้ · แก้ 2 แบบ · ลบ)
ถ้าเทียบกับฝั่ง Go: Active Record ทำหน้าที่เดียวกับ GORM ที่คุณเคยใช้ แต่มากับ Rails เลย ไม่ต้องเลือกเอง · สไลด์สั่งให้อ่านล่วงหน้า: Rails Guides — Active Record Basics (ลิงก์อยู่ในหัวข้ออ้างอิง ท้ายบท)
Quiz ท้ายวัน
สี่ข้อนี้มาจากคำถามท้ายวันในสไลด์ เขียนใหม่ให้อ่านง่ายขึ้น · เฉลยทุกข้อรันพิสูจน์แล้ว
ข้อ 1 · route เขียนว่า get "/hello", to: "greetings#index" — ไฟล์ view (คนจัดจาน) ที่ Rails จะไปหาคือไฟล์ไหน?
ตัวเลือกที่ 2 ถูก · พิสูจน์ด้วยขั้น 3 ของบทนี้เอง: ไฟล์ app/views/greetings/index.html.erb ทำให้ /hello ขึ้นจริง
และ comment BEGIN app/views/greetings/index.html.erb ใน HTML ก็ยืนยัน ·
ส่วนพังแบบที่ 2 หน้า error บอกตรง ๆ ว่าไฟล์ต้องชื่อ app/views/ชื่อ controller/ชื่อ action.html.erb
ตัวเลือกที่ 1 เอาชื่อ URL มาตั้ง — แต่ Rails ไม่ดู URL เลย ดูแค่ "greetings#index" ·
ตัวเลือกที่ 3 ใช้ชื่อ controller แต่ลืมว่า action คือชื่อไฟล์ในโฟลเดอร์ของ controller ·
ตัวเลือกที่ 4 ผิดสองชั้น: ใช้ชื่อ URL และอยู่นอก app/views/
จุดที่ต้องจำ: "แผนก#เมนู" → app/views/แผนก/เมนู.html.erb · เปลี่ยน URL เป็น /hi ไฟล์ view ก็ยังเป็นตัวเดิม
ข้อ 2 · ใน Ruby เขียน if 0 แล้วตามด้วยโค้ด — โค้ดข้างในจะทำงานไหม?
ตัวเลือกที่ 2 ถูก · ผลรันจริงบน Ruby 4.0.9:
if 0
puts "เข้า if"
else
puts "ไม่เข้า"
end
# ผลที่ได้: เข้า if
ตัวเลือกที่ 1 คือกับดักของคนที่มาจาก JavaScript/TypeScript ซึ่ง 0 เป็นเท็จ ·
ตัวเลือกที่ 3 เป็นกฎของ Go ไม่ใช่ Ruby — Go ไม่ยอม build (ผมลองแล้วได้ non-boolean condition in if statement) แต่ Ruby รันได้ ·
ตัวเลือกที่ 4 ผิด — เอกสารทางการของ Ruby เขียนกฎนี้ไว้ตรง ๆ ว่ามีแค่ nil กับ false ที่เป็นเท็จ
จุดที่ต้องจำ: ใน Ruby 0 · "" · [] เป็นจริงหมด · นี่คือเหตุผลที่ ?name= ในภารกิจได้ Hello ว่าง ๆ
ข้อ 3 · posts คือ array ของบทความ (ในตัวอย่างนี้จำลองด้วย Struct — แม่พิมพ์ข้อมูลแบบย่อที่มีช่อง title — เพราะยังไม่มี database) — posts.map(&:title) ได้ผลเท่ากับข้อไหน?
ตัวเลือกที่ 1 ถูก · ผลรันจริงกับบทความจำลองสองชิ้น:
Post = Struct.new(:title)
posts = [Post.new("Hello Rails"), Post.new("Day 1")]
p posts.map(&:title)
# ["Hello Rails", "Day 1"]
p posts.map { |post| post.title }
# ["Hello Rails", "Day 1"]
p posts.map { |post| :title }
# [:title, :title]
p posts.select { |post| post.title }
# [#<struct Post title="Hello Rails">, #<struct Post title="Day 1">]
p posts.each { |post| post.title }
# [#<struct Post title="Hello Rails">, #<struct Post title="Day 1">]
(p หน้าบรรทัดคือคำสั่งพิมพ์ค่าออกจอ บท 93 · บรรทัด # ใต้แต่ละบรรทัดคือผลที่ได้จริง ·
#<struct Post …> คือหน้าตาของบทความจำลองทั้งชิ้น)
ตัวเลือกที่ 2 คืนป้ายชื่อ :title เฉย ๆ ทุกรอบ ไม่ได้ไปเอาชื่อบทความ ·
ตัวเลือกที่ 3 ใช้ select ซึ่ง "คัดของ" ไม่ใช่ "แปลงของ" — ได้บทความทั้งชิ้นกลับมา ไม่ใช่รายชื่อ ·
ตัวเลือกที่ 4 ใช้ each ซึ่ง "เดินดูทีละชิ้น" แล้วคืน array เดิมกลับมา — ค่าที่ block คืนถูกทิ้งไป (บท 92–93)
จุดที่ต้องจำ: &:ชื่อ = "เรียก method ชื่อนี้กับสมาชิกทุกตัว" — ทางลัดของ block ที่ทำแค่เรียก method ตัวเดียว
ข้อ 4 · routes.rb เขียนสองบรรทัดเรียงแบบนี้: บนคือ get "/posts/:id", to: "posts#show" ล่างคือ get "/posts/popular", to: "posts#popular" — เปิด /posts/popular แล้วไปที่ action ไหน?
ตัวเลือกที่ 2 ถูก · ผลรันจริง (ผมสร้าง PostsController ชั่วคราวที่ตอบเป็นข้อความสั้น ๆ) ·
bin/rails runner '…' = สั่งรันโค้ด Ruby หนึ่งบรรทัดในแอปโดยไม่ต้องเปิด server ·
recognize_path = ถามพนักงานต้อนรับตรง ๆ ว่า "URL นี้จะส่งไปแผนกไหน เมนูอะไร":
$ bin/rails runner 'p Rails.application.routes.recognize_path("/posts/popular")'
{controller: "posts", action: "show", id: "popular"}
# เปิด /posts/popular จริง
show id=popular
พนักงานต้อนรับอ่านกฎจากบนลงล่าง เจอข้อแรกที่เข้าได้ก็ส่งเลย · :id รับอะไรก็ได้ รวมถึงคำว่า popular
· ตัวเลือกที่ 1 คือสิ่งที่อยากให้เป็น แต่ Rails ไม่เลือกเส้นที่ตรงที่สุด เลือกเส้นแรก ·
ตัวเลือกที่ 3 ผิด — server เปิดได้ปกติ ไม่มีคำเตือน ·
ตัวเลือกที่ 4 ผิด — ใบสั่งหนึ่งใบไปได้ action เดียว
จุดที่ต้องจำ: เส้นที่เจาะจง (/posts/popular) ไว้บน เส้นที่มี :id ไว้ล่าง · ต่างจากของแถมข้อ 4 ที่สองเส้นไม่แย่ง URL กัน ลำดับจึงไม่มีผล
ถามกลับได้เลย
ถ้าคุณเจอหน้าแดงที่ไม่อยู่ในตารางสรุป — นั่นคือของดีที่สุดที่จะเอาไปถามเพื่อน senior หรือ AI
· ส่งไปสามอย่าง: ชื่อ error · ประโยคแดง · ผลของ git diff (เห็นว่าแก้อะไรไปจาก commit ล่าสุด)
· คนที่ช่วยจะเห็นภาพทันทีโดยไม่ต้องเดา
ถ้าเปิดสไลด์ดูประกอบ
| หน้า | สไลด์เขียน | ของจริง | ต้องสนใจไหม |
|---|---|---|---|
| 40 (ข้อ 1 ของ Lab อ้างถึง) | rails new ได้ git repo พร้อม commit แรก | สร้าง repo ให้ แต่ไม่ commit — git log ฟ้อง fatal | สนใจ — commit เองตามขั้น 1 |
| 72 ข้อ 4 | เพิ่ม /about/:id แล้วลองสลับลำดับดูว่าเกิดอะไร | ไม่มีอะไรเปลี่ยน เพราะสองเส้นไม่แย่ง URL กัน | ไม่ต้องตกใจ — quiz ข้อ 4 คือกรณีที่ลำดับมีผลจริง |
| 72 ข้อ 6 | ลอง 3.days.ago | ตอบเป็นเวลา UTC ไม่ใช่เวลาไทย | แค่รู้ไว้ |
| 72 ฝั่ง Day 2 | resources :posts บรรทัดเดียวแทน 6 บรรทัด | ได้ 8 เส้น (มีฟอร์มแก้ไข edit และ PUT เพิ่ม) | ไม่ต้อง — Day 2 สอนละเอียด |
| 70–71 | quiz ให้ตอบ A–D ในแชท | บทนี้เรียงตัวเลือกตามสไลด์ แต่เขียนคำถามใหม่ให้อ่านง่าย · ข้อ 3 ตัวเลือกสุดท้ายเปลี่ยนจาก posts.title เป็น posts.each และใช้ |post| แทน |p| | ไม่ต้อง |
| ทั้งชุด | Ruby 4.0.6 · Rails 8.1.3 | เครื่องตรวจใช้ Ruby 4.0.9 · Rails 8.1.4 | ไม่ต้อง — ผลในบทนี้ไม่ต่างกัน |
อ้างอิง
- Rails Guides — Getting Started with Rails — อ่านหัวข้อ 4 Hello, Rails! คือ Lab เดียวกับบทนี้ในฉบับทางการ (เปิด server แล้วเพิ่ม route + controller + view)
- Rails Guides — Layouts and Rendering in Rails — อ่านหัวข้อ 2.1 Rendering by Default: Convention Over Configuration in Action ที่อธิบายว่าทำไม action ว่าง ๆ ก็ได้หน้าเว็บ และ Rails เดาชื่อไฟล์ view จากอะไร (quiz ข้อ 1 · พังแบบที่ 2)
- Rails Guides — Rails Routing from the Outside In —
อ่านย่อหน้าที่ขึ้นต้นว่า "Order matters" ในหัวข้อ 2.2 (เส้นแรกที่ตรงชนะ — quiz ข้อ 4)
แล้วต่อด้วย 3.2 Dynamic Segments สำหรับ
:id - Rails Guides — Debugging Rails Applications —
อ่านหัวข้อ 5 Debugging with the web-console Gem คือกล่องดำ
>>ล่างหน้า error - Ruby docs — Control Expressions — อ่านย่อหน้าที่สองของหน้า (ขึ้นต้นว่า For the tests in these control expressions) ที่เขียนว่า nil กับ false เป็นเท็จ ส่วน object อื่นทุกตัวเป็นจริง (quiz ข้อ 2)
- Rails Guides — Active Record Basics — อ่านหัวข้อ 1 What is Active Record? กับหัวข้อ 2 Convention over Configuration in Active Record ก่อนเข้า Day 2 (สไลด์หน้า 72 สั่งไว้)