ทำไมต้องมี Design Patterns?
ปัญหาคือสถาปัตยกรรม Rails (Model-View-Controller) ให้โครงสร้างพื้นฐานวางโค้ด
แต่มันไม่พอ
Views ใหญ่โตและเต็มไปด้วย Logic ในขณะที่เป้าหมายคือ "นำเสนอข้อมูล"
Controllers ถือรายละเอียดมากกว่าที่จำเป็นสำหรับงานหลักของ Controller
โซลูชันคืออะไร?
เรามี 2 แนวทางแก้ปัญหาในรูปแบบ Design Patterns:
- Presenter Pattern
- Service Object Pattern
ไม่ทุกคนเห็นพ้องวิธี Implement แต่อย่างที่ทำงานได้กับผม — มาสำรวจกัน!
วิธีใช้ Presenters ใน Rails
Views ใช้สำหรับ Presentation — คือ HTML, CSS, และ ERB (Embedded Ruby)
ไม่ควรมี ActiveRecord queries ใน Views
และ ส่วนใหญ่ของ Logic ควรเอาออก ถ้าอยากให้ Views สะอาดและทำงานง่าย
"Logic" ที่นี่หมายถึง การตัดสินใจด้วย if statements และ ternary operators
คำถามคือ... ทำยังไง?
เครื่องมือแรกคือ Helpers
Helpers เหมาะสำหรับ Global formatting method ที่ใช้ในหลาย Views
เช่น: Render Markdown, แสดงวันที่ในรูปแบบเฉพาะ, ลบคำเฉพาะจากข้อความ ฯลฯ
ตัวอย่าง:
module DateHelper
def date_as_month_and_year(date)
date.strftime("%B %Y")
end
end
บันทึกโค้ดไว้ที่ app/helpers/date_helper.rb
เคล็ดลับ: ส่ง Input เข้า Helper methods ผ่าน Arguments เสมอ อย่าพึ่ง Instance Variables — จะหลุดพ้นปัญหาเยอะ
Helper methods มีข้อจำกัด โดยเฉพาะถ้าใช้สำหรับทุกความต้องการด้าน Formatting ใน Views — มักสะสมและขาด Organization
มีโซลูชัน!
แทนที่ Conditionals และ Formatting Methods ซับซ้อนด้วย Presenter Object
สมมติ View แบบนี้:
<p>
Post title: <%= post.title.gsub("forbidden word", "") %>
<%= link_to "Read post", post, class: "w-75 p-3 text-#{post.draft? ? "orange" : "green"} border-#{post.draft? ? "orange" : "green"}" %>
</p>
View สั้น แต่รู้สึกซับซ้อนกับ Ternary operators และโค้ดซ้ำ — ไม่ดี!
มาสร้าง Presenter Class กัน
ทำแบบนี้:
class PostPresenter
def initialize(post)
@post = post
end
def title_without_forbidden_words
@post.title.gsub("forbidden word", "")
end
def css_color
@post.draft? ? "orange" : "green"
end
end
บันทึกที่ app/presenters/post_presenter.rb สร้างโฟลเดอร์ presenters ถ้ายังไม่มี
ตอนนี้แก้ View ได้แล้ว
แบบนี้:
<% presenter = PostPresenter.new(post) %>
<p>
Post title: <%= presenter.title_without_forbidden_words %>
<%= link_to "Read post", post, class: "w-75 p-3 text-#{presenter.css_color} border-#{presenter.css_color}" %>
</p>
เสร็จแล้ว!
- เอา Logic ทั้งหมดออกจาก View
- ตั้งชื่อที่มีความหมายให้ Operations การ Format และตัดสินใจ
- Reuse Class นี้ใน Views อื่นได้โดยไม่ซ้ำโค้ด
ที่คือวิธีใช้ Presenters ใน Rails 🙂
วิธีใช้ Service Objects
Controllers ควรแค่ "บอกให้คนอื่นทำ" — ไม่ควรมีความรู้เรื่องการส่ง Tweet, Charge ลูกค้า หรือ Generate PDF
Operation เหล่านี้ ควรมอบหมาย (delegate) ให้ Service Object
Service Object ตามนิยามของผม คือ Ruby Module ที่ห่อหุ้ม Logic สำหรับทำ Action ให้เสร็จ
ตัวอย่าง:
module TwitterService
def self.send_welcome_message(twitter_handle)
client.update("@#{twitter_handle} welcome to 'Oranges & Apples', we hope you enjoy our juicy fruit!")
end
def self.client
@client ||= Twitter::REST::Client.new do |config|
config.consumer_key = "..."
config.consumer_secret = "..."
config.access_token = "..."
config.access_token_secret = "..."
end
end
end
Convention คือบันทึกที่ app/services/twitter_service.rb
ใช้ยังไง?
Rails autoload ทุกอย่างจาก app/ โค้ดนี้พร้อมใช้ใน Controllers
ตัวอย่าง:
class UsersController
def create
# ...
TwitterService.send_welcome_message(user.twitter_handle)
end
end
นี่คือ Service Object Pattern ในการทำงาน
Summary (สรุป)
คุณเรียนรู้ 2 Rails Patterns ที่ช่วยปรับปรุง Code Quality ของโปรเจกต์เมื่อใช้อย่างฉลาด!
ตอนถึงคุณแล้ว — ไป Apply กันเถอะ 🙂
ขอบคุณที่อ่านครับ