30 Days of Tech: Day 20 - Unmixed Methods
June 20th, 2008
mixology is a Ruby extension implemented by some of the Something Nimble collaborators. In short, it allows you to add modules to Objects (much like Ruby’s extend), but in such a way that they can also be removed. Mixology refers to these operations as mixin and unmix. If you want to know more about Mixology and the people behind it, here is a good resource. For the purposes of our discussion, here’s a simple example:
irb(main):001:0> require 'rubygems'; require 'mixology'
=> true
irb(main):002:0> module A
irb(main):003:1> def foo; puts "bar"; end
irb(main):004:1> end
=> nil
irb(main):005:0> o = Object.new
=> #<Object:0x4f2b8>
irb(main):006:0> o.foo
NoMethodError: undefined method `foo' for #<Object:0x4f2b8>
from (irb):6
irb(main):007:0> o.mixin A
=> #<Object:0x4f2b8>
irb(main):008:0> o.foo
bar
=> nil
irb(main):009:0> o.unmix A
=> #<Object:0x4f2b8>
irb(main):010:0> o.foo
NoMethodError: undefined method `foo' for #<Object:0x4f2b8>
from (irb):10
We can see that after o.unmix A the foo method is no longer accessible on the object o. But is there any way we could end up executing the foo method anyhow? It turns out there is. Let’s look at this example:
irb(main):001:0> require 'rubygems'; require 'mixology'
=> true
irb(main):002:0> module A
irb(main):003:1> def foo; puts self.inspect; end
irb(main):004:1> end
=> nil
irb(main):005:0> o = Object.new
=> #<Object:0x4d1ac>
irb(main):006:0> o.mixin A
=> #<Object:0x4d1ac>
irb(main):007:0> o.foo
#<Object:0x4d1ac>
=> nil
irb(main):008:0> foo = o.method(:foo)
=> #<Method: Object(A)#foo>
irb(main):009:0> o.unmix(A)
=> #<Object:0x4d1ac>
irb(main):010:0> o.foo
NoMethodError: undefined method `foo' for #<Object:0x4d1ac>
from (irb):10
irb(main):011:0> foo.call
#<Object:0x4d1ac>
=> nil
In this second example we can see that even though foo can’t be invoked through the object (since module A is no longer mixed in), Ruby has no problem invoking the method using the Method object that was grabbed before unmixing A. So yes, we can access foo if we should absolutely need to. There is one gotcha with this technique, however—even though we are in foo, no other methods from A are available!
irb(main):001:0> require 'rubygems'; require 'mixology'
=> true
irb(main):002:0> module A
irb(main):003:1> def called_as_method_object
irb(main):004:2> puts "got in, executing method_no_longer_available"
irb(main):005:2> method_no_longer_available
irb(main):006:2> end
irb(main):007:1>
irb(main):008:1* def method_no_longer_available
irb(main):009:2> raise "Will never make it"
irb(main):010:2> end
irb(main):011:1> end
=> nil
irb(main):012:0> o = Object.new
=> #<Object:0x366dc>
irb(main):013:0> o.mixin A
=> #<Object:0x366dc>
irb(main):014:0> method = o.method(:called_as_method_object)
=> #<Method: Object(A)#called_as_method_object>
irb(main):015:0> o.unmix A
=> #<Object:0x366dc>
irb(main):016:0> method.call
got in, executing method_no_longer_available
NameError: undefined local variable or method `method_no_longer_available' for #<Object:0x366dc>
from (irb):5:in `called_as_method_object'
from (irb):16:in `call'
from (irb):16
from :0
The implication of this is significant. If the code in called_as_method_object becomes complicated, you can’t even perform extract method to refactor it! Given that this technique is obscure to begin with and has this major limitation, I recommend you not use it if at all possible. That being said, you may find yourself in a situation where it’s the best option available, at least for the moment. I mean, how else would I come up with this stuff?
Sorry, comments are closed for this article.