การประกาศ Rails 4.2 มีข่าวที่น่าสนใจเกี่ยวกับ Rails 5:ที่อาจต้องใช้ Ruby 2.2 ซึ่งจะทำให้เป็น Rails เวอร์ชันแรกที่สามารถใช้ประโยชน์จากสิ่งดี ๆ จาก Ruby 2 ได้ทั้งหมด
โพสต์กล่าวถึงสัญลักษณ์ที่รวบรวมขยะและอาร์กิวเมนต์คำหลัก แต่สำหรับฉัน คุณลักษณะ Ruby 2 ที่น่าสนใจที่สุดอย่างหนึ่งคือ Module#prepend
ข้อดี (และข้อเสีย) ของ alias_method_chain
เมื่อฉันเรียนรู้ Rails เป็นครั้งแรก alias_method_chain
พัดใจของฉันโดยสิ้นเชิง มันแสดงให้เห็นอย่างชัดเจนว่า Ruby สามารถยืดหยุ่นได้เพียงใด
ด้วยโค้ดเพียงบรรทัดเดียว คุณสามารถเปลี่ยนวิธีการทำงานโดยสิ้นเชิง คุณไม่จำเป็นต้องแฮ็คห้องสมุดเพื่อเพิ่มโค้ดที่คุณต้องการอีกต่อไป คุณสามารถเพิ่มได้ทันที alias_method_chain
นำไปสู่แพทช์แรกของฉันสู่อัญมณี ซึ่งนำไปสู่คำขอดึงครั้งแรกของฉัน ซึ่งนำไปสู่การสนับสนุนโอเพนซอร์สครั้งแรกของฉัน
แต่เหมือนกับการปะลิง alias_method_chain
ใช้มากเกินไป และปัญหาเริ่มชัดเจน:
- ชื่อเมธอดที่สร้างขึ้นนั้นสร้างความสับสน ซึ่งทำให้ค้นหาข้อผิดพลาดและแก้ไขข้อบกพร่องได้ยาก ตัวอย่างเช่น:
class Person
def greeting
"Hello"
end
end
module GreetingWithExcitement
def self.included(base)
base.class_eval do
alias_method_chain :greeting, :excitement
end
end
def greeting_with_excitement
"#{greeting_without_excitement}!!!!!"
end
end
Person.send(:include, GreetingWithExcitement)
หากคุณมีข้อผิดพลาดใน Person#greeting
การติดตามย้อนกลับจะบอกคุณว่ามีข้อผิดพลาดเกิดขึ้นจริงใน Person#greeting_without_excitement
. แต่วิธีการนั้นถูกกำหนดไว้ที่ไหน? ฉันไม่เห็นมันทุกที่ คุณรู้ได้อย่างไรว่าgreeting
วิธีการคือวิธีที่มีจุดบกพร่องในนั้นหรือไม่ และชื่อเมธอดยิ่งสับสนมากขึ้นเรื่อยๆ
-
ถ้าคุณเรียก
alias_method_chain
สองครั้งด้วยพารามิเตอร์เดียวกันในคลาสเดียวกัน อาจทำให้สแต็กโอเวอร์โฟลว์ได้ (คุณเห็นไหมว่าทำไม) สิ่งนี้จะไม่เกิดขึ้น ตราบใดที่require
.ของคุณ งบมีความสอดคล้องกันเกี่ยวกับเส้นทางที่พวกเขาใช้ แต่มันน่ารำคาญมากถ้าคุณวางโค้ดลงในคอนโซล Rails บ่อยๆ -
และเรื่องอื่นๆ ที่โพสต์ในบล็อกของ Yehuda Katz อธิบายไว้ โพสต์นี้โน้มน้าวให้นักพัฒนา Rails จำนวนมากเริ่มละทิ้ง
alias_method_chain
เพื่อประโยชน์ในการสืบทอดโมดูล
แล้วทำไมยังใช้อยู่?
คุณสามารถแทนที่ alias_method_chain
. ส่วนใหญ่ได้ โดยการแทนที่เมธอดเหล่านั้นในโมดูล และรวมโมดูลเหล่านั้นไว้ในคลาสย่อยของคุณ แต่จะได้ผลก็ต่อเมื่อคุณต้องการแทนที่ซูเปอร์คลาสของคุณ ไม่ใช่คลาสของคุณเอง นั่นคือ:
class ParentClass
def log
puts "In parent"
end
end
class ChildClass < ParentClass
def log
puts "In child"
super
end
def log_with_extra_message
puts "In child, with extra message"
log_without_extra_message
end
alias_method_chain :log, :extra_message
end
หากคุณรัน ChildClass.new.log
คุณจะเห็น:
In child, with extra message
In child
In parent
หากคุณพยายามใช้โมดูลแทน alias_method_chain
คุณจะได้ผลลัพธ์เป็น:
In child
In child, with extra message
In parent
แต่คุณ ไม่สามารถ จับคู่ผลลัพธ์เดิมโดยไม่ต้องเปลี่ยน log
เมธอดใน ChildClass
. การสืบทอดทับทิมไม่ได้ผลแบบนั้น ก็ไม่ได้
มีอะไรเปลี่ยนแปลงใน Ruby 2.0
จนถึง Ruby 2.0 ไม่มีวิธีเพิ่มโค้ด ด้านล่าง คลาส เหนือมันเท่านั้น แต่มี prepend
คุณสามารถแทนที่เมธอดในคลาสด้วยเมธอดจากโมดูล และยังเข้าถึงการใช้งานคลาสด้วย super
. จากตัวอย่างสุดท้าย เราจะได้ผลลัพธ์เดิมด้วย:
class ParentClass
def log
puts "In parent"
end
end
module ExtraMessageLogging
def log
puts "In child, with extra message"
super
end
end
class ChildClass < ParentClass
prepend ExtraMessageLogging
def log
puts "In child"
super
end
end
In child, with extra message
In child
In parent
สมบูรณ์แบบ
ถ้า prepend
ยังคงยากที่จะคาดคิด ให้คิดว่ามันเป็นการทำอะไรแบบนี้:
class NewChildClass < ChildClass
include ExtraMessageLogging
end
ChildClass = NewChildClass
เว้นแต่จะไม่ยุ่งกับชื่อคลาสของคุณ และจะมีผลกับวัตถุที่มีอยู่แล้ว
(ใช่ คุณสามารถกำหนดชื่อชั้นเรียนใหม่ใน Ruby ได้ ไม่ มันอาจจะไม่ใช่ความคิดที่ดีนัก)
สิ่งนี้หมายความว่าอย่างไรสำหรับ Rails
ข้อแก้ตัวสุดท้ายสำหรับการใช้ alias_method_chain
หายไปใน Ruby 2.0 เราสามารถนำหนึ่งในตัวอย่างที่เหลือของ alias_method_chain
ใน Rails:
require 'active_support/core_ext/module/aliasing'
class Range #:nodoc:
def each_with_time_with_zone(&block)
ensure_iteration_allowed
each_without_time_with_zone(&block)
end
alias_method_chain :each, :time_with_zone
def step_with_time_with_zone(n = 1, &block)
ensure_iteration_allowed
step_without_time_with_zone(n, &block)
end
alias_method_chain :step, :time_with_zone
private
def ensure_iteration_allowed
if first.is_a?(Time)
raise TypeError, "can't iterate from #{first.class}"
end
end
end
และแทนที่ด้วยโมดูลแทน:
require 'active_support/core_ext/module/aliasing'
module RangeWithTimeWithZoneSupport #:nodoc:
def each(&block)
ensure_iteration_allowed
super(&block)
end
def step(n = 1, &block)
ensure_iteration_allowed
super(n, &block)
end
private
def ensure_iteration_allowed
if first.is_a?(Time)
raise TypeError, "can't iterate from #{first.class}"
end
end
end
Range.send(:prepend, RangeSupportingTimeWithZone)
สะอาดขึ้น Range#each
ไม่ได้รับการเปลี่ยนชื่อ และ ensure_iteration_allowed
ไม่ปะติดปะต่อลิง
ใช้การสืบทอด ไม่ใช่แพตช์
Ruby มอบความยืดหยุ่นให้คุณมากมาย และนั่นเป็นเหตุผลหนึ่งที่ฉันชอบ แต่ก็มีโมเดลวัตถุที่ทรงพลังเช่นกัน ดังนั้น เมื่อคุณต้องการใส่โค้ดของคุณเอง ให้ลองพึ่งพาโมดูลและการสืบทอดก่อนที่จะแฮ็คเข้าไป รหัสของคุณจะเข้าใจและแก้ปัญหาได้ง่ายขึ้นมาก และคุณจะหลีกเลี่ยงผลข้างเคียงบางอย่างที่ยากต่อการตรวจจับ เช่น alias_method_chain
.
alias_method_chain
เป็นหนึ่งในวิธีที่ยอดเยี่ยมที่สุดที่ฉันรู้จักใน Rails แต่วันของมันก็นับ เราโตแล้ว ของหมดแล้วจะไม่พลาดค่ะ