คุณรู้หรือไม่ว่า Ruby จัดเตรียมวิธีให้สคริปต์ของคุณใช้ไฟล์ต้นฉบับของตัวเองเป็นแหล่งข้อมูล เป็นกลอุบายที่จะช่วยคุณประหยัดเวลาในการเขียนสคริปต์แบบใช้ครั้งเดียวและการพิสูจน์แนวคิด มาดูกันเลย!
ข้อมูลและ END
ในตัวอย่างด้านล่าง ฉันใช้คำหลักตลกๆ ชื่อ __END__
. ทุกอย่างด้านล่าง __END__
จะถูกละเว้นโดยล่าม Ruby แต่ที่น่าสนใจกว่านั้น ruby ให้คุณมีอ็อบเจ็กต์ IO ชื่อ DATA
ซึ่งให้คุณอ่านทุกอย่างด้านล่าง __END__
เหมือนกับที่คุณอ่านจากไฟล์อื่นๆ
ในตัวอย่างต่อไปนี้ เราวนซ้ำในแต่ละบรรทัดแล้วพิมพ์ออกมา
DATA.each_line do |line|
puts line
end
__END__
Doom
Quake
Diablo
ตัวอย่างการปฏิบัติที่ฉันชอบของเทคนิคนี้ใช้ DATA
เพื่อให้มีเทมเพลต ERB นอกจากนี้ยังใช้งานได้กับ YAML, CSV และอื่นๆ ม
require 'erb'
time = Time.now
renderer = ERB.new(DATA.read)
puts renderer.result()
__END__
The current time is <%= time %>.
คุณสามารถใช้ DATA
. ได้จริง เพื่ออ่านเนื้อหาด้านบน __END__
คำสำคัญ. นั่นเป็นเพราะ DATA
ที่จริงแล้วเป็นตัวชี้ไปยังไฟล์ต้นฉบับทั้งหมด กรอไปข้างหน้าอย่างรวดเร็วไปยัง __END__
คำสำคัญ. คุณสามารถดูสิ่งนี้ได้หากคุณกรอกลับออบเจกต์ IO ก่อนพิมพ์ ตัวอย่างด้านล่างจะพิมพ์ไฟล์ต้นฉบับทั้งหมดออกมา
DATA.rewind
puts DATA.read # prints the entire source file
__END__
meh
ปัญหาหลายไฟล์
ข้อเสียที่สำคัญอย่างหนึ่งของเทคนิคนี้คือใช้งานได้จริงก็ต่อเมื่อสคริปต์ของคุณพอดีกับไฟล์ต้นฉบับไฟล์เดียว และคุณกำลังเรียกใช้ไฟล์นั้นโดยตรง แทนที่จะรวมไว้ด้วย
ในตัวอย่างด้านล่าง ฉันมีไฟล์สองไฟล์ โดยแต่ละไฟล์มี __END__
. ของตัวเอง ส่วน. อย่างไรก็ตาม มี DATA
. ได้เพียงตัวเดียวเท่านั้น ทั่วโลก. ดังนั้น __END__
ไม่สามารถเข้าถึงส่วนของไฟล์ที่สองได้
# first.rb
require "./second"
puts "First file\n----------------------"
puts DATA.read
print_second_data()
__END__
First end clause
# second.rb
def print_second_data
puts "Second file\n----------------------"
puts DATA.read # Won't output anything, since first.rb read the entire file
end
__END__
Second end clause
snhorne ~/tmp $ ruby first.rb
First file
----------------------
First end clause
Second file
----------------------
วิธีแก้ปัญหาสำหรับหลายไฟล์
Sinatra มีฟีเจอร์เจ๋งๆ ที่ช่วยให้คุณสามารถเพิ่มเทมเพลตอินไลน์หลายอันลงในแอปของคุณโดยวางไว้หลัง __END__
คำแถลง. ดูเหมือนว่านี้:
# This code is from the Sinatra docs at https://www.sinatrarb.com/intro.html
require 'sinatra'
get '/' do
haml :index
end
__END__
@@ layout
%html
= yield
@@ index
%div.title Hello world.
แต่ซินาตร้าทำสิ่งนี้ได้อย่างไร? ท้ายที่สุด แอปของคุณน่าจะโหลดโดยแร็ค คุณจะไม่เรียกใช้ ruby myapp.rb
ในการผลิต! พวกเขาต้องคิดหาวิธีใช้ DATA
ที่มีหลายไฟล์
แม้ว่าหากคุณเจาะลึกลงไปในแหล่งซินาตราเพียงเล็กน้อย คุณจะเห็นว่าพวกเขากำลังนอกใจ พวกเขาไม่ได้ใช้ DATA
เลย แต่พวกเขากำลังทำสิ่งที่คล้ายกับโค้ดด้านล่างแทน
# I'm paraphrasing. See the original at https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1284
app, data = File.read(__FILE__).split(/^__END__$/, 2)
จริงๆแล้วมันซับซ้อนกว่าเล็กน้อยเพราะพวกเขาไม่ต้องการอ่าน __FILE__
. นั่นก็เป็นเพียงไฟล์ sinatra/base.rb พวกเขาต้องการรับเนื้อหาของไฟล์ที่เรียกใช้ฟังก์ชันแทน พวกเขาได้รับสิ่งนี้โดยแยกวิเคราะห์ผลลัพธ์ของผู้โทร
ฟังก์ชันผู้โทรจะบอกคุณว่าฟังก์ชันที่กำลังทำงานอยู่ถูกเรียกใช้ที่ใด ต่อไปนี้คือตัวอย่างสั้นๆ:
def some_method
puts caller
end
some_method # => caller.rb:5:in `<main>'
ตอนนี้ เป็นเรื่องง่ายมากที่จะดึงชื่อไฟล์ออกจากที่นั่น และดึงข้อมูลบางอย่างที่เทียบเท่ากับ DATA สำหรับไฟล์นั้น
def get_caller_data
puts File.read(caller.first.split(":").first).split("__END__", 2).last
end
ใช้เพื่อความดี ไม่ใช่ความชั่ว
หวังว่าจะเห็นได้ชัดว่าลูกเล่นเหล่านี้ไม่ใช่สิ่งที่คุณต้องการใช้ทุกวัน ไม่ได้สร้างฐานโค้ดขนาดใหญ่ที่สะอาดและบำรุงรักษาได้อย่างแน่นอน
อย่างไรก็ตาม ในบางครั้งคุณต้องการบางสิ่งที่รวดเร็วและสกปรก ไม่ว่าจะเป็นสคริปต์ยูทิลิตี้แบบใช้ครั้งเดียวหรือเพื่อพิสูจน์แนวคิด ในกรณีนั้น DATA
และ __END__
มีประโยชน์มาก