2012-01-29 25 views
21

I love the autoload functionality of Ruby; tuy nhiên, nó là going away in future versions of Ruby vì nó không bao giờ an toàn chỉ.Tự nạp các lớp trong Ruby mà không có `autoload`

Vì vậy, ngay bây giờ tôi muốn giả vờ nó đã biến mất và viết mã của tôi mà không có nó, bởi thực hiện cơ chế lười tải mình bản thân. Tôi muốn thực hiện nó theo cách đơn giản nhất có thể (Tôi không quan tâm về an toàn luồng ngay bây giờ). Ruby nên cho phép chúng tôi làm điều này.

Hãy bắt đầu bằng việc làm tăng một lớp const_missing:

class Dummy 
    def self.const_missing(const) 
    puts "const_missing(#{const.inspect})" 
    super(const) 
    end 
end 

Ruby sẽ gọi phương thức đặc biệt này khi chúng tôi cố gắng để tham khảo một hằng số dưới 'Dummy' đó là mất tích, ví dụ nếu chúng ta cố gắng tham khảo "Dummy: : Xin chào ", nó sẽ gọi const_missing với Symbol :Hello. Đây chính là điều chúng ta cần, vì vậy hãy mang nó hơn nữa:

class Dummy 
    def self.const_missing(const) 
    if :OAuth == const 
     require 'dummy/oauth' 
     const_get(const)  # warning: possible endless loop! 
    else 
     super(const) 
    end 
    end 
end 

Bây giờ nếu chúng ta tham khảo "Dummy :: OAuth", nó sẽ đòi hỏi sự "giả/oauth.rb" tập tin đó được dự kiến ​​để xác định " Dummy :: OAuth "hằng số. Có khả năng xảy ra vòng lặp vô tận khi chúng tôi gọi const_get (vì nó có thể gọi const_missing nội bộ), nhưng việc bảo vệ chống lại điều đó nằm ngoài phạm vi của câu hỏi này.

Vấn đề lớn là, toàn bộ giải pháp này bị hỏng nếu tồn tại một mô-đun có tên "OAuth" trong không gian tên cấp cao nhất. Tham chiếu "Dummy :: OAuth" sẽ bỏ qua số const_missing và chỉ trả lại "OAuth" từ cấp cao nhất. Hầu hết các trường Ruby cũng sẽ tạo ra một cảnh báo về vấn đề này:

warning: toplevel constant OAuth referenced by Dummy::OAuth 

This was reported as a problem way back in 2003 nhưng tôi không thể tìm thấy bằng chứng cho thấy đội ngũ nòng cốt Ruby đã bao giờ quan tâm về vấn đề này. Ngày nay, các triển khai Ruby phổ biến nhất mang cùng một hành vi.

Vấn đề là const_missing bị bỏ qua một cách âm thầm với một hằng số trong không gian tên cấp cao nhất. Điều này sẽ không xảy ra nếu "Dummy :: OAuth" được khai báo với chức năng autoload của Ruby. Bất kỳ ý tưởng làm thế nào để làm việc xung quanh này?

+0

Điều này có vẻ như là một gợi ý ngớ ngẩn, nhưng bạn có thể nhìn vào nguồn C của 'autoload'? Tôi chắc rằng bạn có thể tìm thấy nó ở đâu đó trong nguồn Ruby. Nếu bạn không thể làm điều đó trong Ruby thẳng, có tùy chọn tạo phần mở rộng C (có quyền truy cập vào phần dưới của trình thông dịch). – Linuxios

+0

Đó là một tùy chọn. – mislav

+0

nghe có vẻ giống như một điều bạo lực, nhưng bạn không thể 'remove_const' trên lớp cấp cao nhất? – phoet

Trả lời

5

Điều này đã được nêu trong vé Rails một thời gian trước và khi tôi điều tra nó dường như không có cách nào làm tròn nó. Vấn đề là Ruby sẽ tìm kiếm tổ tiên trước khi gọi const_missing và vì tất cả các lớp có Object làm tổ tiên nên bất kỳ hằng số cấp cao nào sẽ luôn được tìm thấy. Nếu bạn có thể hạn chế bản thân để chỉ sử dụng mô-đun cho namespacing sau đó nó sẽ làm việc vì họ không có Object như tổ tiên, ví dụ:

>> class A; end 
>> class B; end 
>> B::A 
(irb):3: warning: toplevel constant A referenced by B::A 

>> B.ancestors 
=> [B, Object, Kernel, BasicObject] 

>> module C; end 
>> module D; end 
>> D::C 
NameError: uninitialized constant D::C 

>> D.ancestors 
=> [D] 
+0

Vâng, thật không may là chúng tôi không thể làm cho nó hoạt động với các lớp học, tuy nhiên rất tốt khi biết rằng ít nhất nó hoạt động với các mô-đun. Cảm ơn! – mislav

0

Tôi gặp vấn đề khi ree 1.8.7 (bạn không đề cập đến một phiên bản cụ thể) nếu tôi sử dụng const_get bên trong const_missing, nhưng không phải nếu tôi sử dụng ::. Tôi không thích sử dụng eval, nhưng nó không làm việc ở đây:

class Dummy 
    def self.const_missing(const) 
    if :OAuth == const 
     require 'dummy/oauth' 
     eval "self::#{const}" 
    else 
     super(const) 
    end 
    end 
end 

module Hello 
end 

Dummy.const_get :Hello # => ::Hello 
Dummy::Hello   # => Dummy::Hello 

Tôi muốn Module đã có một phương pháp :: vì vậy bạn có thể làm self.send :"::", const.

+0

Rất tiếc vì không đề cập đến các phiên bản cụ thể. Vấn đề của tôi là hiện diện trên 1.8.7, 1.9.2, 1.9.3, Rubinius (cả ổn định và cạnh) và khác. Tôi sẽ cố gắng gợi ý của bạn, nhưng từ những gì tôi nhận được trong thử nghiệm của tôi phương pháp 'const_missing' đã không được gọi là ở tất cả. Vì vậy, nó không quan trọng như thế nào tôi thực hiện nó. – mislav

+0

Điều đó tùy thuộc vào việc bạn sử dụng 'Dummy.const_get: Hello' hoặc' Dummy :: Hello' * bên ngoài * phương thức 'const_missing'. Chỉ ':: Hello' sẽ kích hoạt' const_missing' - hoặc ít nhất đó là sự thật trên thực thi Ruby mà tôi đã thử. –

0

tải Lazy là một mẫu thiết kế rất phổ biến, bạn có thể thực hiện nó bằng nhiều cách .như:

class Object 
    def bind(key, &block) 
    @hooks ||= Hash.new{|h,k|h[k]=[]} 
    @hooks[key.to_sym] << [self,block] 
    end 

    def trigger(key) 
    @hooks[key.to_sym].each { |context,block| block.call(context) } 
    end 
end 

Sau đó, bạn có thể

bind :json do 
    require 'json' 
end 

begin 
    JSON.parse("[1,2]") 
rescue 
    trigger :json 
    retry 
end 
Các vấn đề liên quan