คุณอาจอ่านเกี่ยวกับ Ruby metaprogramming มาก่อน แต่... อาจสับสนถ้าไม่มีตัวอย่างเฉพาะเจาะจง
ในบทความนี้: เราจะดูโปรเจกต์ open-source ยอดนิยมที่ใช้ Ruby metaprogramming
โปรเจกต์อย่าง:
- Rails
- Sinatra
- Paperclip gem
ทั้งหมดใช้ metaprogramming บางรูปแบบ มาดูโค้ดและหาว่าพวกมันทำอะไรอยู่!
ตัวอย่าง Rails
Rails ใช้ metaprogramming หนัก ดังนั้นเป็นจุดเริ่มต้นที่ดี เช่น: ตอนตรวจสอบ environment ปัจจุบันใน Rails app คุณทำแบบนี้:
Rails.env.production?
แต่ env คืออะไร? และมันทำงานอย่างไร? คำตอบอยู่ที่ class StringInquirer ซึ่งเป็น subclass ของ String คลาสนี้ใช้ method_missing เพื่อให้คุณเรียก env.production? แทน env == production
โค้ด长得像这样:
def method_missing(method_name, *arguments)
if method_name[-1] == '?'
self == method_name[0..-2]
else
super
end
end
หมายความว่า: "ถ้าชื่อเมธอดลงท้ายด้วยเครื่องหมายคำถาม ให้ทำการเปรียบเทียบ มิฉะนั้นไปต่อขึ้น ancestor chain"
โค้ดต้นฉบับหาได้ที่นี่
Sinatra Delegation
ถ้าเคยใช้ Sinatra อาจรู้ว่ามี 2 วิธี define routes:
- ใช้เมธอด
get/postโดยตรง นอก class - define class ที่ inherit จาก
Sinatra::Application
Sinatra DSL methods ถูก define ภายใน Sinatra::Application แล้วทำไมถึงใช้ได้นอก class นี้? มี 2 สิ่งเกิดขึ้น:
- Metaprogramming
- Module extension
Sinatra define module Sinatra::Delegator ใน base.rb ใช้ delegate method calls ไปยัง target ซึ่ง default เป็น Sinatra::Application
นี่คือ delegate class method แบบง่ายใน Sinatra::Delegator:
def self.delegate(*methods)
methods.each do |method_name|
define_method(method_name) do |*args, &block|
Delegator.target.send(method_name, *args, &block)
end
end
end
delegate :get, :patch, :put, :post, :delete
โค้ดนี้สร้างชุดเมธอด (ด้วย define_method) ที่ forward calls ไป Sinatra::Application (default ของ Delegator.target) แล้ว main object ถูก extend ด้วยเมธอดจาก Sinatra::Delegator ทำให้ Sinatra DSL ใช้ได้นอก class Sinatra::Application
ค่าถ่ะ: Ruby มี built-in classes 2 ตัวสำหรับ method delegation: Delegator และ Forwardable
Paperclip Gem
Paperclip เป็น gem สำหรับจัดการ file uploads ไฟล์เชื่อมโยงกับ ActiveRecord models define attached file บน model ด้วย has_attached_file
แบบนี้:
class User
has_attached_file :avatar, :styles => { :normal => "100x100#" }
end
เมธอดนี้ define ใน paperclip.rb ใช้ metaprogramming define เมธอดบน Model ใช้เข้าถึง file attachment (instance ของ Paperclip::Attachment) เช่น attached file เป็น :avatar Paperclip จะ define เมธอด avatar บน model
นี่คือ define_instance_getter ที่รับผิดชอบ:
# @name => attachment name
# @klass => ActiveRecord model ที่ define เมธอดนี้
def define_instance_getter
name = @name
options = @options
@klass.send :define_method, @name do |*args|
ivar = "@attachment_#{name}"
attachment = instance_variable_get(ivar)
if attachment.nil?
attachment = Attachment.new(name, self, options)
instance_variable_set(ivar, attachment)
end
attachment
end
end
จากโค้ด attachment object เก็บอยู่ที่ @attachment_#{name} instance variable ตัวอย่างคือ @attachment_avatar เช็คว่ามี attachment อยู่แล้วไหม ถ้าไม่มี สร้าง Attachment ใหม่และ set instance variable
โค้ดต้นฉบับที่นี่
สรุป
เห็นได้ว่า metaprogramming เป็นเทคนิคทรงพลังและยืดหยุ่น แต่ทุกครั้งที่จะใช้ จงจำคำพูด: "With great power comes great responsibility"
ถ้าชอบบทความนี้อย่าลืมสมัครรับ newsletter 🙂