Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

Ruby Metaprogramming ในทางปฏิบัติ: ตัวอย่างจริงจาก Rails, Sinatra และ Paperclip

คุณอาจอ่านเกี่ยวกับ 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 🙂