Nếu bạn chưa bao giờ dùng refinements, bạn có thể bất ngờ. Bạn có thể nghe refinements được giới thiệu để replace monkey patching. Vì vậy bạn mong đợi implement được kiểu ActiveSupport hours method:
module TimeExtension refine Fixnum do def hours self * 60 end end end class MyFramework using TimeExtension end class MyApp < MyFramework def index 1.hours end end MyApp.new.index # undefined method `hours' for 1:Fixnum (NoMethodError)
Nếu chạy code trên, bạn thấy nó không chạy. Vậy chuyện gì xảy ra?
Giống nhiều ý tưởng tuyệt vời, concept gốc của refinements phải tweak để làm việc với thực tế lạnh lẽo.
Hành vi bất ngờ trong code trên do một tweak này. Cụ thể, rule rằng refinements là lexically scoped.
Lexical scoping là gì?
Khi nói gì đó là lexical, nghĩa là liên quan đến text — code trên màn hình thay vì ý nghĩa của code đó.
Nếu hai dòng lexically scoped, đơn giản nghĩa là chúng xảy ra trong cùng code block — bất kể code block đó evaluate thành gì.
Dễ thấy ví dụ hơn nói:
class B # x và y chia sẻ cùng lexical scope x = 1 y = 1 end class B # z có lexical scope khác x và y, dù nó ở cùng class. z = 3 end
Refinements là lexically scoped
Khi apply refinement với từ khóa using, refinement chỉ visible trong lexical scope.
Ví dụ cho thấy ý tôi:
module TimeExtension refine Fixnum do def hours self * 60 end end end class MyApp using TimeExtension def index 1.hours end end class MyApp def show 2.hours end end MyApp.new.show # undefined method `hour' for 1:Fixnum (NoMethodError)
Dù cả index và show đều thuộc cùng class, chỉ index có access refinement, vì chỉ nó chia sẻ lexical scope với statement using.
Điều này có vẻ hơi kỳ lạ, vì hầu như mọi thứ khác trong Ruby là dynamically scoped. Khi bạn thêm method vào class, method ở đó khi thoát class definition. Nhưng refinements không hoạt động vậy.
Điều này có số hậu quả không rõ ràng ngay.
Không thể dynamically invoke refinement method
Vì method send không được định nghĩa trong cùng code block chứa using statement, nó không thấy refinement.
Vì vậy này không chạy:
class MyApp using TimeExtension def index 1.send(:hours) end end
Không thể query sự tồn tại của refinement method
Method respond_to? cũng không trong cùng code block. Vì vậy nó không chạy vì cùng lý do send không chạy.
class MyApp using TimeExtension def index 1.respond_to?(:hours) end end
Kết luận
Tôi hy vọng điều này làm rõ confusion tôi thấy mọi người gặp về refinements. Chúng chắc chắn là feature potentially useful của Ruby, nhưng nếu chưa từng dùng, chúng hơi tricky.