Computer >> Máy Tính >  >> Lập trình >> Ruby

Kết hợp mã và dữ liệu trong Ruby với DATA và __END__

Bạn có biết rằng Ruby cung cấp một cách để tập lệnh của bạn sử dụng tệp nguồn của chính nó làm nguồn dữ liệu không? Đó là một thủ thuật nhỏ gọn có thể giúp bạn tiết kiệm thời gian khi viết kịch bản một lần và các bằng chứng về khái niệm. Hãy cùng khám phá!

DATA và KẾT THÚC

Trong ví dụ dưới đây, tôi đang sử dụng một từ khóa vui nhộn có tên là __END__ . Mọi thứ bên dưới __END__ sẽ bị trình thông dịch Ruby bỏ qua. Nhưng thú vị hơn, ruby ​​cung cấp cho bạn một đối tượng IO được gọi là DATA , cho phép bạn đọc mọi thứ bên dưới __END__ giống như bạn có thể đọc từ bất kỳ tệp nào khác.

Trong ví dụ sau, chúng tôi lặp lại từng dòng và in nó.

DATA.each_line do |line|
  puts line
end

__END__
Doom
Quake
Diablo

Ví dụ thực tế yêu thích của tôi về kỹ thuật này sử dụng DATA để chứa một mẫu ERB. Nó cũng hoạt động với YAML, CSV, v.v. M

require 'erb'

time = Time.now
renderer = ERB.new(DATA.read)
puts renderer.result()

__END__
The current time is <%= time %>.

Bạn thực sự có thể sử dụng DATA để đọc nội dung bên trên __END__ từ khóa. Đó là bởi vì DATA thực sự là một con trỏ đến toàn bộ tệp nguồn, được chuyển tiếp nhanh tới __END__ từ khóa. Bạn có thể thấy điều này nếu bạn tua lại đối tượng IO trước khi in nó. Ví dụ dưới đây in ra toàn bộ tệp nguồn.

DATA.rewind
puts DATA.read # prints the entire source file

__END__
meh

Dấu phân tách nhiều tệp

Một trong những nhược điểm lớn của kỹ thuật này là nó chỉ thực sự hoạt động nếu tập lệnh của bạn phù hợp với một tệp nguồn duy nhất và bạn đang chạy trực tiếp tệp đó, thay vì bao gồm nó.

Trong ví dụ dưới đây, tôi có hai tệp, mỗi tệp có __END__ riêng của chúng tiết diện. Tuy nhiên, chỉ có thể có một DATA toàn cầu. Vì vậy, __END__ phần của tệp thứ hai không thể truy cập được.

# 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
----------------------

Hoạt động xoay quanh nhiều tệp

Sinatra có một tính năng khá thú vị cho phép bạn thêm nhiều mẫu nội tuyến vào ứng dụng của mình bằng cách đặt chúng sau __END__ bản tường trình. Nó trông như thế này:

# 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.

Nhưng chính xác thì sinatra có thể làm được điều này như thế nào? Rốt cuộc, ứng dụng của bạn có thể sẽ được tải bằng giá đỡ. Bạn sẽ không chạy ruby myapp.rb trong sản xuất! Chắc hẳn họ đã tìm ra cách để sử dụng DATA với nhiều tệp.

Mặc dù vậy, nếu bạn tìm hiểu kỹ nguồn Sinatra một chút, bạn sẽ thấy rằng họ đang gian lận. Họ không sử dụng DATA ở tất cả. Thay vào đó, họ đang làm điều gì đó tương tự như mã bên dưới.

# 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)

Nó thực sự phức tạp hơn một chút, vì họ không muốn đọc __FILE__ . Đó sẽ chỉ là tệp sinatra / base.rb. Thay vào đó, họ muốn lấy nội dung của tệp đã gọi một hàm. Họ nhận được điều này bằng cách phân tích cú pháp kết quả của trình gọi.

Hàm người gọi sẽ cho bạn biết vị trí mà hàm hiện đang chạy đã được gọi. Đây là một ví dụ nhanh:

def some_method
  puts caller
end

some_method # => caller.rb:5:in `<main>'

Bây giờ, việc kéo tên tệp ra khỏi đó và giải nén một cái gì đó tương đương với DATA cho tệp đó là một vấn đề khá đơn giản.

def get_caller_data
  puts File.read(caller.first.split(":").first).split("__END__", 2).last
end

Sử dụng nó vì điều tốt, chứ không phải điều xấu

Hy vọng rằng rõ ràng những thủ thuật như thế này không phải là thứ mà bạn sẽ muốn sử dụng hàng ngày. Chúng không chính xác tạo ra các cơ sở mã lớn sạch sẽ, có thể bảo trì được.

Tuy nhiên, đôi khi bạn cần một cái gì đó nhanh chóng và bẩn thỉu, cho một tập lệnh tiện ích một lần hoặc cho một bằng chứng về khái niệm. Trong trường hợp đó, DATA__END__ có thể khá hữu ích.