🔁 ทวนก่อนเริ่ม — บทนี้ใช้คำจากบทก่อน ๆ ต่อไปนี้
nil(บท 91) — ค่าพิเศษของ Ruby ที่แปลว่า "ไม่มีค่า" · บทนี้จะเจาะลึกมันputsกับp(บท 93) —putsพิมพ์ข้อความเฉย ๆ ·pพิมพ์ให้เห็นหน้าตาจริง เช่นเห็นเครื่องหมาย""- Hash กับ symbol (บท 92) —
post = { title: "…", views: 0 }คือแฟ้มหนึ่งแผ่นที่มีช่องติดป้าย (บทความจำลองหนึ่งชิ้น) · หยิบของด้วยpost[:views] - method ที่ลงท้ายด้วย
?(บท 93) — method ที่ตอบกลับเป็นtrueหรือfalseเช่น5.even? - block
do |v| … end(บท 93) — ก้อนโค้ดที่ส่งให้ method ไปทำซ้ำกับของทีละชิ้น
🧰 เวอร์ชันที่ใช้ตรวจ: Ruby 4.0.9 · Active Support 8.1.4 (มากับ Rails 8.1.4) · Go 1.26.5 — สไลด์ใช้ Ruby 4.0.6 · Rails 8.1.3 เรื่องในบทนี้ไม่ต่างกัน · ผลรันทุกกล่องก็อปมาจากเครื่องจริง
1 · if ใน Ruby ถามแค่ว่า "ผ่านไหม"
if มีหน้าที่เดียว: ดูค่าที่วางไว้ข้างหลัง แล้วตัดสินว่า "ทำโค้ดข้างในไหม"
แต่ละภาษาตั้งกฎการตัดสินไม่เหมือนกัน
Go เข้มที่สุด — ข้างหลัง if ต้องเป็น bool (true/false) เท่านั้น
ลองเขียน if views ทั้งที่ views เป็นตัวเลข Go ไม่ยอม compile เลย:
// main.go
package main
import "fmt"
func main() {
views := 0
if views {
fmt.Println("มีคนอ่านแล้ว")
}
}
$ go run main.go
# command-line-arguments
./main.go:8:5: non-boolean condition in if statement
แปลว่า "เงื่อนไขของ if ไม่ใช่ bool" · ใน Go คุณจึงต้องเขียนเต็ม ๆ เสมอ เช่น if views > 0 ·
if title != "" · if err != nil — ยาว แต่ไม่มีทางเข้าใจผิด
Ruby ใจดีกว่า — ใส่อะไรไว้หลัง if ก็ได้ แล้ว Ruby ใช้กฎสั้น ๆ ข้อเดียว:
มีแค่ 2 ค่าที่ "ไม่ผ่าน": nil กับ false · ค่าอื่นทุกค่า "ผ่าน" หมด
— รวมถึง 0 และสตริงว่าง "" ที่ดูเหมือนว่างเปล่าด้วย
นึกถึงประตูหน้าร้านที่มี รปภ. ยืนเฝ้า และได้รับรายชื่อ "ห้ามเข้า" มาแค่ 2 ชื่อ: nil กับ false
ใครไม่อยู่ในรายชื่อนี้ รปภ. ให้เข้าหมด ไม่สนว่าหน้าตาเป็นยังไง —
เลข 0 เข้าได้ · กระดาษเปล่า "" เข้าได้ · กล่องเปล่า [] ก็เข้าได้
ที่ไม่เหมือนกัน: รปภ. จริงยังพอดูหน้าตาคนได้ แต่ Ruby ไม่ดูข้างในเลย —
แม้แต่สตริง "false" หรือ "0" ที่อ่านแล้วเหมือนแปลว่า "ไม่" ก็ผ่าน
เพราะมันเป็นแค่ข้อความ ไม่ใช่ค่า false ตัวจริง
มาลองให้เห็นกับตา · โค้ดข้างล่างเอาค่า 8 ตัวมาผ่านประตู if ทีละตัว:
# truth.rb
[nil, false, 0, "", " ", [], "false", "0"].each do |v|
if v
puts "#{v.inspect} → เข้า if"
else
puts "#{v.inspect} → ไม่เข้า"
end
end
อ่านทีละบรรทัด:
[ … ]คือ array (บท 92) ใส่ค่า 8 ตัว ·" "คือสตริงที่มีช่องว่าง 1 ตัว ไม่ใช่สตริงว่าง.each do |v| … endคือ "หยิบทีละตัว ตั้งชื่อเรียกว่าvแล้วทำโค้ดข้างใน" (บท 93)if v— สังเกตว่าไม่มี> 0หรือ!= nilต่อท้าย · ถามแค่ว่าvผ่านประตูไหมv.inspectแปลงค่าเป็นข้อความแบบที่pแสดง — จะได้เห็น""ชัด ๆ ไม่ใช่บรรทัดว่าง
ก่อนกดเฉลย จดลงกระดาษ: 8 ค่านี้ ตัวไหนเข้า if บ้าง?
$ ruby truth.rb
nil → ไม่เข้า
false → ไม่เข้า
0 → เข้า if
"" → เข้า if
" " → เข้า if
[] → เข้า if
"false" → เข้า if
"0" → เข้า if
ไม่เข้าแค่ 2 บรรทัดแรก ตรงตามกฎ · ถ้าคุณทายว่า 0 หรือ "" ไม่เข้า ไม่ต้องตกใจ —
ภาษาอื่นอย่าง JavaScript/TypeScript ถือว่าสองตัวนี้ "ไม่ผ่าน" จริง ๆ
คนที่เคยเขียนภาษาพวกนั้นมาจึงพลาดตรงนี้บ่อยที่สุด
- truthy / falsy
- ค่าที่ "ผ่าน" if เรียกว่า truthy · ค่าที่ "ไม่ผ่าน" เรียกว่า falsy · ใน Ruby falsy มีแค่
nilกับfalse· ถาม senior ได้ว่า "ทีมนี้ยอมให้เขียนif countลอย ๆ ไหม หรือต้องเขียนcount.positive?ให้ชัด" - truthiness
- ชื่อเรียกของ "กฎว่าค่าไหนนับว่าจริงเมื่อเอาไปใส่ใน if" · พิมพ์บอก AI ได้ว่า "Check this Ruby code for places that assume 0 or empty string is falsy" — AI จะรู้ทันทีว่าต้องหาอะไร
2 · บั๊กที่เจอจริง: บทความที่มีคนอ่าน 0 ครั้ง
ทวนแอปตัวอย่างของคอร์สก่อน: สไลด์สร้างแอป บล็อก ชื่อ wongnok_blog ·
ของหลักในแอปคือ บทความ (ภาษาอังกฤษ post) ·
สมมติว่าบทความแต่ละชิ้นมีตัวเลข views = จำนวนครั้งที่มีคนเปิดอ่าน
เราอยากโชว์ป้าย "มีคนอ่านแล้ว" เฉพาะบทความที่มีคนอ่าน ·
ตอนนี้ยังไม่มี Rails เลยจำลองบทความหนึ่งชิ้นด้วย Hash ไปก่อน
(ของจริงใน Rails จะเป็น object ชื่อ Post — เจอใน Day 2):
# views.rb
# จำลองบทความ 1 ชิ้นด้วย Hash (ยังไม่มี Rails)
post = { title: "ข้าวมันไก่ประตูน้ำ", views: 0 }
if post[:views]
puts "มีคนอ่านแล้ว #{post[:views]} ครั้ง"
end
$ ruby views.rb
มีคนอ่านแล้ว 0 ครั้ง
นี่คือบั๊ก — ยังไม่มีใครอ่านเลย แต่ขึ้นป้ายว่ามีคนอ่าน ·
เหตุผลคือ post[:views] ได้ 0 และ 0 ผ่านประตู (มันไม่ใช่ nil ไม่ใช่ false) ·
ไม่มี error ไม่มีคำเตือน ทำให้บั๊กแบบนี้รอดไปถึงมือผู้ใช้ได้ง่าย
ทางแก้: ถามให้ตรงกับที่อยากรู้ · เราอยากรู้ว่า "มากกว่า 0 ไหม"
ก็ใช้ method positive? ของตัวเลข (ลงท้าย ? = ตอบ true/false ตามบท 93):
# views2.rb
post = { title: "ข้าวมันไก่ประตูน้ำ", views: 0 }
if post[:views].positive?
puts "มีคนอ่านแล้ว #{post[:views]} ครั้ง"
else
puts "ยังไม่มีใครอ่าน"
end
p 0.positive?
p 5.positive?
p 0.zero?
$ ruby views2.rb
ยังไม่มีใครอ่าน
false
true
true
positive?= "มากกว่า 0 ไหม" ·0.positive?ได้falseเลยไม่ขึ้นป้ายzero?= "เท่ากับ 0 ไหม" · ใช้ตอนอยากรู้ว่า "ยังไม่มีใครอ่าน"- เทียบ Go:
post[:views].positive?ก็คือif post.Views > 0ที่คุณเขียนใน Go ทุกวันนั่นเอง
⚠️ สไลด์หน้า 26 เขียนตัวอย่างเป็น if @post.views — ถ้าพิมพ์ตามในไฟล์ .rb เปล่า ๆ จะพังทันที
ด้วย undefined method 'views' for nil (NoMethodError) เพราะ @post ยังไม่มีค่า
(@post คือตัวแปรที่ controller เตรียมไว้ให้หน้าเว็บ — เจอในบท 102) · ฝึกตอนนี้ให้ใช้ Hash แบบข้างบนแทน
3 · nil คือกล่องว่าง — และเรียก method ใส่กล่องว่างแล้วพัง
nil แปลว่า "ไม่มีค่า" · ใน Rails คุณจะเจอมันบ่อยมาก เช่น บทความที่ไม่มีคนใส่ชื่อผู้เขียน ·
หรือหยิบช่องที่ไม่มีอยู่ใน Hash:
# nil1.rb
post = { title: "ข้าวมันไก่ประตูน้ำ", author: nil }
p post[:author] # ใส่ไว้ว่า nil
p post[:likes] # ไม่มีช่องนี้เลย
p post[:author].nil?
p post[:title].nil?
$ ruby nil1.rb
nil
nil
true
false
- หยิบช่องที่ไม่มี (
:likes) Ruby ไม่ error แต่คืนnilให้เงียบ ๆ .nil?= "เป็นค่าว่างไหม" · ตอบtrueเฉพาะกับnilตัวเดียว- ถ้าอยากถามกลับด้าน ให้วาง
!(แปลว่า "ไม่") ไว้ข้างหน้า ·!post[:title].nil?= "ไม่ใช่ค่าว่าง"
ปัญหาเกิดตอนเราลืมว่าค่าอาจเป็น nil แล้วสั่งมันทำงาน ·
เช่น อยากได้ชื่อผู้เขียนเป็นตัวพิมพ์ใหญ่ด้วย .upcase:
# nil2.rb
post = { title: "ข้าวมันไก่ประตูน้ำ", author: nil }
puts post[:author].upcase
$ ruby nil2.rb
nil2.rb:4:in '<main>': undefined method 'upcase' for nil (NoMethodError)
puts post[:author].upcase
^^^^^^^
อ่าน error นี้ให้เป็น: "บรรทัด 4 · ไม่มี method ชื่อ upcase สำหรับ nil" ·
ลูกศร ^^^^^^^ ชี้ตรงจุดที่พัง · แปลเป็นภาษาคนคือ "คุณสั่งกล่องว่างให้ทำงาน"
นี่คือ error ที่คุณจะเห็นบ่อยที่สุดในชีวิตการเขียน Rails
เทียบ Go: คุณเคยเจอเรื่องเดียวกันแล้ว — pointer ที่เป็น nil แล้วไปอ่านช่องข้างใน
Go ก็พัง (panic) เหมือนกัน ·
ในโค้ดข้างล่าง *Author คือ pointer (ตัวชี้) ไปหา struct Author · ถ้ายังไม่ได้ชี้ไปไหน มันเป็น nil:
// main.go
package main
import "fmt"
type Author struct {
Name string
}
type Post struct {
Title string
Author *Author
}
func main() {
post := Post{Title: "ก๋วยเตี๋ยวเรือ"} // ไม่ได้ใส่ Author → เป็น nil
if post.Author != nil {
fmt.Println("ผู้เขียน:", post.Author.Name)
} else {
fmt.Println("ไม่มีผู้เขียน")
}
fmt.Println(post.Author.Name) // ไม่เช็คก่อน → พัง
}
$ go run main.go
ไม่มีผู้เขียน
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x10306f634]
goroutine 1 [running]:
… (ตามด้วยรายละเอียดอีกหลายบรรทัด · เลข pc=… ในเครื่องคุณจะไม่เหมือนนี้)
ช่วงแรกที่เช็ค != nil ก่อน ทำงานปกติ · บรรทัดสุดท้ายที่ไม่เช็ค พังทันที —
Ruby กับ Go เหมือนกันตรงนี้ ต่างกันแค่ชื่อ error
ในร้านอาหาร พ่อครัวสั่งลูกมือว่า "หยิบกล่องผักชีมา แล้วล้างผักชีในกล่อง" ·
ถ้ากล่องว่าง (nil) ลูกมือก็ทำต่อไม่ได้ ยืนงงแล้วหยุดงานทั้งครัว — นั่นคือ NoMethodError
.nil? คือการ "เปิดฝาดูก่อนว่ากล่องว่างไหม" ซึ่งทำได้เสมอ ไม่มีวันพัง
ที่ไม่เหมือนกัน: กล่องว่างในครัวมีหลายใบ แต่ nil ใน Ruby มีใบเดียวทั้งโปรแกรม ·
และมันไม่ได้ "ไม่มีอะไรเลย" จริง ๆ — มันเป็นค่าที่ตอบคำถามง่าย ๆ ได้บางข้อ เช่น nil.nil? ได้ true ·
สิ่งที่ทำไม่ได้คือคำสั่งของค่าชนิดอื่น เช่น upcase ของสตริง
- nil
- ค่า "ไม่มีค่า" ของ Ruby มีตัวเดียว (เทียบได้กับ
nilของ Go) · ถาม AI ได้ว่า "Where can this variable become nil?" — ได้คำตอบที่ตรงต้นเหตุกว่าถามว่า "แก้ error นี้ยังไง" - NoMethodError
- error ที่เกิดเมื่อสั่ง method ที่ค่านั้นไม่มี · แบบที่เจอบ่อยสุดคือ
undefined method '…' for nil· บอก senior ได้ว่า "ตัวแปรนี้เป็น nil ตอนเรียก.upcaseครับ กำลังหาว่า nil มาจากไหน"
4 · &. — "ถ้ามีค่อยเรียก"
ใน Go เราป้องกัน nil ด้วย if post.Author != nil { … } ทุกครั้ง ·
Ruby มีทางลัดให้ คือเขียน &. แทนจุดธรรมดา:
# safe.rb
post1 = { title: "ข้าวมันไก่ประตูน้ำ", author: "somchai" }
post2 = { title: "ก๋วยเตี๋ยวเรือ", author: nil }
p post1[:author]&.upcase
p post2[:author]&.upcase
$ ruby safe.rb
"SOMCHAI"
nil
&.อ่านว่า "ถ้าทางซ้ายมีค่า ก็เรียก method ทางขวา · ถ้าทางซ้ายเป็น nil ก็ข้ามไป ได้ nil กลับมา"post1มีผู้เขียน → เรียกupcaseได้"SOMCHAI"post2ผู้เขียนเป็น nil → ข้าม ไม่พัง ได้nil
จุดธรรมดา . คือลูกมือที่ได้ยินว่า "ล้างผักชีในกล่อง" แล้วลงมือทันทีโดยไม่ดู — กล่องว่างก็ยืนงง หยุดทั้งครัว ·
&. คือลูกมือที่เปิดฝาดูก่อน: มีผักชีก็ล้างให้ · กล่องว่างก็วางคืนแล้วบอกว่า "ว่างครับ" (ได้ nil) งานไม่สะดุด
ที่ไม่เหมือนกัน: ลูกมือจริงอาจวิ่งไปหาผักชีมาเติม หรือบอกว่าทำไมกล่องถึงว่าง ·
แต่ &. ไม่เติมของให้และไม่บอกเหตุผลเลย — ส่งแค่ nil กลับมาเงียบ ๆ
วางโค้ด Go กับ Ruby ที่ทำเรื่องเดียวกันไว้ข้างกัน — "ถ้ามีผู้เขียน ค่อยเอาชื่อมาทำเป็นตัวพิมพ์ใหญ่":
Go — เช็คเองทุกครั้ง
name := ""
if post.Author != nil {
name = strings.ToUpper(post.Author.Name)
}
Ruby — ใช้ &.
name = post[:author]&.upcase
ความคิดเดียวกัน แต่ Ruby เหลือบรรทัดเดียว ·
ต่างกันตรงที่ถ้าไม่มีผู้เขียน ฝั่ง Go name ยังเป็น "" ที่เราตั้งไว้ ส่วนฝั่ง Ruby name ได้ nil
ข้อควรระวัง: &. ไม่ได้ทำให้ค่าว่างหายไป แค่เลื่อนปัญหาไปข้างหน้า ·
ได้ nil มาแล้วถ้าบรรทัดถัดไปเอาไปใช้ต่อโดยไม่เช็ค ก็พังอยู่ดี ·
ถ้าค่านั้น "ไม่ควรเป็น nil ตั้งแต่แรก" ให้ไปแก้ที่ต้นทางดีกว่าโรย &. ทุกจุด
- safe navigation operator (
&.) - ชื่อทางการในเอกสาร Ruby ของ "จุดที่เปิดฝาดูก่อน" ·
ถาม senior ได้ว่า "ตรงนี้ควรใช้
&.หรือควรทำให้ค่าไม่มีวันเป็น nil ตั้งแต่ต้นครับ"
5 · ||= — "ถ้ายังว่างค่อยใส่"
บ่อยครั้งเราอยากใส่ค่าเริ่มต้นให้ตัวแปร แต่เฉพาะตอนที่มันยังว่างอยู่ ·
ใน Go คุณเขียนแบบนี้ (slice ที่ยังไม่ได้ใส่อะไรจะเป็น nil):
// main.go
package main
import "fmt"
func main() {
var tags []string // ยังไม่ได้ใส่อะไร = nil
if tags == nil {
tags = []string{"ทั่วไป"}
}
fmt.Println(tags)
}
$ go run main.go
[ทั่วไป]
Ruby ย่อทั้งก้อน if นั้นเหลือเครื่องหมายเดียว ||=:
# orq.rb
tags = nil
tags ||= ["general"]
p tags
tags = ["food"]
tags ||= ["general"]
p tags
$ ruby orq.rb
["general"]
["food"]
tags ||= ["general"]อ่านว่า "ถ้า tags ยังไม่ผ่านประตู (เป็น nil หรือ false) ให้ใส่["general"]· ถ้ามีค่าแล้วก็ปล่อยไว้"- รอบแรก
tagsเป็น nil → ได้ค่าเริ่มต้น · รอบสองtagsมี["food"]อยู่แล้ว → ไม่ถูกแตะ ||แปลว่า "หรือ" ·a ||= bจึงอ่านได้ว่า "a หรือไม่งั้นก็ใส่ b ให้ a" — ถ้า a ผ่านประตูก็จบแค่นั้น ไม่งั้นเอา b ใส่ลงไปใน a
พนักงานเดินรอบร้าน ถือเหยือกน้ำ กฎคือ "แก้วไหนว่าง เติมให้ · แก้วไหนมีน้ำอยู่ ไม่ต้องยุ่ง" —
นี่คือ ||= เป๊ะ ๆ
ที่ไม่เหมือนกัน: พนักงานจริงเห็นแก้วที่ลูกค้าคว่ำไว้ (แปลว่า "ไม่เอาแล้ว") ก็จะไม่เติม ·
แต่ ||= มองไม่ออก — ค่า false ที่ตั้งใจใส่ไว้ว่า "ไม่" ถูกนับเป็น "แก้วว่าง" แล้วถูกเติมทับ ·
ส่วนแก้วที่มีน้ำแข็งก้อนเดียว (0) พนักงานจริงอาจเติม แต่ ||= ไม่เติม เพราะ 0 ผ่านประตู
ลองทายสองกรณีในท่อน "ที่ไม่เหมือนกัน" ข้างบน:
# orq2.rb
views = 0
views ||= 99
p views
published = false # ตั้งใจว่า "ยังไม่เผยแพร่"
published ||= true
p published
สองบรรทัดที่ p ออกมาคืออะไร?
$ ruby orq2.rb
0
true
views ยังเป็น 0 เพราะ 0 ผ่านประตู → ||= ถือว่ามีค่าแล้ว ไม่แตะ ✅
published กลายเป็น true — นี่คือกับดัก ·
เราตั้งใจใส่ false แต่ ||= เห็นว่า false ไม่ผ่านประตู เลยเขียนทับ ·
ในเว็บจริงหน้าตาจะเป็น "ผู้ใช้ปิดการแจ้งเตือนไว้ แต่ระบบเปิดกลับให้เองทุกครั้ง"
ทางแก้คือถามให้ตรง ๆ ว่า "ยังเป็น nil อยู่ไหม" แทน:
# orq3.rb
published = false
published = true if published.nil?
p published
draft = nil
draft = true if draft.nil?
p draft
$ ruby orq3.rb
false
true
คำสั่ง if เงื่อนไข คือการเขียน if ไว้ท้ายบรรทัด แปลว่า "ทำคำสั่งนี้ ถ้าเงื่อนไขจริง" — สั้นกว่าเขียน if … end สามบรรทัด ·
false.nil? ได้ false จึงไม่เขียนทับ · ส่วน nil.nil? ได้ true จึงใส่ค่าเริ่มต้นให้
ในโค้ด Rails คุณจะเจอ ||= บ่อยมากในรูป @current_user ||= …
(@ คือตัวแปรที่ติดอยู่กับ object — เจอละเอียดในบท 95) ·
ความหมายเดิมทุกอย่าง: "ถ้ายังไม่เคยหาผู้ใช้ ค่อยไปหา · ถ้าหาไว้แล้วก็ใช้ของเดิม"
- or-equals (
||=) - ชื่อเล่นที่คนในวงการเรียก · ในเอกสาร Ruby อยู่หัวข้อ abbreviated assignment (การกำหนดค่าแบบย่อ) ·
ถาม senior ได้ว่า "ตัวแปรนี้เป็น false ได้ไหมครับ ถ้าได้
||=ตรงนี้จะเขียนทับมัน" — เป็นคำถามรีวิวโค้ดที่ดีมาก - default value
- ค่าเริ่มต้นที่ใส่ให้เมื่อยังไม่มีค่า · พิมพ์บอก AI ได้ว่า
"Set a default only when the value is nil — it can be false, so don't use
||="
6 · blank? · present? · presence — ของที่ Rails เติมให้
ทีนี้มาดูเรื่องที่เจอทุกวันในเว็บ: ฟอร์ม · ในภาพร้านอาหาร ค่าที่ผู้ใช้กรอกมาคือ "รายละเอียดในใบสั่ง"
ซึ่ง Rails เรียกว่า params (Hash ที่เก็บค่าจากผู้ใช้ — เจอละเอียดในบท 102)
จุดสำคัญ: ถ้าผู้ใช้เว้นช่องว่างไว้แล้วกดส่ง ฟอร์มส่งมาเป็นสตริงว่าง "" ไม่ใช่ nil ·
ถ้าเผลอเคาะ space ก็ได้ " " (ตรวจกับ Rails 8.1.4 แล้วว่าได้แบบนี้จริง)
และจากหัวข้อ 1 — ทั้งสองค่านี้ผ่านประตู if · ถ้าใช้ || ใส่ค่าเริ่มต้นจะพลาด:
# params.rb — รันด้วย ruby -ractive_support/all params.rb
# จำลอง params ด้วย Hash — ผู้ใช้เคาะ space 3 ที แล้วกดค้นหา
params = { q: " " }
p params[:q] || "all"
p params[:q].presence || "all"
$ ruby -ractive_support/all params.rb
" "
"all"
บรรทัดแรก || ธรรมดาได้คำค้นเป็นช่องว่างสามตัว — ค้นหาแล้วไม่เจออะไร ·
บรรทัดที่สองใช้ presence ได้ "all" ตามที่ตั้งใจ · มาดูว่า presence และเพื่อน ๆ คืออะไร
ข่าวร้ายก่อน: Ruby เปล่า ๆ แยก "ว่างจริง" กับ "มีแต่ช่องว่าง" ไม่ได้ ·
มีแค่ empty? (ยาว 0 ตัวไหม) ส่วน blank? ไม่มีเลย:
# blank_plain.rb
p "".empty?
p " ".empty?
p " ".blank?
$ ruby blank_plain.rb
true
false
blank_plain.rb:4:in '<main>': undefined method 'blank?' for an instance of String (NoMethodError)
p " ".blank?
^^^^^^^
blank? มาจาก Active Support — ส่วนเสริมที่ติดมากับ Rails
ซึ่ง "เติม method ใหม่" ให้ของพื้นฐานของ Ruby (ตามที่เล่าในบท 90) ·
ในแอป Rails ใช้ได้เลยทุกไฟล์ · นอก Rails ต้องสั่งโหลดเอง ด้วย ruby -ractive_support/all ไฟล์.rb
(-r = "โหลดของชิ้นนี้ก่อนรันไฟล์")
# blank_as.rb — รันด้วย ruby -ractive_support/all blank_as.rb
p "".blank?
p " ".blank?
p nil.blank?
p [].blank?
p 0.blank?
p false.blank?
p "ramen".present?
p " ".present?
$ ruby -ractive_support/all blank_as.rb
true
true
true
true
false
true
true
false
| method | ถามว่า | จำแค่นี้ |
|---|---|---|
blank? | "ว่างไหม" ในความหมายของคนทั่วไป | nil · false · "" · " " · [] นับว่าว่าง · 0 ไม่ว่าง |
present? | "มีของจริงไหม" | ตรงข้ามกับ blank? เป๊ะ |
presence | "ถ้ามีของจริง ส่งตัวมันมา · ถ้าว่าง ส่ง nil มา" | ใช้คู่กับ || เพื่อใส่ค่าเริ่มต้น |
# presence.rb — รันด้วย ruby -ractive_support/all presence.rb
p "ramen".presence
p "".presence
p " ".presence
p nil.presence
$ ruby -ractive_support/all presence.rb
"ramen"
nil
nil
nil
ทีนี้ย้อนกลับไปอ่าน params[:q].presence || "all" จะเข้าใจแล้ว:
" ".presence ได้ nil → nil ไม่ผ่านประตู → || หยิบ "all" ทางขวามาแทน
ใบสั่งอาหารมีช่อง "หมายเหตุ" · ลูกค้าบางคนเว้นว่าง ("") บางคนขีด ๆ เคาะ ๆ ไว้แต่ไม่มีตัวหนังสือ (" ") ·
พนักงานที่ใช้ blank? มองทั้งสองแบบว่า "ไม่ได้เขียนอะไร" เหมือนที่คนทั่วไปมอง ·
ส่วน presence คือพนักงานที่ "ถ้ามีหมายเหตุก็ส่งต่อให้เชฟตามนั้น · ถ้าไม่มี ก็ส่งมือเปล่า (nil) ให้เชฟใช้สูตรมาตรฐาน"
ที่ไม่เหมือนกัน: ถ้าลูกค้าเขียนว่า "พริก 0 เม็ด" คนอาจคิดว่า "ไม่มีหมายเหตุ" แต่ 0.blank? ได้ false —
สำหรับ Active Support ตัวเลขไม่เคยว่าง เพราะ 0 คือค่าที่ผู้ใช้ตั้งใจกรอก ·
และพนักงานคนนี้ไม่ได้ "เกลาลายมือ" ให้ — " ramen ".presence ยังได้ " ramen " ที่มีช่องว่างหัวท้ายติดมา
เทียบ Go: Go ไม่มี blank? ให้ · คุณต้องตัดช่องว่างเองด้วย strings.TrimSpace
แล้วเทียบกับ "" เอง — งานเดียวกับ params[:q].presence || "all":
// main.go
package main
import (
"fmt"
"strings"
)
func main() {
q := " " // ผู้ใช้เคาะ space 3 ที
if strings.TrimSpace(q) == "" {
q = "all"
}
fmt.Println(q)
}
$ go run main.go
all
strings.TrimSpace(q)= ตัดช่องว่างหัวท้ายทิ้ง ·" "จึงเหลือ""== ""= "ว่างไหม" · ถ้าว่างก็ใส่"all"— ทั้งก้อนifนี้ Ruby + Active Support ย่อเหลือ.presence || "all"
- Active Support
- ส่วนหนึ่งของ Rails ที่เติม method ให้ของพื้นฐานของ Ruby (
blank?·present?·presenceและอีกเยอะ) · ถาม AI ได้ว่า "Is this method plain Ruby or Active Support?" ทุกครั้งที่ไม่แน่ใจ — สำคัญมากถ้าโค้ดนั้นจะไปรันนอก Rails - blank / present
- blank = ว่างในความหมายของคน (nil · false · สตริงที่มีแต่ช่องว่าง · กล่องเปล่า) · present = ตรงข้าม ·
พิมพ์บอก AI ได้ว่า "Treat whitespace-only input as missing — use
presence" - presence
- คืนตัวเองถ้า present · คืน nil ถ้า blank · ถาม senior ได้ว่า "ช่องนี้ควรตัดช่องว่างหัวท้ายก่อนเรียก
presenceไหมครับ"
7 · สรุป: อยากรู้อะไร ถามให้ตรง
| อยากรู้ว่า… | เขียนแบบนี้ | อย่าเขียน |
|---|---|---|
| ตัวเลขมากกว่า 0 ไหม | views.positive? | if views (0 ก็ผ่าน) |
| ค่านี้ว่างเปล่า (nil) ไหม | x.nil? | if !x (เข้าทั้งตอน x เป็น nil และ false · แยกสองตัวนี้ไม่ออก) |
| เรียก method ถ้ามีค่า | author&.upcase | author.upcase เมื่อ author อาจเป็น nil |
| ใส่ค่าเริ่มต้นให้ตัวแปรที่อาจเป็น nil | tags ||= [...] | — |
| ใส่ค่าเริ่มต้นให้ค่า true/false | x = true if x.nil? | x ||= true (ทับ false) |
| ผู้ใช้กรอกฟอร์มมาจริงไหม | q.present? · q.presence || "all" | if q · q || "all" |
💡 สับสนว่าใช้ nil? หรือ blank?? ถามตัวเองว่า "ค่านี้มาจากไหน" —
มาจากฟอร์มที่ผู้ใช้พิมพ์ ใช้ blank? / presence · มาจากที่อื่นที่อาจไม่มีค่า ใช้ nil? / &.
8 · ภารกิจ: แก้บั๊ก 2 จุด (~10 นาที)
🎯 ภารกิจ
- สร้างไฟล์
task.rbแล้ววางโค้ดนี้ทั้งก้อน · มีบั๊ก 2 จุด ทั้งคู่เป็นเรื่องในบทนี้# task.rb — มีบั๊ก 2 จุด def views_label(views) if views "มีคนอ่าน #{views} ครั้ง" else "ยังไม่มีใครอ่าน" end end def title_or_default(title) title || "untitled" end puts views_label(12) # ควรได้: มีคนอ่าน 12 ครั้ง puts views_label(0) # ควรได้: ยังไม่มีใครอ่าน p title_or_default("Ramen") # ควรได้: "Ramen" p title_or_default("") # ควรได้: "untitled" p title_or_default(" ") # ควรได้: "untitled" p title_or_default(nil) # ควรได้: "untitled" - รัน
ruby task.rbก่อนแก้ · ดูว่าบรรทัดไหนไม่ตรงกับ comment "ควรได้" แล้วอธิบายให้ตัวเองฟังว่าเพราะอะไร - แก้ method ละ 1 บรรทัด
- รันใหม่ด้วย
ruby -ractive_support/all task.rbจนทุกบรรทัดตรงกับ comment
ถ้าเจอ error นี้ แปลว่า…
undefined method 'presence' for an instance of String (NoMethodError)→ ลืมใส่-ractive_support/all·presenceเป็นของ Rails ไม่ใช่ Ruby เปล่า ๆundefined method 'positive?' for nil (NoMethodError)→ ถ้าลองส่งnilเข้าไปในviews_labelที่แก้แล้ว จะเจออันนี้ · กล่องว่างไม่มีpositive?(หัวข้อ 3)
ก่อนแก้ — ผลรันจริง:
$ ruby task.rb
มีคนอ่าน 12 ครั้ง
มีคนอ่าน 0 ครั้ง
"Ramen"
""
" "
"untitled"
บรรทัด 2 ผิดเพราะ 0 ผ่านประตู · บรรทัด 4–5 ผิดเพราะ "" กับ " " ผ่านประตู
|| เลยไม่หยิบ "untitled" · มีแค่ nil บรรทัดสุดท้ายที่ได้ค่าเริ่มต้น
แก้สองบรรทัด:
def views_label(views)
if views.positive? # ← ถามให้ตรงว่า "มากกว่า 0 ไหม"
"มีคนอ่าน #{views} ครั้ง"
else
"ยังไม่มีใครอ่าน"
end
end
def title_or_default(title)
title.presence || "untitled" # ← "" กับ " " กลายเป็น nil ก่อน
end
$ ruby -ractive_support/all task.rb
มีคนอ่าน 12 ครั้ง
ยังไม่มีใครอ่าน
"Ramen"
"untitled"
"untitled"
"untitled"
nil.presence ก็ได้ nil (Active Support เติม presence ให้ nil ด้วย) บรรทัดสุดท้ายจึงยังถูก ·
ถ้าลองรันเฉลยนี้ด้วย ruby task.rb เฉย ๆ จะเห็น error presence ตัวแรกในรายการ "ถ้าเจอ error" ข้างบน
ลองตอบดู
บทความมี views เป็น 0 แต่ if post[:views] ขึ้นป้าย "มีคนอ่านแล้ว 0 ครั้ง" — แก้แบบไหนถึงถูก?
ตัวเลือกที่ 2 ถูก · 0.positive? ได้ false จึงไม่ขึ้นป้าย ·
ตัวเลือกที่ 1 กับ 4 ผิดเพราะ 0 ไม่ใช่ nil — รันจริงทั้งคู่ได้ true ป้ายยังขึ้นเหมือนเดิม ·
ตัวเลือกที่ 3 ผิดเพราะตัวเลขไม่เคย blank — 0.present? รันจริงได้ true
จุดที่ต้องจำ: อยากรู้เรื่องตัวเลข ให้ถามด้วย method ของตัวเลข (positive? · zero?) อย่าโยนตัวเลขเข้า if ลอย ๆ
ใน Ruby ค่ากลุ่มไหน "ไม่ผ่าน" if ครบทุกตัว และไม่มีตัวอื่นอีกแล้ว?
ตัวเลือกที่ 1 ถูก · ผลรัน truth.rb ในหัวข้อ 1 มีแค่ nil กับ false ที่ "ไม่เข้า" ·
ตัวเลือกที่ 2 กับ 3 เป็นกฎของ JavaScript/TypeScript ที่ถือว่า 0 และ "" เป็นเท็จ — ใน Ruby สองตัวนี้ผ่าน ·
ตัวเลือกที่ 4 ยังเพิ่ม [] (กล่องเปล่า) เข้ามาอีก ซึ่งรันจริงก็ "เข้า if" เหมือนกัน
จุดที่ต้องจำ: ประตูของ Ruby ห้ามแค่ 2 คน — nil กับ false
โค้ด published = false แล้วบรรทัดถัดไป published ||= true · สุดท้าย published เป็นอะไร?
ตัวเลือกที่ 2 ถูก · รันจริง (orq2.rb) ได้ true ·
ตัวเลือกที่ 1 คือสิ่งที่คนส่วนใหญ่คาด แต่ ||= ไม่ได้ดูว่า "มีค่าไหม" มันดูว่า "ผ่านประตูไหม" และ false ไม่ผ่าน ·
ตัวเลือกที่ 3 ผิดเพราะ ||= ไม่เคยใส่ nil ให้ใคร ·
ตัวเลือกที่ 4 ผิดเพราะ ||= ไม่ใช่ method ของค่า แต่เป็นไวยากรณ์ของภาษา ใช้กับค่าไหนก็ได้
จุดที่ต้องจำ: ค่า true/false ห้ามใส่ค่าเริ่มต้นด้วย ||= · ใช้ x = true if x.nil?
ผู้ใช้เคาะ space 3 ทีในช่องค้นหาแล้วกดค้น · params[:q] ได้ " " · อยากให้ได้ "all" แทน ต้องเขียนแบบไหน? (strip = method ของสตริงที่ตัดช่องว่างหัวท้ายทิ้ง)
ตัวเลือกที่ 3 ถูก · " ".presence ได้ nil แล้ว || หยิบ "all" มาแทน (รันจริงได้ "all") ·
ตัวเลือกที่ 1 กับ 2 ได้ " " เหมือนเดิม เพราะสตริงที่มีช่องว่างผ่านประตู ·
ตัวเลือกที่ 4 ใกล้แต่พลาด — strip ตัดช่องว่างทิ้งเหลือ "" ซึ่งก็ยังผ่านประตู รันจริงได้ ""
จุดที่ต้องจำ: ค่าจากฟอร์มที่ว่างมาเป็น "" ไม่ใช่ nil · ใส่ค่าเริ่มต้นด้วย .presence || …
ถ้าเปิดสไลด์ดูประกอบ
| หน้า | สไลด์เขียน | ของจริง | ต้องสนใจไหม |
|---|---|---|---|
| 26 | if @post.views # จริงเสมอ |
จริงเสมอยกเว้นตอน views เป็น nil · และพิมพ์ตามในไฟล์เปล่า ๆ จะพังเพราะ @post ยังไม่มีค่า |
สนใจ — ฝึกด้วย Hash แบบหัวข้อ 2 |
| 26 | TypeScript "falsy 6 ตัว" | ลิสต์มาแค่ 5 ตัว (ขาด false) · ของจริงมีมากกว่า 6 |
ไม่ต้อง — บทนี้เทียบกับ Go |
| 27 | // Go: if x == 0 { x = 0 } |
บรรทัดนี้ไม่เปลี่ยนอะไรเลย (เป็น 0 ก็ตั้งเป็น 0) · ตัวอย่าง Go ที่ใกล้ ||= จริงอยู่ในหัวข้อ 5 |
ไม่ต้อง |
| 27 | ||= เทียบกับ x ??= 0 ของ TS |
เหมือนกันเกือบหมด ยกเว้นกรณี false — TS ??= ไม่ทับ แต่ Ruby ||= ทับ |
สนใจถ้าเคยเขียน TS |
| 27 | @current_user ||= User.find_by(...) |
ยังไม่ต้องเข้าใจทั้งบรรทัด (@ = บท 95 · User.find_by = Day 2) · จำแค่ว่าเป็น ||= แบบเดียวกับหัวข้อ 5 |
ไม่ต้องตอนนี้ |
อ้างอิง
- Ruby docs — Control Expressions — อ่านแค่ย่อหน้าที่สองของหน้า (ก่อนหัวข้อ if Expression) ที่บอกประโยคเดียวว่า nil กับ false เป็น false-value ที่เหลือเป็น true-value · ที่มาของหัวข้อ 1
- Ruby docs — Calling Methods › Safe Navigation Operator —
ย่อหน้าสั้น ๆ เรื่อง
&.ที่คืน nil แทนการเรียก method เมื่อทางซ้ายเป็น nil · หัวข้อ 4 - Ruby docs — Assignment › Abbreviated Assignment —
ย่อหน้าท้ายหัวข้อที่บอกว่า
||=ใส่ค่าเมื่อค่าเดิมเป็น nil หรือ false · หัวข้อ 5 - Rails Guides — Active Support Core Extensions › blank? and present? —
อ่านรายการว่าอะไรนับว่า blank แล้วเลื่อนลงไปหัวข้อ
presenceถัดไป · หัวข้อ 6 - Ruby docs — Numeric —
ค้นคำว่า
positive?และzero?ในหน้า · หัวข้อ 2 - The Go Programming Language Specification › If statements — ประโยคแรกบอกว่าเงื่อนไขของ if ต้องเป็น boolean · ฝั่ง Go ของหัวข้อ 1