T Thingnoy
Home Ruby on Rails
Part 1

Ruby on Rails — เกิดมาทำไม แก้อะไร ต่างจากชาวบ้านยังไง

อ่าน ~102 นาที
ruby rails web-development
Ruby on Rails — เกิดมาทำไม แก้อะไร ต่างจากชาวบ้านยังไง
Table of Contents

Ruby on Rails — เกิดมาทำไม แก้อะไร ต่างจากชาวบ้านยังไง

“Convention over Configuration — อย่าให้โปรแกรมเมอร์ตัดสินใจในสิ่งที่ไม่จำเป็นต้องตัดสินใจ”


Part 1 — โลกก่อน Rails เป็นยังไง

ย้อนไปปี 2004

สมมติอยากทำเว็บง่ายๆ สักตัว — ระบบ blog ที่ user สร้าง post ได้, comment ได้, มี authentication

ตอนนั้นมีทางเลือกอยู่ไม่กี่ทาง:

ทางเลือกที่ 1 — PHP แบบดิบๆ

<?php
// blog.php
$conn = mysql_connect("localhost", "root", "password");
mysql_select_db("blog");
$result = mysql_query("SELECT * FROM posts ORDER BY created_at DESC");

echo "<html><body>";
while ($row = mysql_fetch_assoc($result)) {
    echo "<h2>" . $row['title'] . "</h2>";
    echo "<p>" . $row['body'] . "</p>";
    // SQL injection? XSS? ช่างมัน...
}
echo "</body></html>";
?>

ปัญหา:

ทางเลือกที่ 2 — Java / J2EE

// PostServlet.java
public class PostServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        // ... 20 บรรทัดแค่ setup connection
    }
}

แล้วก็ต้องมี:

web.xml          — config servlet mapping
build.xml        — config build process
hibernate.cfg.xml — config ORM
struts-config.xml — config MVC framework
applicationContext.xml — config dependency injection

ปัญหา:

ทางเลือกที่ 3 — Perl CGI

#!/usr/bin/perl
print "Content-type: text/html\n\n";
# ... สิ่งที่ไม่มีใครอยากดูแล maintain

ทุกทางเลือกมีปัญหาเดียวกัน: web development ในยุคนั้นช้า เจ็บปวด และซ้ำซาก


Part 2 — Rails เกิดมายังไง

DHH กับ Basecamp

ปี 2003 — David Heinemeier Hansson (DHH) โปรแกรมเมอร์ชาวเดนมาร์กที่ทำงานให้ 37signals (บริษัทเล็กๆ ในชิคาโก) ได้รับมอบหมายให้สร้าง project management tool ชื่อ Basecamp

DHH เลือกใช้ Ruby — ภาษาที่ตอนนั้นแทบไม่มีใครรู้จักนอกญี่ปุ่น เพราะมันเขียนแล้ว “มีความสุข” ตามปรัชญาของ Matz (ผู้สร้าง Ruby) ที่ว่า “optimize for developer happiness”

ระหว่างที่สร้าง Basecamp, DHH เริ่มเห็น pattern ที่ซ้ำๆ:

เขาก็เลย extract ส่วนที่ซ้ำๆ ออกมาเป็น framework แล้วปล่อยเป็น open source ในปี 2004

นั่นคือ Ruby on Rails

Rails ไม่ได้เกิดจากทฤษฎี ไม่ได้เกิดจาก research paper มันเกิดจากการสร้างของจริง แล้ว extract pattern ที่ใช้ได้ออกมา


ต้องรู้ Ruby ก่อนมั้ย?

คำตอบตรงๆ: ไม่ต้องรู้ก่อนก็เริ่มได้ แต่ต้องเรียนไปพร้อมกัน

ถ้าเขียน JavaScript / Python / PHP มาก่อน — Ruby อ่านรู้เรื่องเกือบทันที:

# ตัวแปร — ไม่ต้องประกาศ type
name = "Skypart"
age = 25
is_admin = true

# String interpolation — เหมือน JS template literal
greeting = "Hello, #{name}!"     # Ruby
# greeting = `Hello, ${name}!`   # JS เทียบ

# Array
numbers = [1, 2, 3, 4, 5]
numbers.push(6)
numbers.first       # => 1
numbers.last        # => 6
numbers.length      # => 6

# Hash (เหมือน Object ใน JS, Dict ใน Python)
user = { name: "Skypart", age: 25, role: "admin" }
user[:name]          # => "Skypart"

# if/else — ไม่ต้องมีวงเล็บ ไม่ต้องมีปีกกา
if age >= 18
  puts "Adult"
else
  puts "Minor"
end

