🔁 ทวนก่อนเริ่ม — คำจากฝั่ง Go ที่บทนี้จะหยิบมาเทียบ (จำไม่ได้ก็ไม่เป็นไร บทนี้อธิบายซ้ำให้)
- server — โปรแกรมที่เปิดค้างไว้ รอให้มีคนส่งคำขอเข้ามา (บท 37)
- handler — ฟังก์ชันที่รับคำขอหนึ่งครั้งแล้วตอบกลับ (บท 37–38)
- router ของ Gin — ตัวจับคู่ว่า URL ไหนให้ handler ไหนรับ เช่น
r.GET("/users/:id", …)(บท 38) - struct — แม่พิมพ์ของข้อมูล เช่น
type User struct { … } - GORM — ตัวช่วยคุยกับ database ที่เดาว่า struct
Userอยู่ในตารางusers(บท 27)
🧰 เวอร์ชันที่ใช้ตรวจ: Ruby 4.0.9 · Rails 8.1.4 · สไลด์ใช้ Ruby 4.0.6 · Rails 8.1.3 — เป็นรุ่นแก้บั๊กของสายเดียวกัน ใช้ตามสไลด์ได้เลย
1 · เปิดหน้าเว็บหนึ่งครั้ง เกิดอะไรขึ้น
ตอนคุณพิมพ์ที่อยู่เว็บแล้วกด Enter โปรแกรม browser (Chrome, Safari) จะส่งคำขอออกไปหาเครื่องอีกเครื่องหนึ่ง
เครื่องนั้นมีโปรแกรมเปิดรออยู่ เรียกว่า server · server อ่านคำขอ ทำหน้าเว็บเสร็จแล้วส่งคำตอบกลับมา
คำตอบส่วนใหญ่คือ HTML — ข้อความที่บอก browser ว่าหน้าเว็บมีหัวเรื่องอะไร ย่อหน้าอะไร แล้ว browser ก็วาดออกมาให้เห็น
browser คือลูกค้า · request คือใบสั่งอาหาร ที่เขียนว่าอยากได้อะไร ·
server คือร้านที่เปิดอยู่ · response คือจานอาหารที่ยกมาเสิร์ฟ
ลูกค้าไม่ต้องรู้ว่าในครัวทำยังไง ขอแค่ได้จานที่สั่ง · ภาพนี้จะใช้ต่อไปทั้ง 16 บท
ที่ไม่เหมือนกัน: ร้านจริงจำหน้าลูกค้าประจำได้ แต่ server ลืมทุกครั้ง — ใบสั่งแต่ละใบถูกทำเหมือนลูกค้าคนใหม่ (ถ้าอยากให้จำได้ ลูกค้าต้องยื่นบัตรสมาชิกมาด้วยทุกครั้ง — เรื่องนี้เรียนทีหลัง) · และร้านจริงใช้เวลาเป็นนาที แต่ server ตอบเสร็จในเสี้ยววินาที
ใน Go คุณเคยเป็นเจ้าของร้านแบบนี้มาแล้ว — บท 37 เปิด server ด้วย net/http แล้วบท 38 ใช้ Gin
ทุกบรรทัดอย่าง r.GET("/users/:id", …) คือการบอกว่า "ใบสั่งแบบนี้ ให้ฟังก์ชันนี้ทำ"
· Rails ทำงานเดียวกันนี้ แค่แบ่งงานในครัวละเอียดกว่า (หัวข้อ 5)
- browser / client
- ฝั่งที่ขอ · ในเว็บคือโปรแกรมท่องเว็บ แต่จริง ๆ อะไรที่ส่งคำขอได้ก็นับเป็น client เช่นคำสั่ง
curl(คำสั่งที่ขอหน้าเว็บจาก terminal ได้โดยไม่ต้องเปิด browser) · ถาม senior ได้ว่า "ปัญหานี้อยู่ฝั่ง client หรือฝั่ง server" - server
- ฝั่งที่ตอบ · โปรแกรมที่เปิดค้างไว้รอคำขอ · ใน Rails เปิดด้วยคำสั่ง
bin/rails server(บท 100) - request / response
- คำขอหนึ่งครั้ง กับคำตอบของมัน · มาเป็นคู่เสมอ · พิมพ์บอก AI ได้ว่า "walk me through the request/response cycle of this page"
- HTML
- ภาษาบอกโครงหน้าเว็บ (หัวเรื่อง ย่อหน้า ลิงก์) · server ส่งมา browser วาดออกมา
2 · Ruby กับ Rails ไม่ใช่ของชิ้นเดียวกัน
ชื่อ "Ruby on Rails" ฟังเหมือนของชิ้นเดียว แต่จริง ๆ เป็นของสองชิ้นซ้อนกัน
Ruby คือภาษาโปรแกรม เหมือนที่ Go เป็นภาษา · ใช้เขียนอะไรก็ได้ ไม่จำเป็นต้องเป็นเว็บ
Rails คือ framework — ชุดเครื่องมือทำเว็บสำเร็จรูป ที่เขียนด้วยภาษา Ruby และรันอยู่บน Ruby
| ภาษา | framework ที่เขียนด้วยภาษานั้น | คุณเคยเจอที่ไหน |
|---|---|---|
| Go | Gin | บท 38 |
| TypeScript | Next.js | บท 69 |
| Ruby | Rails | เริ่มบทนี้ |
แล้ว framework "ทำอะไรแทนเรา"? คำตอบสั้น ๆ คือ ทำส่วนที่ทุกเว็บต้องมีเหมือนกัน —
รับคำขอ · แยกว่า URL ไหนไปหาใคร · คุยกับ database · ประกอบหน้า HTML
เราเขียนแค่ส่วนที่เป็นของร้านเราเอง เช่น "หน้าบทความต้องโชว์หัวเรื่องกับเนื้อหา"
อีกเรื่องที่ต่างจากการเรียก library: framework เป็นฝ่ายเรียกโค้ดของเรา — ใน Gin คุณเขียน handler ไว้ แล้ว Gin เรียกมันเองตอนมีคำขอเข้ามา
Go ที่เรียนมา = เปิดร้านเองตั้งแต่ศูนย์ — จ้างพนักงานต้อนรับเอง (Gin router) ·
เขียนใบงานให้เชฟเอง (handler) · ซื้อตู้เก็บของเอง (GORM + database) · ทุกชิ้นคุณเลือกและต่อเข้าหากันเอง
Rails = ร้านแฟรนไชส์ที่ได้ครัวครบชุด — ส่งของมาพร้อมใช้ทุกชิ้น
ขอแค่คุณวางของตามตำแหน่งที่แฟรนไชส์กำหนด แล้วทุกอย่างทำงานต่อกันเอง
ที่ไม่เหมือนกัน: แฟรนไชส์จริงต้องจ่ายค่าสิทธิ์และห้ามเปลี่ยนเมนู · Rails ใช้ฟรี และถอดชิ้นไหนออกไปใส่ของอื่นแทนก็ได้ — แต่ยิ่งเปลี่ยนเยอะ ยิ่งเสียข้อดีของการ "มาครบชุด" และต้องดูแลเองมากขึ้น
- programming language (ภาษาโปรแกรม)
- ตัวกำหนดว่าเขียนโค้ดหน้าตาแบบไหน · Ruby รันด้วยคำสั่ง
rubyเหมือน Go รันด้วยgo run· เวลาถาม AI ให้บอกชั้นให้ชัด: "Is this a plain Ruby question or a Rails question?" - framework
- ชุดโค้ดสำเร็จรูปที่วางโครงร้านให้ แล้วเรียกโค้ดของเราตามจังหวะของมัน · ถาม senior ได้ว่า "ส่วนนี้ Ruby ทำเอง หรือ Rails เติมให้" — จะได้รู้ว่าต้องเปิดเอกสารเล่มไหน
- gem
- ชื่อเรียก "ชุดโค้ดที่คนอื่นเขียนไว้ให้ลงมาใช้" ของ Ruby · ในภาพร้านอาหารคืออุปกรณ์สำเร็จรูปที่ซื้อมาใช้ในครัว · เทียบได้กับ module ที่
go getมา · Rails เองก็เป็น gem ตัวหนึ่ง (รายละเอียดบท 98)
3 · ลองดูหน้าตา Ruby ครั้งแรก
ยังไม่ต้องจำไวยากรณ์ · ดูแค่ว่า Ruby ตั้งใจให้อ่านจากซ้ายไปขวาแล้วเป็นประโยคภาษาอังกฤษ
· นี่คือไฟล์ first.rb สามบรรทัด (บรรทัดแรกเป็น comment อีกสองบรรทัดเป็นคำสั่ง) จากตัวอย่างในสไลด์หน้า 4:
# first.rb
puts "abc".upcase.reverse
5.times { puts "hello" }
ผลรันจริง · บรรทัดที่ขึ้นต้นด้วย $ คือคำสั่งที่พิมพ์ใน terminal (ตัว $ ไม่ต้องพิมพ์) · บรรทัดที่เหลือคือสิ่งที่ขึ้นจอ:
$ ruby first.rb
CBA
hello
hello
hello
hello
hello
อ่านทีละบรรทัดแบบภาษาคน:
# first.rb= comment (ข้อความที่ Ruby ข้ามไม่รัน) · Ruby ใช้#แทน//ของ Go · บรรทัดนี้แค่บอกว่าไฟล์ชื่ออะไรputs= พิมพ์ออกจอแล้วขึ้นบรรทัดใหม่ · เหมือนfmt.Printlnใน Go- จุด
.= "สั่งของทางซ้าย ให้ทำสิ่งทางขวา" ·"abc".upcaseอ่านว่า "เอา abc มาทำเป็นตัวใหญ่" ได้"ABC"แล้ว.reverseต่ออีกทีคือ "กลับด้าน" ได้CBA· ใน Go ต้องเรียกstrings.ToUpper("abc")แทน 5.times { … }= "ทำสิ่งในปีกกา 5 ครั้ง" · ใน Go คือfor i := 0; i < 5; i++ { … }· สังเกตว่าตัวเลข5ก็สั่งงานด้วยจุดได้ (บท 91 อธิบายว่าทำไม · ปีกกา{ }แบบนี้เรียนบท 92–93)
ทีนี้ลองบรรทัดที่อยู่ในสไลด์หน้าเดียวกัน — 3.days.ago อ่านว่า "สามวันที่แล้ว"
puts 3.days.ago
ไฟล์ days.rb มีแค่บรรทัดเดียวข้างบนนี้ · รันด้วยคำสั่ง ruby days.rb (Ruby เปล่า ๆ ยังไม่มี Rails) จะได้วันที่ของสามวันก่อน หรือได้อย่างอื่น?
ผลรันจริง — พัง:
$ ruby days.rb
days.rb:1:in '<main>': undefined method 'days' for an instance of Integer (NoMethodError)
puts 3.days.ago
^^^^^
อ่าน error ทีละท่อน:
days.rb:1= ไฟล์days.rbบรรทัดที่ 1 ·in '<main>'= พังในเนื้อไฟล์หลัก ไม่ได้อยู่ในคำสั่งย่อยไหน (ตอนนี้ข้ามไปได้)undefined method 'days' for an instance of Integer= "ตัวเลข (Integer) ไม่รู้จักคำสั่งชื่อdays"^^^^^= ลูกศรชี้ว่าพังตรงคำไหนในบรรทัด
ทีนี้รันไฟล์เดิม แต่สั่งให้โหลด Active Support (ชิ้นส่วนหนึ่งของ Rails) เข้ามาก่อน · -r ย่อมาจาก require แปลว่า "โหลดสิ่งนี้เข้ามาก่อนรัน":
$ ruby -ractive_support/all days.rb
2026-10-31 20:35:55 +0700
ผ่าน · ไฟล์เดียวกันเป๊ะ ต่างกันแค่โหลด Active Support เข้ามา
แปลว่า days ไม่ใช่คำของ Ruby · Rails เป็นคนเติมให้
(วันที่ของคุณจะต่างจากนี้ เพราะรันคนละวัน)
Ruby เปล่า ๆ เหมือนพนักงานใหม่ที่พูดไทยคล่อง แต่ยังไม่รู้ศัพท์ในครัวของร้านนี้ —
ได้ยินคำว่า "สามวันก่อน" แบบย่อ ๆ ของร้านแล้วงง
Active Support คือสมุดศัพท์ที่แฟรนไชส์แจกมา พอเปิดร้าน Rails พนักงานทุกคนก็ได้สมุดเล่มนี้ทันที
เลยเข้าใจ 3.days.ago (สามวันก่อน) · 1.megabyte (หนึ่งเมกะไบต์) ได้หมด
ที่ไม่เหมือนกัน: สมุดศัพท์ไม่ได้เปลี่ยนภาษาไทย แต่ Active Support เติมคำสั่งใหม่ลงไปในของของ Ruby โดยตรง
— ตัวเลขทุกตัวในโปรแกรมรู้จัก days ขึ้นมาเอง ·
นี่คือเหตุผลที่มือใหม่แยกไม่ออกว่าบรรทัดไหนเป็น Ruby บรรทัดไหนเป็น Rails · วิธีพิสูจน์คือรันด้วย ruby เปล่า ๆ ถ้าพังแปลว่าเป็นของ Rails
- method
- คำสั่งที่ของชิ้นหนึ่งทำได้ เช่น
upcaseของข้อความ ·timesของตัวเลข · เรียกด้วยจุด (ละเอียดบท 91 และ 93) - NoMethodError
- error ที่แปลว่า "ของชิ้นนี้ไม่มีคำสั่งชื่อนี้" · เจอบ่อยที่สุดในชีวิตคน Ruby · เอาทั้งบรรทัดไปถาม AI ได้ว่า "Why do I get NoMethodError here — is this method from Rails?"
- Active Support
- ชิ้นส่วนของ Rails ที่เติมคำสั่งสะดวก ๆ ให้ Ruby (
days·megabyte· และอีกเยอะ) · ในแอป Rails ถูกโหลดให้อัตโนมัติ ไม่ต้องสั่ง-rเอง
4 · แอปตัวอย่างของคอร์ส: บล็อก wongnok_blog
ตลอดคอร์ส Rails สไลด์จะสร้างแอปเดียวต่อเนื่องกันไป ชื่อ wongnok_blog — เป็นเว็บบล็อก
บล็อกคือเว็บที่มีบทความหลายชิ้น คนเข้ามาอ่านได้ เจ้าของเขียนใหม่ แก้ หรือลบได้
คำว่า "บทความ" ภาษาอังกฤษคือ post · และคำนี้จะโผล่มาสี่หน้าตา:
| เขียนว่า | หมายถึง | ใน Go ที่เคยเขียน เทียบได้กับ |
|---|---|---|
Post (ตัวใหญ่ · เอกพจน์) | แม่พิมพ์ของบทความ — บอกว่าบทความหนึ่งชิ้นมีอะไรบ้าง (หัวเรื่อง เนื้อหา) | type Post struct { … } |
posts (ตัวเล็ก · พหูพจน์) | ตารางใน database ที่เก็บบทความทุกชิ้น | ตาราง posts ที่ GORM เดาให้จาก struct Post |
/posts | URL ของหน้ารวมบทความทั้งหมด | r.GET("/posts", …) |
/posts/42 | URL ของบทความเลขที่ 42 ชิ้นเดียว | r.GET("/posts/:id", …) |
กฎจำง่าย: ตัวใหญ่เอกพจน์ = แม่พิมพ์ (ชนิดของข้อมูล) · ตัวเล็กพหูพจน์ = ที่เก็บหลายชิ้น หรือ URL
ในห้องเก็บของของร้าน มีตู้เก็บแฟ้ม (database) · ในตู้มีลิ้นชักบทความ (ตาราง posts) ·
ในลิ้นชักมีแฟ้มหลายแผ่น แผ่นละหนึ่งบทความ (แถว · row)
Post คือคนดูแลสต็อกประจำลิ้นชักบทความ — รู้ว่าแฟ้มหนึ่งแผ่นมีช่องอะไร (หัวเรื่อง เนื้อหา)
และใครอยากได้บทความ ต้องขอผ่านเขา
ที่ไม่เหมือนกัน: ในร้านจริง ต้องมีคนออกแบบแบบฟอร์มก่อน แล้วค่อยทำลิ้นชัก ·
แต่ใน Rails ลิ้นชักมาก่อน — Post เดินไปเปิดดูว่าลิ้นชัก posts มีช่องอะไร แล้วจำตามนั้นเอง
(พิสูจน์ในกล่องข้างล่าง)
❓ "ใน Go ต้องประกาศ struct ชัด ๆ แล้ว Rails เอา Post มาจากไหน?"
Post ไม่ได้โผล่มาเฉย ๆ — มีไฟล์ประกาศอยู่จริงชื่อ app/models/post.rb (Rails มีคำสั่งเขียนไฟล์นี้ให้ · บท 99)
ที่ดูเหมือน "มาเฉย ๆ" เพราะไฟล์นั้นสั้นมาก · เทียบกันตรง ๆ:
// Go: ท่อนหนึ่งในโปรเจกต์ — ต้องเขียนทุกช่องเอง
type Post struct {
ID uint
Title string
Body string
}
# Rails: ไฟล์ app/models/post.rb ทั้งไฟล์ — ที่ Rails สร้างให้จริง
class Post < ApplicationRecord
end
class Post…end= "ประกาศแม่พิมพ์ชื่อ Post" · ใกล้กับtype Post structของ Go (ละเอียดบท 95)< ApplicationRecord= "รับความสามารถทั้งหมดมาจาก ApplicationRecord" — ตัวกลางที่ Rails เตรียมไว้ ซึ่งรู้วิธีคุยกับ database (เครื่องหมาย<ตรงนี้ไม่ใช่ "น้อยกว่า" · บท 95)- ไม่มีรายชื่อช่องเลยสักช่อง — Rails ไปอ่านจากตาราง
postsใน database ตอนโปรแกรมรัน
พิสูจน์ได้ (แค่ดู ยังไม่ต้องทำตาม): ผมสร้างตาราง posts ที่มีช่อง title กับ body ไว้ก่อน (วิธีสร้างเรียน Day 2)
แล้วถาม Post ว่ารู้จักช่องอะไรบ้าง:
$ bin/rails runner 'p Post.column_names'
["id", "title", "body", "created_at", "updated_at"]
bin/rails runner '…'= สั่งรันโค้ด Ruby สั้น ๆ ภายในแอป Rails ·p= พิมพ์ออกจอให้เห็นหน้าตาข้อมูล (บท 93)Post.column_names= ถาม Post ว่า "ลิ้นชักของเธอมีช่องอะไรบ้าง"- ผลคือรายชื่อ 5 ช่อง —
titleกับbodyมาจากตาราง ทั้งที่post.rbไม่ได้เขียนไว้ · ส่วนid(เลขประจำแฟ้ม)created_atupdated_at(เวลาสร้าง/แก้ล่าสุด) คำสั่งสร้างตารางของ Rails ใส่ให้เอง
สรุป: ใน Go คุณเขียน struct ก่อน แล้ว GORM ทำตามให้ (AutoMigrate บท 27) · ใน Rails กลับกัน — ทำตารางก่อน แล้ว model อ่านตามตาราง
บท 91–97 ยังไม่มี Rails · ตัวอย่างที่เป็นบทความในบทพวกนั้นจะจำลองขึ้นด้วย Ruby ธรรมดา และจะบอกทุกครั้งว่ากำลังจำลอง
- model
- ส่วนของแอปที่ดูแลข้อมูล ·
Postคือ model ของบทความ · ถาม senior ได้ว่า "model นี้ผูกกับตารางไหน มีคอลัมน์อะไรบ้าง" - table / row (ตาราง / แถว)
- ตารางคือลิ้นชัก · แถวคือแฟ้มหนึ่งแผ่น = บทความหนึ่งชิ้น · ถ้าพูดว่า "post เลข 42" = แถวที่
idเป็น 42 - singular / plural (เอกพจน์ / พหูพจน์)
- Rails ใช้สองรูปนี้ต่างหน้าที่กันเสมอ (
Postกับposts) · พิมพ์บอก AI ได้ว่า "follow Rails naming conventions for singular and plural"
5 · MVC — เดินตามใบสั่ง "ขอดูบทความที่ 42" ทั้งเส้น
ในร้าน Rails งานไม่ได้กองอยู่ที่คนคนเดียว · ใบสั่งหนึ่งใบจะผ่านมือ 4 ฝ่าย ตามลำดับ
สามฝ่ายหลักมีชื่อย่อรวมกันว่า MVC = Model · View · Controller
· อีกฝ่ายคือ routes ที่ยืนรับอยู่หน้าร้าน
บทนี้ดูแค่เส้นทาง ยังไม่ต้องดูโค้ด:
localhost:3000 คือที่อยู่ของร้านที่เปิดอยู่ในเครื่องคุณเอง — localhost = "เครื่องนี้" · 3000 = เลขประตูร้าน (port) ที่ Rails ใช้เป็นค่าเริ่มต้น (บท 100 จะเปิดจริง) ·
show คือชื่อที่ Rails ตกลงกันไว้สำหรับเมนู "ดูหนึ่งชิ้น"
เทียบกับ Gin: handler ฟังก์ชันเดียวของคุณทำทุกขั้นเอง — อ่าน id จาก URL · เรียก GORM · ประกอบคำตอบ ·
Rails แยกขั้นพวกนี้ไปไว้คนละไฟล์ คนละหน้าที่
พนักงานต้อนรับไม่ทำอาหาร · หัวหน้าเชฟไม่ไปค้นตู้เอง · คนดูแลสต็อกไม่จัดจาน ·
ทุกคนทำแค่ส่วนของตัวเองแล้วส่งต่อ
ข้อดีคือพอหน้าเว็บผิด คุณรู้ว่าต้องไปหาใคร: URL ผิดทาง → routes · ข้อมูลผิด → model · หน้าตาผิด → view
ที่ไม่เหมือนกัน: ในร้านจริงมีคนสี่คนเดินไปมา · ใน Rails ทั้งหมดคือโค้ดที่ถูกเรียกต่อกันเป็นทอด ๆ ในโปรแกรมเดียว เสร็จในเสี้ยววินาที · และไม่มีใครห้ามให้ controller ทำ HTML เอง — แค่ "ข้อตกลง" บอกว่าไม่ควร
- MVC (Model–View–Controller)
- วิธีแบ่งโค้ดเป็นสามฝ่าย: ข้อมูล · หน้าตา · ตัวกลางที่คุมจังหวะ · ถาม senior ได้ว่า "logic นี้ควรอยู่ใน model หรือ controller"
- routes
- ตารางจับคู่ URL กับ controller + action · เทียบกับ
r.GET(…)ของ Gin (บท 101) - controller / action
- controller คือแผนก (เช่น
PostsController) · action คือเมนูแต่ละอย่างของแผนก (เช่นshow) · เวลาเขียนรวมกันจะเห็นเป็นPostsController#show—#ตรงนี้แค่คั่นชื่อแผนกกับชื่อเมนู ไม่ใช่ comment (บท 102) - view
- ไฟล์แม่แบบหน้า HTML ที่เติมข้อมูลลงไป · พิมพ์บอก AI ได้ว่า "which view renders this page?" (บท 103)
6 · Convention over Configuration — ทุกสาขาวางของที่เดิม
ในแผนภาพข้างบน ไม่มีใครบอก Rails เลยว่า "แผนกบทความชื่อ PostsController" หรือ "view อยู่โฟลเดอร์ไหน"
Rails รู้เองเพราะทุกอย่างตั้งชื่อตามกฎเดียวกัน · รู้คำว่า posts คำเดียว ก็เดาได้ทั้งสาย:
ใน Go คุณต้องบอกทุกอย่างเอง: route นี้ไปฟังก์ชันไหน · template อยู่ไฟล์ไหน
จริง ๆ คุณเคยเห็นความคิดนี้มาแล้วนิดหนึ่ง — GORM เดาว่า struct User อยู่ตาราง users (บท 27) ·
Rails แค่เอาความคิดเดียวกันนี้ไปใช้กับทุกห้องในร้าน
สังเกตว่าชื่อไฟล์เขียนตัวเล็กคั่นด้วย _ (posts_controller.rb) ส่วนชื่อในโค้ดเขียนติดกันตัวใหญ่ (PostsController) · กฎทั้งชุดเรียนบท 99
ร้านแฟรนไชส์วางของตำแหน่งเดียวกันทุกสาขา — ซอสอยู่ชั้นที่สอง ถาดอยู่ใต้เคาน์เตอร์
พนักงานย้ายสาขาแล้วทำงานได้ทันที ไม่ต้องมีคู่มือของสาขานั้น ·
Rails ก็แบบนั้น: เปิดโปรเจกต์ Rails ของใครก็หาไฟล์เจอ เพราะทุกโปรเจกต์วางของที่เดิม
ที่ไม่เหมือนกัน: ในร้านจริง ถ้าวางของผิดที่ เพื่อนจะทักทันที ·
Rails ไม่ทักตอนคุณพิมพ์ชื่อผิด — มันแค่หาไม่เจอ แล้วพังตอนมีคนเปิดหน้านั้น ·
ต่างจาก Go ที่ go build ฟ้องก่อนได้รัน
เพราะชื่อคือตัวบอกทาง สะกดผิดตัวเดียวก็หาไม่เจอ — เช่นตั้งชื่อ PostController (ขาด s) แทน PostsController
· เวลาพิมพ์ตามสไลด์ให้ดูตัวใหญ่ตัวเล็กและตัว s ท้ายคำให้ดี
- Convention over Configuration
- "ทำตามข้อตกลง แทนการเขียนตั้งค่า" · ตั้งชื่อถูกกฎแล้ว framework เดาส่วนที่เหลือให้ · พิมพ์บอก AI ได้ว่า "follow Rails conventions, don't add extra configuration"
- configuration (config)
- การเขียนบอกโปรแกรมตรง ๆ ว่าอะไรอยู่ไหน เชื่อมกับอะไร · Go ใช้วิธีนี้เป็นหลัก
- the Rails way
- วิธีที่ Rails ปูทางไว้ให้ · ถาม AI ว่า "what is the Rails way to do this?" มักได้คำตอบที่สั้นกว่าถามว่า "how to do this in Rails"
7 · ความเชื่อ 3 ข้อของ Rails และราคาที่ต้องจ่าย
สไลด์หน้า 11 บอกว่า Rails ยืนอยู่บนความเชื่อสามข้อ · ข้อแรกเพิ่งเรียนไป อีกสองข้อสั้นมาก:
| ความเชื่อ | แปลเป็นภาษาคน | ในภาพร้านอาหาร |
|---|---|---|
| Convention over Configuration | ตั้งชื่อตามกฎ แล้วไม่ต้องเขียนตั้งค่า | ทุกสาขาวางของที่เดิม |
| Omakase | Rails เลือกเครื่องมือให้ครบชุดแล้ว ไม่ต้องเลือกเอง | คำญี่ปุ่น แปลว่า "แล้วแต่เชฟ" — สั่งเป็นชุด เชฟคิดมาให้ว่าอะไรเข้ากับอะไร |
| The One Person Framework | คนคนเดียวควรทำเว็บได้ครบ ตั้งแต่ database ถึงหน้าเว็บ | เจ้าของคนเดียวเปิดร้านได้ทั้งร้าน เพราะครัวมาครบ |
ในคอร์สนี้คุณทำ backend Go หนึ่งโปรเจกต์ + frontend Next.js อีกโปรเจกต์ + Keycloak + Redis — แต่ละชิ้นเรียนกันเป็นวัน ๆ
· ความเชื่อข้อ 3 บอกว่าใน Rails ทั้งหมดนี้ควรอยู่ในโปรเจกต์เดียวได้
แต่สไลด์ก็เตือนตรง ๆ ว่าทั้งสามข้อมีราคา (หน้า 12):
สิ่งที่ได้
- เขียนโค้ดน้อยลงมาก
- ของครบในกล่องเดียว ไม่ต้องประกอบเอง
- ทุกโปรเจกต์ Rails โครงเหมือนกัน เปิดของคนอื่นแล้วอ่านออก
- มี "ครัวทดลองหลังร้าน" ลองโค้ดกับแอปจริงได้สด ๆ (บท 104)
สิ่งที่เสีย
- พิมพ์ชื่อผิด รู้ตอนโปรแกรมวิ่งไปถึง ไม่ใช่ก่อนรัน
- หลายอย่างเกิดขึ้นเองโดยไม่เห็นโค้ด พอพังแล้วหาสาเหตุยาก
- กินหน่วยความจำ (RAM) มากกว่า Go
- ฝืนข้อตกลงเมื่อไหร่ เหนื่อยเมื่อนั้น
- งานคำนวณหนัก ๆ หรือต้องตอบเร็วระดับเสี้ยวมิลลิวินาที ไม่ใช่ที่ของมัน
ข้อแรกของฝั่ง "เสีย" คือข้อที่คนมาจาก Go รู้สึกที่สุด — Go ตรวจชื่อทุกตัวก่อนเปิดร้าน ·
Ruby ไปรู้เอาตอนครัววิ่งไปถึงบรรทัดนั้น (เห็นแล้วใน days.rb หัวข้อ 3) ·
คน Rails จึงจริงจังกับการเขียนเทสมาก (Day 4)
- opinionated (framework ที่มีความเห็นของตัวเอง)
- framework ที่ตัดสินใจแทนเราไว้เยอะ ว่าควรตั้งชื่อยังไง วางไฟล์ไว้ไหน ใช้เครื่องมืออะไร · Rails เรียกตัวเองแบบนี้ · ถาม senior ได้ว่า "เรื่องนี้ Rails มีทางที่ตั้งใจไว้แล้วไหม หรือต้องเลือกเอง"
- Omakase
- ชื่อเล่นของแนวคิด "Rails เลือกเครื่องมือมาให้เป็นชุด" · ถาม AI ได้ว่า "what does a new Rails app include by default?" จะได้รายชื่อของในชุดนี้
8 · ของในกล่องที่ Rails ให้มา
สไลด์หน้า 13 ไล่ของ 12 อย่างที่ "เคยต้องเลือกเอง แต่ Rails เลือกมาให้แล้ว" · ไม่ต้องจำชื่อฝั่ง Rails ดูแค่ว่ามีครบ:
| ของในสไลด์ | มันคืออะไร | ใน Go ที่เรียนมา | ใน Rails (เรียนเมื่อไหร่) |
|---|---|---|---|
| router | จับคู่ URL กับคนรับ | Gin (บท 38) | config/routes.rb (บท 101) |
| ORM | ตัวแปลงโค้ดเป็นคำสั่ง database | GORM (บท 27) | Active Record (Day 2) |
| migration tool | ใบสั่งช่างมาต่อเติมตาราง | goose (บท 31) | migration ในโฟลเดอร์ db/migrate/ (Day 2) |
| validator | ตรวจว่าข้อมูลที่ส่งมาครบ ถูกแบบ | tag binding ของ Gin (บท 39) | validations (Day 2) |
| job queue | คิวงานที่ทำทีหลังได้ เช่นส่งอีเมล | คอร์สไม่ได้สอน | Active Job + Solid Queue (Day 3) |
| cache client | จำคำตอบที่ทำแล้ว จะได้ไม่ต้องทำซ้ำ | go-redis (บท 32) | Solid Cache (Day 3) |
| WebSocket | สายที่เปิดค้างไว้ ให้ server ส่งของมาหาเองได้ | คอร์สไม่ได้สอน | Action Cable |
| mailer | ส่งอีเมล | คอร์สไม่ได้สอน | Action Mailer |
| file storage | เก็บไฟล์ที่ผู้ใช้อัปโหลด | คอร์สไม่ได้สอน | Active Storage (Day 3) |
| rich text editor | ช่องพิมพ์ที่ใส่ตัวหนา รูป ลิงก์ได้ | ไม่ใช่งานของ backend | Action Text |
| frontend | ส่วนที่ทำให้หน้าเว็บกดแล้วตอบสนองไว | แยกอีกโปรเจกต์: Next.js (บท 69) | Hotwire / Turbo (Day 3) |
| e2e test | เทสที่เปิดเว็บแล้วกดเหมือนคนจริง | httptest (บท 40) ทดสอบแค่ระดับ handler | system test (Day 4) |
ประโยคปิดของสไลด์คือ ทั้ง 12 อย่างทำงานด้วยกันได้ตั้งแต่วันแรก และอัปเกรดไปพร้อมกัน
ฝั่ง Go ไม่มีใครรับประกันว่า Gin รุ่นใหม่จะไปด้วยกันกับ GORM รุ่นใหม่ — คุณต้องลองเอง
9 · ประวัติสั้น ๆ
- 1995 — Yukihiro Matsumoto ("Matz") ชาวญี่ปุ่น ปล่อย Ruby รุ่นแรก · โจทย์ของเขาคือออกแบบภาษาให้คนเขียนมีความสุข ไม่ใช่ให้เครื่องเร็วที่สุด
- 2004 — David Heinemeier Hansson ("DHH") ทำแอปจัดการโปรเจกต์ชื่อ Basecamp แล้วแกะส่วนที่ใช้ซ้ำได้ออกมาปล่อยฟรี กลายเป็น Rails (สไลด์บอกว่ากรกฎาคม 2004) · Rails จึงเกิดจากแอปที่ใช้งานจริง ไม่ได้ออกแบบลอย ๆ บนกระดาษ
- วันนี้ — สไลด์อ้างว่า GitHub · Shopify · GitLab ยังรันบน Rails · และเล่าว่า Twitter ไม่ได้ "เลิกใช้ Rails เพราะล่ม" แต่ย้ายเฉพาะส่วนที่หนักที่สุดออกไปเขียนด้วยภาษาอื่น
ข้อคิดของสไลด์หน้า 10 ที่ควรจำ: ระบบใหญ่แทบไม่มีระบบไหนใช้ภาษาเดียวทั้งหมด · คำถามที่ถูกคือ "ส่วนไหนของระบบเหมาะกับ Rails" ไม่ใช่ "Rails ดีหรือไม่ดี"
10 · แผนที่ 16 บทของ Day 1
Day 1 แบ่งเป็นสองช่วง · ช่วงแรกเรียนภาษา Ruby ให้พออ่านโค้ด Rails ออก · ช่วงหลังเปิดร้าน Rails จริง แล้วเดินดูทีละห้อง
| บท | เรื่อง | ในภาพร้านอาหาร |
|---|---|---|
| 90 | Rails คืออะไร (บทนี้) | ดูแผนผังร้านทั้งร้าน |
| ช่วงที่ 1 · ภาษา Ruby | ||
| 91 | ติดตั้ง Ruby · ตัวแปร · object · string | หัดพูดภาษาของครัว |
| 92 | Array · Hash · Symbol · map / select | จัดของหลายชิ้นลงกล่อง |
| 93 | Method และ Block | เขียนสูตรอาหารของตัวเอง |
| 94 | อะไรนับว่า "จริง" · nil | เช็คว่าของในตู้มีจริงไหม |
| 95 | Class และ Module | ทำแม่พิมพ์ของเอง |
| 96 | เมื่อโค้ดพัง · rescue | ของหกในครัว รับมือยังไง |
| 97 | 5 ไวยากรณ์ที่ Rails ใช้บ่อย | ศัพท์ในใบสั่งของแฟรนไชส์ |
| ช่วงที่ 2 · Rails | ||
| 98 | rails new · โฟลเดอร์ในโปรเจกต์ | รับมอบร้านแฟรนไชส์ เดินดูทีละห้อง |
| 99 | กฎตั้งชื่อ Post ↔ posts | ป้ายชื่อที่ทุกสาขาใช้เหมือนกัน |
| 100 | เปิด server · request เดินทางยังไง | เปิดร้าน แล้วตามใบสั่งใบแรก |
| 101 | Routing | พนักงานต้อนรับ |
| 102 | Controller · params | หัวหน้าเชฟ + รายละเอียดในใบสั่ง |
| 103 | View · ERB · layout | จัดจาน + จานรองแบบเดียวกันทุกจาน |
| 104 | debugger · console | หยุดเวลาในครัว · ครัวทดลองหลังร้าน |
| 105 | Lab: สร้างแอปเอง + หน้า /about | เปิดร้านเองตั้งแต่ต้นจนเสิร์ฟจานแรก |
ส่วนของ database จริง (เก็บบทความลงตาราง · เพิ่ม/แก้/ลบ) รออยู่ Day 2 · ระหว่าง Day 1 ถ้าเจอคำว่า Active Record ให้รู้แค่ว่า "คือห้องเก็บวัตถุดิบ เดี๋ยวได้เรียน"
ภารกิจ — เล่าเส้นทางของใบสั่งด้วยคำของตัวเอง
🎯 ภารกิจ (ประมาณ 10 นาที)
ส่วน A — ทุกคนทำได้ ไม่ต้องติดตั้งอะไร
- เปิดกระดาษหรือโน้ตเปล่า
- เขียนเส้นทางของใบสั่ง "ขอหน้า
/posts" (หน้ารวมบทความทั้งหมด) เป็นขั้น ๆ 5 ขั้น แบบหัวข้อ 5 · แต่ละขั้นเขียนทั้งชื่อในร้านอาหารและชื่อใน Rails - ตอบเพิ่มหนึ่งข้อ: ขั้นที่ไปหยิบของจากตู้ หยิบแฟ้มกี่แผ่น — ต่างจากใบสั่ง
/posts/42ยังไง
ส่วน B — ถ้าเครื่องคุณมี Ruby ของคอร์สแล้ว (ยังไม่มีก็ข้ามได้ บท 91 พาติดตั้ง)
- เปิด terminal (โปรแกรมที่ให้พิมพ์คำสั่ง — บท 91 อธิบาย) พิมพ์
ruby -v· ควรเห็นเลขขึ้นต้นด้วยruby 4.0. - สร้างไฟล์
first.rbด้วยโปรแกรมแก้ข้อความที่ใช้อยู่ (เช่น VS Code) พิมพ์ให้เหมือนกล่องโค้ดในหัวข้อ 3 ครบทั้งสามบรรทัด (รวมบรรทัด# first.rb) เซฟ แล้วรันruby first.rbในโฟลเดอร์เดียวกับไฟล์ · ควรเห็นCBAตามด้วยhello5 บรรทัด - เพิ่มบรรทัดที่สี่
puts 3.days.agoต่อท้าย แล้วรันอีกที · ควรเห็นCBAกับhelloก่อน แล้วค่อยพังด้วยNoMethodErrorที่บรรทัด 4 (ถ้าคุณไม่ได้ใส่บรรทัด comment เลขจะเป็น 3 แทน — ไม่ผิด)
ถ้าเจอ error นี้ แปลว่า…
command not found: ruby— เครื่องยังไม่มี Ruby เลย · ไปบท 91ruby -vได้ruby 2.6.10p210 …— นี่คือ Ruby รุ่นเก่าที่ติดมากับ macOS ไม่ใช่ตัวของคอร์ส · ไปบท 91 ก่อน อย่าใช้ตัวนี้เรียนundefined method 'days' for an instance of Integer (NoMethodError)ตอนเพิ่มบรรทัด3.days.ago— ถูกต้องแล้ว ไม่ใช่เครื่องคุณเสีย · Ruby เปล่าไม่รู้จักdays(หัวข้อ 3)
ส่วน A — ใช้คำของคุณเองได้ ขอแค่ลำดับถูก:
- ลูกค้าส่งใบสั่ง = browser ส่ง request
GET /posts - พนักงานต้อนรับ = routes ดูแล้วชี้ว่า "แผนกบทความ เมนูดูทั้งหมด" (ใน Rails เมนูนี้ชื่อ
index— ได้เรียนบท 101) - หัวหน้าเชฟแผนกบทความ =
PostsControllerรับงาน แล้วขอบทความทั้งหมดจากห้องเก็บของ - ห้องเก็บวัตถุดิบ = model
Postหยิบแฟ้มทุกแผ่นในลิ้นชักposts - จัดจานแล้วเสิร์ฟ = view ใน
app/views/posts/ทำหน้า HTML ที่มีรายการบทความ แล้วส่งกลับเป็น response
ข้อ 3: /posts หยิบทุกแผ่น · /posts/42 หยิบแผ่นเดียว ·
สังเกตว่าใบสั่ง /posts ไม่มีเลขบทความ จึงไม่มี "เลข 42" ในรายละเอียดใบสั่ง (params)
ส่วน B — ผลรันจริงของไฟล์สี่บรรทัด (รันจาก terminal):
$ ruby first.rb
CBA
hello
hello
hello
hello
hello
first.rb:4:in '<main>': undefined method 'days' for an instance of Integer (NoMethodError)
puts 3.days.ago
^^^^^
สองบรรทัดคำสั่งแรกทำงานไปแล้วก่อนจะพังที่บรรทัด 4 — Ruby อ่านไปรันไปทีละบรรทัด ·
ถ้าเป็น Go ทั้งไฟล์จะไม่ได้รันเลยสักบรรทัด เพราะ go build ฟ้องก่อน ·
นี่คือ "พิมพ์ผิด รู้ตอนวิ่งไปถึง" ในหัวข้อ 7 เวอร์ชันที่เห็นกับตา
ลองตอบดู
ข้อไหนอธิบายความสัมพันธ์ของ Ruby กับ Rails ได้ถูกที่สุด?
ตัวเลือกที่ 2 ถูก · Ruby เป็นภาษาแบบที่ Go เป็นภาษา · Rails เป็น framework แบบที่ Gin เป็น framework (แต่ให้มาครบกว่ามาก)
ตัวเลือกที่ 1 ผิด เพราะ Rails ไม่ได้มีไวยากรณ์ใหม่ — มันคือโค้ด Ruby ธรรมดาชุดหนึ่ง ·
ตัวเลือกที่ 3 ผิด เพราะ Ruby เขียนโปรแกรมที่ไม่ใช่เว็บได้โดยไม่มี Rails เลย (เช่น first.rb ในหัวข้อ 3) ·
ตัวเลือกที่ 4 ผิด เพราะคนรันไฟล์ .rb คือคำสั่ง ruby เสมอ ทั้งในและนอกแอป Rails
จุดที่ต้องจำ: Ruby = ภาษา (เหมือน Go) · Rails = framework ที่เขียนด้วย Ruby (เหมือน Gin แต่ครบชุด)
ไฟล์ที่มีบรรทัดเดียว puts 3.days.ago รันด้วย ruby เปล่า ๆ แล้วได้ NoMethodError เพราะอะไร?
ตัวเลือกที่ 4 ถูก · ผลรันจริงคือ undefined method 'days' for an instance of Integer ·
พอสั่ง ruby -ractive_support/all days.rb (โหลด Active Support มาก่อน) ไฟล์เดิมก็ได้วันที่ออกมา
ตัวเลือกที่ 1 ผิด เพราะ 5.times ในหัวข้อ 3 ใช้จุดกับตัวเลขได้ปกติ ·
ตัวเลือกที่ 2 ผิด เพราะ 3.days.ago เขียนถูกแล้ว ใช้ได้ทันทีเมื่อมี Active Support ·
ตัวเลือกที่ 3 ผิด เพราะลองรันจริงแล้วพังทั้ง Ruby 4.0.9 ที่คอร์สใช้ และ Ruby 2.6 ที่ติดมากับ macOS — ปัญหาไม่ได้อยู่ที่รุ่น แต่อยู่ที่ยังไม่ได้โหลด Active Support
จุดที่ต้องจำ: โค้ดที่อ่านเป็นภาษาอังกฤษสวย ๆ ไม่ได้แปลว่าเป็นของ Ruby เสมอ · สงสัยเมื่อไหร่ให้รันด้วย ruby เปล่า ๆ
ใบสั่ง "ขอดูบทความที่ 42" เดินผ่านฝ่ายต่าง ๆ ในร้าน Rails ตามลำดับไหน?
ตัวเลือกที่ 1 ถูก · พนักงานต้อนรับ (routes) ต้องดูใบสั่งก่อนว่าแผนกไหนรับ → หัวหน้าเชฟ (controller) สั่งงาน → ห้องเก็บของ (model) หยิบแฟ้มเลข 42 → จัดจาน (view) → เสิร์ฟ
ตัวเลือกที่ 2 ผิด เพราะ controller ไม่รู้ว่าใบสั่งเป็นของตัวเองจนกว่า routes จะส่งมาให้ · ตัวเลือกที่ 3 ผิด เพราะ model ไม่รู้ว่าต้องหยิบแฟ้มไหน ถ้ายังไม่มีใครอ่านใบสั่ง · ตัวเลือกที่ 4 ผิด เพราะจัดจานก่อนมีของไม่ได้ — view ต้องรอข้อมูลบทความก่อน
จุดที่ต้องจำ: เส้นทางมาตรฐานคือ routes → controller → model → view · หน้าเว็บผิดตรงไหน ให้ไล่หาตามเส้นนี้
ในแอปบล็อก คำไหนหมายถึง "แม่พิมพ์ของบทความ" — ตัวบอกว่าบทความหนึ่งชิ้นมีช่องอะไรบ้าง?
ตัวเลือกที่ 3 ถูก · Post ตัวใหญ่เอกพจน์คือ model — เทียบได้กับ type Post struct ใน Go
ตัวเลือกที่ 1 ผิด เพราะ posts คือลิ้นชัก (ตาราง) ที่เก็บแฟ้มหลายแผ่น ไม่ใช่แม่พิมพ์ ·
ตัวเลือกที่ 2 ผิด เพราะ /posts เป็นแค่ที่อยู่ที่ลูกค้าพิมพ์ ·
ตัวเลือกที่ 4 ผิด เพราะ controller คือหัวหน้าเชฟที่สั่งงาน ไม่ได้เป็นคนกำหนดว่าบทความมีช่องอะไร
จุดที่ต้องจำ: ตัวใหญ่เอกพจน์ (Post) = แม่พิมพ์ · ตัวเล็กพหูพจน์ (posts) = ที่เก็บหลายชิ้น หรือ URL
💬 ติดตรงไหน ถามกลับได้เลย — บทนี้เป็นแผนที่ของทั้ง 16 บท ถ้าแผนที่ยังไม่ชัด บทต่อ ๆ ไปจะงงตาม · ลองถามแบบ "ในร้านอาหาร X คือใคร" มาได้ทุกคำ
ถ้าเปิดสไลด์ดูประกอบ
| หน้า | สไลด์เขียน | ของจริง | ต้องสนใจไหม |
|---|---|---|---|
| 3 | Post.where(published: true).order(:title) เป็นตัวอย่างโค้ด Rails | พิมพ์ลงไฟล์แล้วรันด้วย ruby เปล่า ๆ จะพัง uninitialized constant Post (NameError) · ใช้ได้เฉพาะในแอป Rails ที่มี model Post แล้ว (Day 2) | ไม่ต้องพิมพ์ตาม แค่ดูหน้าตา |
| 4 | วาง 3.days.ago · 1.megabyte ปนกับตัวอย่าง Ruby ล้วน | สองบรรทัดนี้เป็นของ Active Support (Rails) · Ruby เปล่าพัง NoMethodError | สนใจ — คือหัวข้อ 3 ของบทนี้ |
| 4 | if user.admin? … grant_access … end | เป็นท่อนที่ตัดมาให้ดู ไม่มีการประกาศ user · รันเฉย ๆ พัง undefined local variable or method 'user' | ไม่ต้องพิมพ์ตาม |
| 7 | Ruby อายุ 30 ปี · Rails อายุ 21 ปี | นับถึงปี 2026 ได้ 31 กับ 22 ปี | ไม่ต้องสนใจ |
| 9 | ตัวเลขของ Shopify ช่วง Black Friday · GitHub มีโค้ด Rails เกือบ 2 ล้านบรรทัด | ยังยืนยันกับแหล่งต้นทางไม่ได้ ถือเป็น "สไลด์อ้าง" | ไม่ต้องจำ |
| 1 | Ruby 4.0.6 · Rails 8.1.3 | บทเรียนตรวจด้วย Ruby 4.0.9 · Rails 8.1.4 (สายเดียวกัน) | ไม่ต้องสนใจ |
อ้างอิง
- Rails Guides — Getting Started: Rails Philosophy — อ่านแค่หัวข้อ 2 (สั้นไม่ถึงหนึ่งหน้าจอ) · เป็นคำอธิบายทางการของ "Rails is opinionated software" และ Convention Over Configuration ในหัวข้อ 6–7
- Rails Guides — Model-View-Controller Basics — อ่านหัวข้อ 3.4 (ห้าบรรทัด) แล้วกระโดดไปหัวข้อ 8 A Request's Journey Through Rails · คือเส้นทาง route → controller → view แบบเดียวกับหัวข้อ 5 ของบทนี้ เขียนโดยทีม Rails เอง
- Rails Guides — Active Support Core Extensions: Stand-Alone Active Support —
อ่านหัวข้อ 1.1 กับ 1.2 · 1.1 อธิบายการโหลด
active_support/allซึ่งคือสิ่งที่-ractive_support/allในหัวข้อ 3 ทำ · 1.2 บอกว่าในแอป Rails โหลดให้เองอยู่แล้ว - About Ruby (ruby-lang.org) — อ่านหัวข้อ The Ideals of Ruby's Creator · เป็นที่มาของโจทย์ "ภาษาที่ให้คนเขียนมีความสุข" ในหัวข้อ 9
- Ruby FAQ — What is the history of Ruby? — อ่านหัวข้อนี้หัวข้อเดียว · ยืนยันว่า Ruby 0.95 ออกธันวาคม 1995 ตามสไลด์หน้า 4 และ 7
- Ruby From Other Languages — ยังไม่ต้องอ่านตอนนี้ · เก็บไว้อ่านระหว่างบท 91–96 เป็นหน้าที่เขียนให้คนมาจากภาษาอื่นโดยเฉพาะ