2009-08-27 25 views
50

Tôi có một mẫu ERB inlined vào mã Ruby:Mẫu Ruby: Làm thế nào để chuyển các biến vào ERB nội tuyến?

require 'erb' 

DATA = { 
    :a => "HELLO", 
    :b => "WORLD", 
} 

template = ERB.new <<-EOF 
    current key is: <%= current %> 
    current value is: <%= DATA[current] %> 
EOF 

DATA.keys.each do |current| 
    result = template.result 
    outputFile = File.new(current.to_s,File::CREAT|File::TRUNC|File::RDWR) 
    outputFile.write(result) 
    outputFile.close 
end 

tôi không thể vượt qua biến "hiện tại" vào mẫu.

Lỗi này là:

(erb):1: undefined local variable or method `current' for main:Object (NameError) 

Làm thế nào để sửa lỗi này?

Trả lời

10

Got it!

tôi tạo ra một lớp bindings

class BindMe 
    def initialize(key,val) 
     @key=key 
     @val=val 
    end 
    def get_binding 
     return binding() 
    end 
end 

và vượt qua một ví dụ để ERB

dataHash.keys.each do |current| 
    key = current.to_s 
    val = dataHash[key] 

    # here, I pass the bindings instance to ERB 
    bindMe = BindMe.new(key,val) 

    result = template.result(bindMe.get_binding) 

    # unnecessary code goes here 
end 

Mẫu tập tin .erb trông như thế này:

Key: <%= @key %> 
+8

Điều này là không cần thiết. Trong mã từ câu hỏi ban đầu của bạn, chỉ cần thay thế "result = template.result" bằng "result = template.result (binding)" Điều đó sẽ sử dụng ngữ cảnh của mỗi khối thay vì ngữ cảnh cấp cao nhất. – sciurus

4

Tôi không thể cung cấp cho bạn câu trả lời hay về lý do điều này xảy ra vì tôi không chắc chắn cách thức hoạt động của ERB, nhưng chỉ cần nhìn vào số ERB RDocs, nó nói rằng bạn cần bindinga Binding or Proc object which is used to set the context of code evaluation. Đang thử mã trên của bạn một lần nữa và chỉ thay thế result = template.result với result = template.result(binding) làm cho nó hoạt động.

Tôi chắc chắn/hy vọng ai đó sẽ nhảy vào đây và cung cấp giải thích chi tiết hơn về những gì đang diễn ra. Chúc mừng.

EDIT: Để biết thêm thông tin về Binding và làm cho tất cả điều này rõ ràng hơn một chút (ít nhất là đối với tôi), hãy xem Binding RDoc.

0

EDIT: Đây là giải pháp thay thế dơ bẩn. Xin vui lòng xem câu trả lời khác của tôi.

Nó hoàn toàn xa lạ, nhưng thêm

current = "" 

trước khi "for-each" vòng lặp sửa chữa vấn đề.

Thiên Chúa ban phước cho ngôn ngữ kịch bản và "tính năng ngôn ngữ" của họ ...

+0

Tôi nghĩ điều này là do tham số chặn không phải là biến thực sự trong Ruby 1.8. Điều này đã thay đổi trong Ruby 1.9. –

+1

Ràng buộc mặc định mà ERB sử dụng để đánh giá các biến là ràng buộc mức cao nhất. Biến "hiện tại" của bạn không tồn tại trong liên kết cấp cao nhất, trừ khi bạn sử dụng nó ở đó trước (gán một giá trị cho nó). – molf

+0

Vì vậy, trong Ruby 1.9 nó sẽ không hoạt động? –

57

Đối với một giải pháp đơn giản, sử dụng OpenStruct:

require 'erb' 
require 'ostruct' 
namespace = OpenStruct.new(name: 'Joan', last: 'Maragall') 
template = 'Name: <%= name %> <%= last %>' 
result = ERB.new(template).result(namespace.instance_eval { binding }) 
#=> Name: Joan Maragall 

Mã ở trên đủ đơn giản nhưng có (ít nhất) hai vấn đề: 1) Vì nó dựa trên OpenStruct, quyền truy cập vào biến không tồn tại trả về nil trong khi bạn có thể muốn không thành công. 2) binding được gọi là trong một khối, đó là nó, trong một đóng cửa, do đó, nó bao gồm tất cả các biến địa phương trong phạm vi (trên thực tế, các biến này sẽ đổ bóng các thuộc tính của cấu trúc!).

Vì vậy, đây là một giải pháp, tiết hơn nhưng không có bất kỳ những vấn đề này:

class Namespace 
    def initialize(hash) 
    hash.each do |key, value| 
     singleton_class.send(:define_method, key) { value } 
    end 
    end 

    def get_binding 
    binding 
    end 
end 

template = 'Name: <%= name %> <%= last %>' 
ns = Namespace.new(name: 'Joan', last: 'Maragall') 
ERB.new(template).result(ns.get_binding) 
#=> Name: Joan Maragall 

Tất nhiên, nếu bạn đang sử dụng điều này thường xuyên, chắc chắn rằng bạn tạo tiện ích String#erb cho phép bạn viết một cái gì đó như "x=<%= x %>, y=<%= y %>".erb(x: 1, y: 2).

+0

Bạn đã kiểm tra điều này? Trên hệ thống của tôi, mã chính xác của bạn tạo ra "NameError: undefined local variable hoặc method' name 'cho main: Object. " (Chỉnh sửa: Xuất hiện dưới dạng sự cố 1.9.2 http://stackoverflow.com/questions/3242470/problem-using-openstruct-with-erb) –

+0

@Ryan. Thật vậy, tôi đã thử nghiệm nó chỉ 1.8.7, cập nhật. Tôi sẽ thêm một câu trả lời trong câu hỏi bạn liên kết, tôi nghĩ 'instance_eval' là giải pháp dễ nhất. Cảm ơn bạn đã viết ra vấn đề. – tokland

+0

@Ryan, đã thêm giải pháp mới trong http://stackoverflow.com/a/8293786/188031. – tokland

5
require 'erb' 

class ERBContext 
    def initialize(hash) 
    hash.each_pair do |key, value| 
     instance_variable_set('@' + key.to_s, value) 
    end 
    end 

    def get_binding 
    binding 
    end 
end 

class String 
    def erb(assigns={}) 
    ERB.new(self).result(ERBContext.new(assigns).get_binding) 
    end 
end 

REF: giải pháp http://stoneship.org/essays/erb-and-the-context-object/

19

đơn giản sử dụng Binding:

b = binding 
b.local_variable_set(:a, 'a') 
b.local_variable_set(:b, 'b') 
ERB.new(template).result(b) 
+1

' local_variable_set' được giới thiệu trong ruby ​​2.1. – kbrock

0

Như những người khác đã nói, để đánh giá ERB với một số tập hợp các biến, bạn cần có một ràng buộc thích hợp. Có một số giải pháp với việc xác định các lớp và phương thức nhưng tôi nghĩ đơn giản nhất và cho hầu hết sự kiểm soát và an toàn nhất là tạo ra một ràng buộc sạch sẽ và sử dụng nó để phân tích cú pháp ERB. Dưới đây là quan điểm của tôi về nó (ruby 2.2.x):

module B 
    def self.clean_binding 
    binding 
    end 

    def self.binding_from_hash(**vars) 
    b = self.clean_binding 
    vars.each do |k, v| 
     b.local_variable_set k.to_sym, v 
    end 
    return b 
    end 
end 
my_nice_binding = B.binding_from_hash(a: 5, **other_opts) 
result = ERB.new(template).result(my_nice_binding) 

Tôi nghĩ rằng với eval và không ** tương tự có thể được thực hiện làm việc với ruby ​​lớn hơn 2.1

5

Trong mã từ câu hỏi ban đầu, chỉ cần thay thế

result = template.result 

với

result = template.result(binding) 

Điều đó sẽ sử dụng thứ e bối cảnh của mỗi khối thay vì ngữ cảnh cấp cao nhất.

(Chỉ cần trích xuất nhận xét bằng @sciurus là câu trả lời vì đó là câu trả lời ngắn nhất và chính xác nhất.)

Các vấn đề liên quan