Use Ruby’s Refinements Anywhere With An Anonymous Class
For a long time I’ve been looking for a way to use Ruby’s refinements without building a class & method setup and then instantiating an instance of the class just to use it. And now I’ve finally found a solution.
module Moo refine Fixnum do def to_s "moo" end end end # begin/end blocks allow access to local variables begin # valid Ruby 2.2.3 and NOT Ruby 2.3.0 using Moo 1.to_s end # => "moo" # class syntax has no access to outer local variables class << Class.new # valid Ruby 2.3.0 using Moo 1.to_s end # => "moo"
The best work around I’ve found for accessing values outside the class wood be constants (preferable over global vars). If you wanted to take the extra effort your can have a constant be an instance of Binding to grab the local variables.
class Object # bv for binding & variable def bv(a_binding, local_var) a_binding.local_variable_get local_var end end x = 5 A = binding class << Class.new using Moo bv(A, :x).to_s end # => "moo"
I’ve put in a feature request for Ruby’s using method to take a block syntax and allow access to local variables Feature Request #12281. So this would bring back the convenience of local variable access available in Ruby 2.2.3.
Example of Refinement Use in Just One Method
If you use the Object#bv method defined above you may use kind of a dynamic constant variable to grab the local variable.
class Example def number_to_moo(num) Example.const_set :B, binding result = class << Class.new using Moo bv(B, :num).to_s end Example.send :remove_const, :B result end end Example.new.number_to_moo 9 # => "moo"
But at this point we’ve gone too far to bend against the design of how a refinement is used (and other Ruby design principles) and it would be simpler to just write the refinement as intended by design.
class Example2 def number_to_moo(num) DoTheThing.new.the_thing(num) end class DoTheThing using Moo def the_thing(num) num.to_s end end private_constant :DoTheThing end Example2.new.number_to_moo 9 # => "moo"
As you can see this is a lot of class wrapper just to refine one little thing in a method. So I really hope my feature request get’s accepted for Ruby and we can simplify the use of refinements anywhere and still stick to a strict lexical scope.
Refinements are great but they still have a way to go (see: Ruby Refinements – Not quite ready for production). At the time of this writing the Ruby core team seams really focused on just patch/bug related updates and I haven’t seen them accept a new feature since before the release of 2.3.0. But once there next release version is out I’m hopeful for newer and improved features for the language 🙂 .
I’ve been into programming in the Rust language a lot lately and there use of implementations on types works in the same manner as Ruby’s refinements without all the extra hassle. In Rust where ever you write use my_crate::MyTrait; it adds the functionality to a specific object type just like Ruby’s refinements and it’s available within whatever scope you put it. I’m hoping Ruby heads more in this direction. I’m sure I’ll be writing about Rust soon so look forward to it!
-Daniel P. Clark
Image by solarisgirl via the Creative Commons Attribution-ShareAlike 2.0 Generic License.