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.