# เขียนบรรทัดเดียวก็ได้
puts "Adult" if age >= 18

# Loop
numbers.each do |n|
  puts n
end

# เขียนสั้นๆ
numbers.each { |n| puts n }

# Method — ไม่ต้อง return (บรรทัดสุดท้ายคือ return อัตโนมัติ)
def greet(name)
  "Hello, #{name}!"     # return อัตโนมัติ
end

greet("Skypart")   # => "Hello, Skypart!"

# Class
class User
  attr_accessor :name, :email    # สร้าง getter/setter ให้

  def initialize(name, email)    # constructor
    @name = name                 # @ = instance variable
    @email = email
  end

  def admin?                     # method ที่จบด้วย ? return boolean (convention)
    @email.end_with?("@admin.com")
  end
end

user = User.new("Skypart", "sky@admin.com")
user.name       # => "Skypart"
user.admin?     # => true

Ruby ที่ใช้ใน Rails จริงๆ 90% คือแค่นี้:

# ส่วนใหญ่เขียนแค่นี้ใน Rails:

# 1. ประกาศ model
class Post < ApplicationRecord
  has_many :comments
  validates :title, presence: true
end

# 2. query ข้อมูล
Post.where(published: true).order(:created_at)

# 3. if/else ใน controller
if @post.save
  redirect_to @post
else
  render :new
end

# 4. loop ใน view (ERB)
@posts.each do |post|
  # render แต่ละ post
end

ไม่ต้องรู้ metaprogramming ไม่ต้องรู้ monkey patching ไม่ต้องรู้ Ruby ลึก — เริ่ม Rails ก่อน แล้วเรียน Ruby เพิ่มเมื่อต้องการ

Ruby syntax cheat sheet (เทียบกับ JS)

