If you're not familiar with Mixology, it's a gem that enables modules to be both mixed into and unmixed from objects, creating a great way to implement the state pattern in Ruby.
The first test for Mixology is fairly straightforward. Let's say we have an object with a foo method that returns "foo from object", and a module with a foo method that returns "foo from mixin". When we mix in the module, foo should return "foo from mixin". We should then be able to unmix the module from the object and have the foo method return "foo from object" again.
def test_unmix object = Class.new { def foo; "foo from object"; end }.new mixin = Module.new { def foo; "foo from mixin"; end } object.mixin mixin assert_equal "foo from mixin", object.foo object.unmix mixin assert_equal "foo from object", object.foo end
Using MRI, we're at the point where we need to drop down into C code to implement this. My C skills aren't too great, which is why Patrick Farley implemented Mixology. But my Ruby skills are much better. Because Rubinius implements as much of Ruby in Ruby as possible, we can get this test to pass with 100% Ruby code.
module Mixology def mixin(mod) reset_method_cache IncludedModule.new(mod).attach_to metaclass reset_method_cache self end def unmix(mod_to_unmix) last_super = metaclass this_super = metaclass.direct_superclass while this_super if (this_super == mod_to_unmix || this_super.respond_to?(:module) && this_super.module == mod_to_unmix) reset_method_cache last_super.superclass = this_super.direct_superclass reset_method_cache return self else last_super = this_super this_super = this_super.direct_superclass end end self end protected def reset_method_cache self.methods.each do |name| name = self.metaclass.send(:normalize_name,name) Rubinius::VM.reset_method_cache(name) end end end Object.send :include, Mixology
And now if we run the test, we'll have green.
$ rubinius test/mixology_test.rb
require 'test/unit/testcase' has been deprecated
Loaded suite test/mixology_test
Started
.
Finished in 0.024741 seconds.
1 tests, 1 assertions, 0 failures, 0 errors
It's not every day when you want implement something like unmixing a module. But if you do want to, Rubinius empowers you to do much more at the Ruby level. It's also a great way to learn more about Ruby's internals. For example, the IncludedModule class used in this implementation is how Ruby puts modules in the inheritance hierarchy for an object.
There are still 5 pending tests before Mixology's whole test suite passes in Rubinius. If you want to give it a shot, fork my github repo.