Bạn có thể đã đọc về metaprogramming Ruby trước đây.
Nhưng…
Có thể hơi rối nếu không có vài ví dụ cụ thể.
Đó là lý do bài này:
Chúng ta sẽ xem một số dự án open-source phổ biến dùng metaprogramming Ruby.
Dự án như:
- Rails
- Sinatra
- Gem Paperclip
Tất cả dùng dạng metaprogramming nào đó.
Hãy cùng đọc code và tìm hiểu chính xác chúng đang làm gì!
Ví Dụ Rails
Rails dùng metaprogramming mạnh mẽ, nên là điểm khởi đầu tốt.
Ví dụ:
Khi muốn kiểm tra môi trường hiện tại trong ứng dụng Rails, bạn làm như sau.
Rails.env.production?
Nhưng env là gì? Và nó hoạt động như thế nào?
Câu trả lời nằm trong lớp StringInquirer, con của String. Lớp này dùng method_missing để bạn gọi env.production? thay vì env == production.
Code trông như sau:
def method_missing(method_name, *arguments)
if method_name[-1] == '?'
self == method_name[0..-2]
else
super
end
end
Điều này nói:
"Nếu tên phương thức kết thúc bằng dấu hỏi thì so sánh, ngược lại tiếp tục lên chuỗi tổ tiên".
Code gốc có thể tìm thấy tại đây.
Delegation Trong Sinatra
Nếu dùng Sinatra trước đây, bạn biết có hai cách định nghĩa route:
- Dùng phương thức
get/posttrực tiếp, bên ngoài bất kỳ lớp nào. - Định nghĩa lớp kế thừa từ
Sinatra::Application.
Phương thức DSL (Ngôn ngữ chuyên biệt) Sinatra được định nghĩa bên trong Sinatra::Application, vậy làm sao dùng chúng bên ngoài lớp này?
Có hai thứ xảy ra:
- Metaprogramming
- Mở rộng Module (Module extension)
Sinatra định nghĩa module Sinatra::Delegator trong base.rb, dùng để ủy thác gọi phương thức đến target.
Target mặc định là Sinatra::Application.
Đây là phiên bản đơn giản của phương thức lớp delegate, định nghĩa trong 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
Code này tạo tập phương thức (với define_method) chuyển tiếp lời gọi đến Sinatra::Application (giá trị mặc định cho Delegator.target).
Sau đó đối tượng main được mở rộng (extended) với phương thức định nghĩa trong Sinatra::Delegator, làm cho DSL Sinatra khả dụng bên ngoài lớp Sinatra::Application.
Đáng chú ý Ruby có hai lớp nội建 cho ủy thác phương thức, nếu cần: Delegator và Forwardable.
Gem Paperclip
Paperclip là gem cho phép ứng dụng xử lý tải tệp lên. Tệp liên kết với model ActiveRecord.
Bạn định nghĩa tệp đính kèm trên model dùng phương thức has_attached_file.
Như sau:
class User
has_attached_file :avatar, :styles => { :normal => "100x100#" }
end
Phương thức này định nghĩa trên paperclip.rb và dùng metaprogramming định nghĩa phương thức trên Model.
Phương thức này dùng để truy cập tệp đính kèm (là instance của Paperclip::Attachment).
Ví dụ, nếu tệp đính kèm là :avatar, Paperclip sẽ định nghĩa phương thức avatar trên model.
Đây là phương thức define_instance_getter, chịu trách nhiệm việc đó:
# @name => Tên tệp đính kèm
# @klass => Model ActiveRecord nơi phương thức được định nghĩa
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
Từ code này thấy đối tượng attachment lưu dưới biến instance @attachment_#{name}.
Ví dụ của chúng ta là @attachment_avatar.
Sau đó kiểm tra attachment đã tồn tại chưa, nếu chưa thì tạo đối tượng Attachment mới và set biến instance.
Code gốc tại đây.
Kết Luận
Như bạn thấy, metaprogramming là kỹ thuật mạnh mẽ và linh hoạt, nhưng nhớ câu nói: "Quyền lực lớn đi kèm trách nhiệm lớn".
Nếu thích bài viết đừng quên đăng ký bản tin của mình 🙂