สิ่งที่ทำJavaScriptRuby
ประกาศตัวแปรlet x = 1x = 1
String interpolation`Hello ${name}`"Hello #{name}"
Array maparr.map(x => x * 2)`arr.map {
Array filterarr.filter(x => x > 3)`arr.select {
Object / Hash{ name: "A" }{ name: "A" }
Access propertyobj.namehash[:name]
Functionfunction greet(name) {}def greet(name) ... end
Arrow function(x) => x * 2-> (x) { x * 2 }
Console logconsole.log("hi")puts "hi"
nullnullnil
if conditionif (x > 0) { }if x > 0 ... end
forEacharr.forEach(x => ...)`arr.each {
Classclass User { }class User ... end
Constructorconstructor(name) {}def initialize(name) ... end
thisthis.name@name
Importimport X from 'x'require 'x'

แล้ว Ruby เป็น strong type มั้ย?

Ruby เป็น dynamically typed + strongly typed

ฟังดูขัดกัน แต่ 2 คำนี้คนละเรื่อง:

                    Weakly Typed              Strongly Typed
                    (แปลง type ให้เอง)         (ไม่แปลงให้ ต้องชัดเจน)
                    ┌─────────────────┐       ┌─────────────────┐
Dynamically Typed   │   JavaScript    │       │   Ruby          │
(ไม่ต้องประกาศ type) │   PHP           │       │   Python        │
                    └─────────────────┘       └─────────────────┘
Statically Typed    │   C (บางกรณี)    │       │   Java          │
(ต้องประกาศ type)    │                 │       │   TypeScript    │
                    └─────────────────┘       │   Swift         │
                                              │   Go            │
                                              └─────────────────┘

Dynamically typed = ไม่ต้องประกาศ type ตัวแปรเปลี่ยน type ได้ตลอด:

x = 1          # x เป็น Integer
x = "hello"    # x เป็น String แล้ว — ไม่ error

Strongly typed = ไม่แปลง type ให้อัตโนมัติ ถ้า type ไม่ตรงก็ error:

# Ruby — strongly typed: ไม่แปลงให้
"age: " + 25           # TypeError! String + Integer ไม่ได้
"age: " + 25.to_s      # ต้องแปลงเอง → "age: 25"

1 + "2"                # TypeError!
1 + "2".to_i           # ต้องแปลงเอง → 3
// JavaScript — weakly typed: แปลงให้เงียบๆ
"age: " + 25           // "age: 25" — JS แปลง 25 เป็น string ให้
1 + "2"                // "12" — อ้าว! ได้ string ไม่ใช่ 3

// นี่คือที่มาของ meme "JavaScript is weird"

สรุป:

แล้วถ้าอยาก type safety มากขึ้นล่ะ?

Ruby มี Sorbet (by Stripe) และ RBS (official) สำหรับเพิ่ม type checking:

# ปกติ — ไม่มี type
def greet(name)
  "Hello, #{name}!"
end

# ใช้ Sorbet — เพิ่ม type annotation (optional)
sig { params(name: String).returns(String) }
def greet(name)
  "Hello, #{name}!"
end

แต่ส่วนใหญ่ Rails community ไม่ใช้ type checker — ใช้ test แทน ปรัชญาคือ “เขียน test ดีกว่าประกาศ type”

สรุป: ถ้าเขียน JS/Python/PHP ได้ — Ruby อ่านรู้เรื่องใน 30 นาที ไม่ต้องเรียน Ruby course ก่อน เริ่ม Rails เลยแล้วค่อยเรียน Ruby ไปพร้อมกัน


Part 3 — Rails แก้อะไร

ปรัชญาหลัก 2 ข้อ

1. Convention over Configuration

แทนที่จะให้โปรแกรมเมอร์ตั้ง config ทุกอย่าง — Rails ตั้ง default ที่สมเหตุสมผลให้หมด

ก่อน Rails (Java-style):

<!-- ต้อง config ว่า class Post map กับ table ไหน -->
<hibernate-mapping>
    <class name="com.blog.model.Post" table="posts">
        <id name="id" column="id">
            <generator class="native"/>
        </id>
        <property name="title" column="title" type="string"/>
        <property name="body" column="body" type="text"/>
    </class>
</hibernate-mapping>

Rails:

class Post < ApplicationRecord
  # แค่นี้จบ
  # Rails รู้เองว่า:
  # - class Post → table ชื่อ "posts" (พหูพจน์อัตโนมัติ)
  # - column ใน table → กลายเป็น attribute อัตโนมัติ
  # - primary key คือ "id"
end

Convention ที่ Rails ตั้งไว้:

ทุกอย่างมี “ที่ของมัน” ถ้าทำตาม convention — ไม่ต้อง config อะไรเลย

2. DRY — Don’t Repeat Yourself

ทุก piece of knowledge ควรมี single, unambiguous representation ในระบบ

ก่อน Rails:

1. สร้าง table ใน database
2. เขียน class ใน code ที่ mirror structure ของ table
3. เขียน mapping file ที่บอกว่า class map กับ table ยังไง
4. เขียน SQL query สำหรับ CRUD แต่ละตัว

Rails:

1. เขียน migration สร้าง table
2. จบ — Rails จัดการที่เหลือให้

Part 4 — Rails ต่างจากอันอื่นยังไง

เทียบกัน: สร้าง Blog CRUD

Django (Python)

# models.py
class Post(models.Model):
    title = models.CharField(max_length=200)
    body = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)

# views.py
def post_list(request):
    posts = Post.objects.all()
    return render(request, 'blog/post_list.html', {'posts': posts})

# urls.py
urlpatterns = [
    path('posts/', views.post_list, name='post_list'),
]

Django ใกล้เคียง Rails มากที่สุด (ได้แรงบันดาลใจจาก Rails ด้วยซ้ำ) แต่ต่างกันตรง:

Express.js (Node.js)

// ไม่มี ORM มาให้ ต้องเลือกเอง (Sequelize? Mongoose? Prisma?)
// ไม่มี project structure มาให้ ต้องออกแบบเอง
// ไม่มี convention — ทุกอย่างคือทางเลือก

const express = require('express');
const app = express();

app.get('/posts', async (req, res) => {
    // ORM ตัวไหน? query ยังไง? ขึ้นอยู่กับว่าเลือกอะไร
    const posts = await Post.findAll();
    res.json(posts);
});

Express.js เป็น un-opinionated framework — ให้อิสระเต็มที่ แต่ต้องตัดสินใจทุกอย่างเอง:

Rails เป็น opinionated framework — “ฉันตัดสินใจให้แล้ว ถ้าเชื่อฉัน มันจะเร็วมาก”

Spring Boot (Java)

@Entity
public class Post {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String body;
    // + getter, setter, constructor อีก 40 บรรทัด
}

@RestController
@RequestMapping("/posts")
public class PostController {
    @Autowired
    private PostRepository repository;

    @GetMapping
    public List<Post> index() {
        return repository.findAll();
    }
}

Spring Boot ดีขึ้นมากจาก J2EE แต่ยังคง verbose กว่า Rails หลายเท่า และ annotation hell แทน XML hell


Part 5 — ข้อเสียและข้อจำกัด

Rails ไม่ได้สมบูรณ์แบบ ต้องรู้ข้อจำกัดด้วย:

1. Performance

Ruby ช้ากว่า Go, Rust, Java ในแง่ raw throughput หลายเท่า ถ้าต้อง handle request เป็นแสนต่อวินาที Rails อาจไม่ใช่คำตอบ

แต่สำหรับ app ส่วนใหญ่ — bottleneck อยู่ที่ database query ไม่ใช่ภาษา Rails ก็เร็วพอ

มาดู context เพิ่มเติม:

2. Magic มากเกินไป

Convention ที่เยอะ = สิ่งที่เกิดขึ้น “โดยไม่รู้ตัว” ก็เยอะ

# ทำไม Post.find_by_title("Hello") ถึงใช้ได้?
# method นี้ไม่ได้ถูก define ตรงไหนเลย
# → Rails สร้างให้อัตโนมัติจาก column name ด้วย method_missing

สำหรับมือใหม่ magic เยอะ = เข้าใจยากว่าเบื้องหลังเกิดอะไรขึ้น

ลองดูตัวอย่าง magic ที่มักจะทำให้งง:

วิธีรับมือ: อ่าน Rails Guides ให้เข้าใจ convention ก่อน อย่าเดา เพราะถ้าเดาผิด debug ยาก แต่ถ้าเข้าใจ convention แล้ว magic เหล่านี้จะกลายเป็น productivity boost

3. Monolith by default

Rails ถูกออกแบบมาเป็น monolithic application ถ้าทีมโตมาก หรือต้อง scale แต่ละส่วนแยกกัน จะต้อง break ออกเป็น service ซึ่งไม่ใช่จุดแข็งของ Rails

(แต่ DHH ก็ argue ว่า monolith ดีสำหรับทีมส่วนใหญ่ — และ Shopify ก็ยังเป็น Rails monolith ที่ใหญ่ที่สุดในโลก)

เรื่องนี้มี nuance เยอะ:

4. Hiring pool เล็กลง

JavaScript/TypeScript ครองตลาด Python มี AI hype — คน Ruby หายากขึ้นเรื่อยๆ ในบางตลาด

แต่ต้องมองหลายมุม:

5. Ecosystem เล็กกว่า JavaScript/Python


Part 6 — แล้วใครใช้ Rails อยู่

Rails ไม่ได้ตาย — หลายบริษัทใหญ่ยังใช้อยู่:

สิ่งที่น่าสังเกตคือ — หลายบริษัทที่ “เริ่มต้นด้วย Rails” แล้ว scale จนใหญ่มาก บางส่วนอาจ migrate ออกไปใช้ภาษาอื่นสำหรับ service ที่ต้อง performance สูงเป็นพิเศษ แต่ core ยังเป็น Rails อยู่ นี่พิสูจน์ว่า Rails เหมาะเป็นจุดเริ่มต้นที่ดีเยี่ยม — ship เร็ว, iterate เร็ว, แล้วค่อย optimize ทีหลังเมื่อจำเป็นจริงๆ


Part 7 — สรุป: Rails เหมาะกับอะไร

เหมาะไม่เหมาะ
Web app ทั่วไป (CRUD-heavy)Real-time system ที่ต้อง ultra-low latency
Startup ที่ต้อง ship เร็วCPU-intensive computation
Solo developer / ทีมเล็กMicroservices architecture ตั้งแต่วันแรก
Prototype / MVPMobile app backend ที่เป็น pure API (อาจลองดู API-only mode)
Content-heavy siteSystem programming

แก่นแท้ของ Rails

Rails เกิดมาจากคำถามเดียว:

“ทำไม web development ถึงต้องเจ็บปวดขนาดนี้?”

คำตอบของ DHH คือ: มันไม่จำเป็นต้องเจ็บปวด ถ้าเรา:

  1. ตั้ง convention ที่ดี แทนที่จะให้ทุกคนตัดสินใจเอง
  2. ลด boilerplate ให้เหลือแต่ code ที่มีความหมาย
  3. ให้ทุกอย่างที่ต้องใช้มากับ framework แทนที่จะให้ไปหาเอง
  4. ทำให้โปรแกรมเมอร์มีความสุข — เพราะคนที่มีความสุขเขียน code ได้ดีกว่า

ไม่ว่าจะใช้ Rails หรือไม่ — แนวคิดพวกนี้เปลี่ยน web development ไปตลอดกาล ทุกวันนี้ทุก framework ได้รับอิทธิพลจาก Rails ไม่ทางใดก็ทางหนึ่ง Django, Laravel, Phoenix, Adonis, Next.js — ล้วนยืมแนวคิดจาก Rails ทั้งนั้น


“The best frameworks aren’t designed — they’re extracted.” — David Heinemeier Hansson


ต่อไป: Part 2: Rails ทำงานยังไง → — เจาะลึก rendering model, Hotwire, ERB, CSS, deploy