├── .gitignore ├── lib ├── .gitignore ├── win32 │ └── mixology.so └── mixology_rubinius.rb ├── ext └── mixology │ ├── .gitignore │ ├── extconf.rb │ ├── mixology.c │ └── MixologyService.java ├── test ├── test_helper.rb └── mixology_test.rb ├── README.markdown └── Rakefile /.gitignore: -------------------------------------------------------------------------------- 1 | *.rbc 2 | *.o 3 | *.so 4 | pkg/ 5 | 6 | -------------------------------------------------------------------------------- /lib/.gitignore: -------------------------------------------------------------------------------- 1 | mixology.jar 2 | mixology.bundle 3 | -------------------------------------------------------------------------------- /lib/win32/mixology.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dan-manges/mixology/HEAD/lib/win32/mixology.so -------------------------------------------------------------------------------- /ext/mixology/.gitignore: -------------------------------------------------------------------------------- 1 | MixologyService.class 2 | mixology.jar 3 | mixology.o 4 | Makefile 5 | mixology.bundle -------------------------------------------------------------------------------- /ext/mixology/extconf.rb: -------------------------------------------------------------------------------- 1 | require "mkmf" 2 | dir_config "mixology" 3 | $CPPFLAGS += " -DRUBY_19" if RUBY_VERSION =~ /1.9/ 4 | create_makefile "mixology" 5 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | # In Ruby 1.9, require test/unit will implicitly require pp, we do it here explicitly to ensure compatibily. 3 | require "pp" 4 | 5 | $LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib" 6 | if defined?(RUBY_ENGINE) && RUBY_ENGINE == "rbx" 7 | require "mixology_rubinius" 8 | else 9 | require "mixology" 10 | end 11 | -------------------------------------------------------------------------------- /lib/mixology_rubinius.rb: -------------------------------------------------------------------------------- 1 | module Mixology 2 | def mixin(mod) 3 | unmix mod 4 | reset_method_cache 5 | IncludedModule.new(mod).attach_to metaclass 6 | reset_method_cache 7 | self 8 | end 9 | 10 | def unmix(mod_to_unmix) 11 | last_super = metaclass 12 | this_super = metaclass.direct_superclass 13 | while this_super 14 | break if this_super == self.class 15 | if (this_super == mod_to_unmix || 16 | this_super.respond_to?(:module) && this_super.module == mod_to_unmix) 17 | reset_method_cache 18 | last_super.superclass = this_super.direct_superclass 19 | reset_method_cache 20 | return self 21 | else 22 | last_super = this_super 23 | this_super = this_super.direct_superclass 24 | end 25 | end 26 | self 27 | end 28 | 29 | protected 30 | 31 | def reset_method_cache 32 | self.methods.each do |name| 33 | name = self.metaclass.send(:normalize_name,name) 34 | Rubinius::VM.reset_method_cache(name) 35 | end 36 | end 37 | end 38 | 39 | Object.send :include, Mixology 40 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | mixology 2 | ======== 3 | 4 | a gem that allows objects to effectively mixin and unmix modules 5 | 6 | installation 7 | ------------ 8 | 9 | gem install mixology 10 | 11 | usage 12 | ----- 13 | 14 | require "mixology" 15 | 16 | mixin = Module.new { def foo; "foo from mixin"; end } 17 | object = Class.new { def foo; "foo from object"; end }.new 18 | 19 | object.mixin mixin 20 | object.foo #=> "foo from mixin" 21 | 22 | object.unmix mixin 23 | object.foo #=> "foo from object" 24 | 25 | that's pretty much it. for other examples, take a look at the tests. 26 | 27 | implementations 28 | --------------- 29 | 30 | * MRI 1.8.x, 1.9.x 31 | * JRuby 1.1.x 32 | 33 | collaborators 34 | ------------- 35 | 36 | * [Patrick Farley](http://www.klankboomklang.com/) 37 | * anonymous z 38 | * [Dan Manges](http://www.dcmanges.com/blog) 39 | * Clint Bishop 40 | * [Banister Fiend](http://banisterfiend.wordpress.com/) 41 | * [Tianyi Cui](http://cuitianyi.com/) 42 | 43 | source 44 | ------ 45 | 46 | hosted on [github](http://github.com/dan-manges/mixology/tree/master) 47 | 48 | license 49 | ------- 50 | 51 | released under [Ruby's license](http://www.ruby-lang.org/en/LICENSE.txt) 52 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "rake" 2 | require 'rake/clean' 3 | require 'rake/gempackagetask' 4 | require "rake/testtask" 5 | 6 | desc "clean, compile, test" 7 | task :default => %w[clean compile test] 8 | 9 | Rake::TestTask.new("test") do |t| 10 | t.pattern = "test/**/*_test.rb" 11 | end 12 | 13 | desc "Builds the extension" 14 | if RUBY_PLATFORM =~ /java/ 15 | task :compile => :compile_java 16 | else 17 | task :compile => %W[ext/mixology/Makefile ext/mixology/mixology.#{Config::CONFIG['DLEXT']}] 18 | end 19 | 20 | file "ext/mixology/Makefile" => ["ext/mixology/extconf.rb"] do 21 | Dir.chdir("ext/mixology") do 22 | ruby "extconf.rb" 23 | end 24 | end 25 | 26 | file "ext/mixology/mixology.#{Config::CONFIG['DLEXT']}" do 27 | Dir.chdir("ext/mixology") do 28 | sh "make" 29 | end 30 | cp "ext/mixology/mixology.#{Config::CONFIG['DLEXT']}", "lib" 31 | end 32 | 33 | CLEAN.include %w[ext/mixology/Makefile ext/mixology/mixology.bundle ext/mixology/mixology.so lib/mixology.bundle lib/mixology.so ext/mixology/mixology.o] 34 | CLEAN.include %w[ext/mixology/MixableService.class ext/mixology/mixable.jar lib/mixology.jar] 35 | 36 | specification = Gem::Specification.new do |s| 37 | s.name = "mixology" 38 | s.summary = "Mixology enables objects to mixin and unmix modules." 39 | s.version = "0.2.0" 40 | s.author = "anonymous z, Pat Farley, Dan Manges" 41 | s.description = s.summary 42 | s.homepage = "http://mixology.rubyforge.org" 43 | s.rubyforge_project = "mixology" 44 | s.has_rdoc = false 45 | s.autorequire = "mixology" 46 | s.files = FileList['ext/**/*.{c,rb}', '{lib,test}/**/*.rb', '^[A-Z]+$', 'Rakefile'].to_a 47 | if RUBY_PLATFORM =~ /mswin/ 48 | s.platform = Gem::Platform::WIN32 49 | s.files += ["lib/mixology.so"] 50 | elsif RUBY_PLATFORM =~ /java/ 51 | s.platform = "java" 52 | s.files += ["lib/mixology.jar"] 53 | else 54 | s.platform = Gem::Platform::RUBY 55 | s.extensions = FileList["ext/**/extconf.rb"].to_a 56 | end 57 | end 58 | Rake::GemPackageTask.new(specification) do |package| 59 | package.need_zip = false 60 | package.need_tar = false 61 | end 62 | 63 | desc "Compiles the JRuby extension" 64 | task :compile_java do 65 | Dir.chdir("ext/mixology") do 66 | sh %{javac -source 1.5 -target 1.5 -classpath $JRUBY_HOME/lib/jruby.jar MixologyService.java} 67 | sh %{jar cf mixology.jar MixologyService.class} 68 | cp "mixology.jar", "../../lib/mixology.jar" 69 | end 70 | end 71 | 72 | desc "test against multiple ruby implementations" 73 | task :test_multi do 74 | # this is specific to how I have Ruby installed on my machine -Dan 75 | jruby = %w[1.1.3 1.1.4] 76 | mri = %w[1.8.6-p368 1.9.1-p129] 77 | failed = false 78 | test_implementation = proc do |implementation, command| 79 | print "#{implementation}: " 80 | output = `#{command} 2>&1` 81 | if $?.success? && output =~ /\d\d+ tests.*0 failures, 0 errors/ 82 | puts "PASS" 83 | else 84 | puts "FAIL" 85 | failed = true 86 | end 87 | end 88 | jruby.each do |jruby_version| 89 | test_implementation.call( 90 | "JRuby #{jruby_version}", 91 | "JRUBY_HOME=/usr/local/jruby-#{jruby_version} /usr/local/jruby-#{jruby_version}/bin/jruby -S rake" 92 | ) 93 | end 94 | mri.each do |mri_version| 95 | test_implementation.call "MRI #{mri_version}", "/usr/local/ruby-#{mri_version}/bin/rake" 96 | end 97 | fail if failed 98 | end 99 | 100 | -------------------------------------------------------------------------------- /ext/mixology/mixology.c: -------------------------------------------------------------------------------- 1 | #include "ruby.h" 2 | 3 | /* cannot use ordinary CLASS_OF as it does not return an lvalue */ 4 | #define KLASS_OF(c) (RBASIC(c)->klass) 5 | 6 | /* macros for backwards compatibility with 1.8 */ 7 | #ifndef RUBY_19 8 | # define RCLASS_M_TBL(c) (RCLASS(c)->m_tbl) 9 | # define RCLASS_SUPER(c) (RCLASS(c)->super) 10 | # define RCLASS_IV_TBL(c) (RCLASS(c)->iv_tbl) 11 | #endif 12 | 13 | #ifdef RUBY_19 14 | static VALUE class_alloc(VALUE flags, VALUE klass) 15 | { 16 | rb_classext_t *ext = ALLOC(rb_classext_t); 17 | NEWOBJ(obj, struct RClass); 18 | OBJSETUP(obj, klass, flags); 19 | obj->ptr = ext; 20 | RCLASS_IV_TBL(obj) = 0; 21 | RCLASS_M_TBL(obj) = 0; 22 | RCLASS_SUPER(obj) = 0; 23 | RCLASS_IV_INDEX_TBL(obj) = 0; 24 | return (VALUE)obj; 25 | } 26 | #endif 27 | 28 | static void remove_nested_module(VALUE klass, VALUE include_class) 29 | { 30 | if (KLASS_OF(RCLASS_SUPER(klass)) != KLASS_OF(RCLASS_SUPER(include_class))) { 31 | return; 32 | } 33 | if (RCLASS_SUPER(RCLASS_SUPER(include_class)) && BUILTIN_TYPE(RCLASS_SUPER(include_class)) == T_ICLASS) { 34 | remove_nested_module(RCLASS_SUPER(klass), RCLASS_SUPER(include_class)); 35 | } 36 | RCLASS_SUPER(klass) = RCLASS_SUPER(RCLASS_SUPER(klass)); 37 | } 38 | 39 | static VALUE rb_unmix(VALUE self, VALUE module) 40 | { 41 | VALUE klass; 42 | 43 | /* check that module is valid */ 44 | if (TYPE(module) != T_MODULE) 45 | rb_raise(rb_eArgError, "error: parameter must be a module"); 46 | 47 | for (klass = KLASS_OF(self); klass != rb_class_real(klass); klass = RCLASS_SUPER(klass)) { 48 | VALUE super = RCLASS_SUPER(klass); 49 | if (BUILTIN_TYPE(super) == T_ICLASS) { 50 | if (KLASS_OF(super) == module) { 51 | if (RCLASS_SUPER(module) && BUILTIN_TYPE(RCLASS_SUPER(module)) == T_ICLASS) 52 | remove_nested_module(super, module); 53 | 54 | RCLASS_SUPER(klass) = RCLASS_SUPER(RCLASS_SUPER(klass)); 55 | rb_clear_cache(); 56 | } 57 | } 58 | } 59 | return self; 60 | } 61 | 62 | static void add_module(VALUE self, VALUE module) 63 | { 64 | VALUE super = RCLASS_SUPER(rb_singleton_class(self)); 65 | 66 | #ifdef RUBY_19 67 | VALUE klass = class_alloc(T_ICLASS, rb_cClass); 68 | #else 69 | NEWOBJ(klass, struct RClass); 70 | OBJSETUP(klass, rb_cClass, T_ICLASS); 71 | #endif 72 | 73 | if (BUILTIN_TYPE(module) == T_ICLASS) { 74 | module = KLASS_OF(module); 75 | } 76 | if (!RCLASS_IV_TBL(module)) { 77 | RCLASS_IV_TBL(module) = (void*)st_init_numtable(); 78 | } 79 | 80 | RCLASS_IV_TBL(klass) = RCLASS_IV_TBL(module); 81 | RCLASS_M_TBL(klass) = RCLASS_M_TBL(module); 82 | RCLASS_SUPER(klass) = super; 83 | 84 | if (TYPE(module) == T_ICLASS) { 85 | KLASS_OF(klass) = KLASS_OF(module); 86 | } else { 87 | KLASS_OF(klass) = module; 88 | } 89 | OBJ_INFECT(klass, module); 90 | OBJ_INFECT(klass, super); 91 | 92 | RCLASS_SUPER(rb_singleton_class(self)) = (VALUE)klass; 93 | } 94 | 95 | static VALUE rb_mixin(VALUE self, VALUE module) 96 | { 97 | VALUE nested_modules; 98 | int index; 99 | 100 | /* check that module is valid */ 101 | if (TYPE(module) != T_MODULE) 102 | rb_raise(rb_eArgError, "error: parameter must be a module"); 103 | 104 | rb_unmix(self, module); 105 | nested_modules = rb_mod_included_modules(module); 106 | 107 | for (index = RARRAY_LEN(nested_modules); index > 0; index--) { 108 | VALUE nested_module = RARRAY_PTR(nested_modules)[index - 1]; 109 | add_module(self, nested_module); 110 | } 111 | 112 | add_module(self, module); 113 | 114 | rb_clear_cache(); 115 | return self; 116 | } 117 | 118 | void Init_mixology() 119 | { 120 | VALUE Mixology = rb_define_module("Mixology"); 121 | 122 | rb_define_method(Mixology, "mixin", rb_mixin, 1); 123 | rb_define_method(Mixology, "unmix", rb_unmix, 1); 124 | rb_include_module(rb_cObject, Mixology); 125 | } 126 | -------------------------------------------------------------------------------- /ext/mixology/MixologyService.java: -------------------------------------------------------------------------------- 1 | import java.io.IOException; 2 | import java.lang.reflect.InvocationTargetException; 3 | import java.lang.reflect.Method; 4 | import java.util.Iterator; 5 | import java.util.List; 6 | import java.util.ArrayList; 7 | 8 | import org.jruby.Ruby; 9 | import org.jruby.RubyArray; 10 | import org.jruby.RubyClass; 11 | import org.jruby.RubyModule; 12 | import org.jruby.anno.JRubyMethod; 13 | import org.jruby.IncludedModuleWrapper; 14 | import org.jruby.runtime.Block; 15 | import org.jruby.runtime.builtin.IRubyObject; 16 | import org.jruby.exceptions.RaiseException; 17 | import org.jruby.runtime.load.BasicLibraryService; 18 | 19 | public class MixologyService implements BasicLibraryService { 20 | public boolean basicLoad(final Ruby runtime) throws IOException { 21 | RubyModule mixologyModule = runtime.defineModule("Mixology"); 22 | mixologyModule.defineAnnotatedMethods(MixologyService.class); 23 | runtime.getObject().includeModule(mixologyModule); 24 | return true; 25 | } 26 | 27 | 28 | @JRubyMethod(name = "unmix", required = 1) 29 | public synchronized static IRubyObject unmix(IRubyObject recv, IRubyObject _module, Block block) 30 | throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { 31 | RubyModule module = (RubyModule)_module; 32 | for (RubyModule klass = recv.getMetaClass(); klass != recv.getMetaClass().getRealClass(); klass = klass.getSuperClass()) { 33 | if (klass.getSuperClass() != null && klass.getSuperClass().getNonIncludedClass() == module) { 34 | if(module.getSuperClass() != null && module.getSuperClass() instanceof IncludedModuleWrapper) 35 | remove_nested_module(klass.getSuperClass(), module); 36 | setSuperClass(klass, klass.getSuperClass().getSuperClass()); 37 | clearCache(klass, module); 38 | } 39 | } 40 | 41 | return recv; 42 | 43 | } 44 | 45 | @JRubyMethod(name = "mixin", required = 1) 46 | public synchronized static IRubyObject mixin(IRubyObject recv, IRubyObject _module, Block block) 47 | throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { 48 | RubyModule module = (RubyModule)_module; 49 | unmix(recv, module, block); 50 | 51 | RubyClass klass = recv.getSingletonClass(); 52 | 53 | int nestedModuleCount = 0; 54 | for (RubyModule p = module.getSuperClass(); p != null; p = p.getSuperClass()) 55 | nestedModuleCount++; 56 | 57 | IncludedModuleWrapper[] nestedModules = new IncludedModuleWrapper[nestedModuleCount]; 58 | 59 | IncludedModuleWrapper nestedModule = (IncludedModuleWrapper)module.getSuperClass(); 60 | for(int index = 0; index < nestedModules.length; index++){ 61 | nestedModules[index] = nestedModule; 62 | nestedModule = (IncludedModuleWrapper)nestedModule.getSuperClass(); 63 | } 64 | 65 | for(int index = nestedModules.length; index > 0; index--) { 66 | add_module(klass, nestedModules[index-1].getNonIncludedClass()); 67 | } 68 | 69 | add_module(klass, module); 70 | clearCache(klass, klass.getSuperClass()); 71 | 72 | return recv; 73 | } 74 | 75 | protected synchronized static void add_module(RubyClass klass, RubyModule module) 76 | throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { 77 | 78 | klass.infectBy(module); 79 | 80 | IncludedModuleWrapper includedKlass = new IncludedModuleWrapper(klass.getRuntime(), klass.getSuperClass(), module); 81 | 82 | setSuperClass(klass, includedKlass); 83 | 84 | clearCache(klass, klass.getSuperClass()); 85 | } 86 | 87 | protected synchronized static void remove_nested_module(RubyClass klass, RubyModule include_class) 88 | throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { 89 | 90 | if(! (klass.getSuperClass() instanceof IncludedModuleWrapper) || 91 | ((IncludedModuleWrapper)klass.getSuperClass()).getNonIncludedClass() != ((IncludedModuleWrapper)include_class.getSuperClass()).getNonIncludedClass()) 92 | return; 93 | 94 | 95 | if(include_class.getSuperClass().getSuperClass() != null && include_class.getSuperClass() instanceof IncludedModuleWrapper) { 96 | remove_nested_module(klass.getSuperClass(), include_class.getSuperClass()); 97 | } 98 | 99 | setSuperClass(klass, klass.getSuperClass().getSuperClass()); 100 | 101 | } 102 | 103 | protected synchronized static void setSuperClass(RubyModule klass, RubyModule superClass) 104 | throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { 105 | Method method = RubyModule.class.getDeclaredMethod("setSuperClass", new Class[] {RubyClass.class} ); 106 | method.setAccessible(true); 107 | Object[] superClassArg = new Object[] { superClass }; 108 | method.invoke(klass, superClassArg); 109 | } 110 | 111 | protected static void clearCache(RubyModule klass, RubyModule module) { 112 | List methodNames = new ArrayList(module.getMethods().keySet()); 113 | for (Iterator iter = methodNames.iterator(); 114 | iter.hasNext();) { 115 | String methodName = (String) iter.next(); 116 | klass.getRuntime().getCacheMap().remove(klass.searchMethod(methodName)); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /test/mixology_test.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/test_helper" 2 | 3 | class MixologyTest < Test::Unit::TestCase 4 | 5 | def test_mixin 6 | mixin = Module.new { def foo; "foo"; end } 7 | object = Object.new 8 | object.mixin mixin 9 | assert_equal "foo", object.foo 10 | end 11 | 12 | def test_unmix 13 | mixin = Module.new { def foo; "mixin"; end } 14 | object = Class.new { def foo; "object"; end }.new 15 | object.mixin mixin 16 | assert_equal "mixin", object.foo 17 | object.unmix mixin 18 | assert_equal "object", object.foo 19 | end 20 | 21 | def test_mixin_twice 22 | first_mixin = Module.new { def foo; "first"; end } 23 | second_mixin = Module.new { def foo; "second"; end } 24 | object = Object.new 25 | object.mixin first_mixin 26 | object.mixin second_mixin 27 | assert_equal "second", object.foo 28 | end 29 | 30 | def test_mixin_to_class 31 | mix = Module.new { def foo; "foo"; end } 32 | klass = Class.new { mixin mix } 33 | assert_equal "foo", klass.foo 34 | end 35 | 36 | def test_can_mixin_again 37 | first_mixin = Module.new { def foo; "first"; end } 38 | second_mixin = Module.new { def foo; "second"; end } 39 | object = Object.new 40 | object.mixin first_mixin 41 | object.mixin second_mixin 42 | object.mixin first_mixin 43 | assert_equal "first", object.foo 44 | end 45 | 46 | def test_unmix_effects_limited_to_instance 47 | mixin = Module.new { def foo; "mixin"; end } 48 | object = Class.new {include mixin}.new 49 | assert_equal "mixin", object.foo 50 | object.unmix mixin 51 | assert_equal "mixin", object.foo 52 | end 53 | 54 | def test_can_add_mod_to_an_instance_even_when_already_included_by_class 55 | mixin = Module.new { def foo; "mixin"; end } 56 | klass = Class.new {include mixin; def foo; "class"; end } 57 | object = klass.new 58 | assert_equal "class", object.foo 59 | object.mixin mixin 60 | assert_equal "mixin", object.foo 61 | end 62 | 63 | def test_included_modules_after_mixin 64 | mixin = Module.new 65 | object = Object.new 66 | object.mixin mixin 67 | assert_equal [mixin, Mixology, PP::ObjectMixin, Kernel], (class << object; self; end).included_modules 68 | end 69 | 70 | def test_included_modules_after_unmix 71 | mixin = Module.new 72 | object = Object.new 73 | object.mixin mixin 74 | object.unmix mixin 75 | assert_equal [Mixology, PP::ObjectMixin, Kernel], (class << object; self; end).included_modules 76 | end 77 | 78 | def test_included_modules_after_remix 79 | mixin_one = Module.new 80 | mixin_two = Module.new 81 | object = Object.new 82 | object.mixin mixin_one 83 | object.mixin mixin_two 84 | assert_equal [mixin_two, mixin_one, Mixology, PP::ObjectMixin, Kernel], (class << object; self; end).included_modules 85 | object.mixin mixin_one 86 | assert_equal [mixin_one, mixin_two, Mixology, PP::ObjectMixin, Kernel], (class << object; self; end).included_modules 87 | end 88 | 89 | def test_mixin_returns_object 90 | object = Object.new 91 | mixin = Module.new 92 | assert_equal object, object.mixin(mixin) 93 | end 94 | 95 | def test_unmix_returns_object 96 | object = Object.new 97 | mixin = Module.new 98 | object.mixin mixin 99 | assert_equal object, object.unmix(mixin) 100 | end 101 | 102 | def test_nested_modules_are_mixedin 103 | if rubinius? 104 | print "PENDING"; return 105 | end 106 | nested_module = Module.new { def foo; "foo"; end } 107 | mixin = Module.new { include nested_module } 108 | object = Object.new 109 | object.mixin mixin 110 | assert_equal [mixin, nested_module, Mixology, PP::ObjectMixin, Kernel], (class << object; self; end).included_modules 111 | end 112 | 113 | def test_nested_modules_are_mixedin_deeply 114 | if rubinius? 115 | print "PENDING"; return 116 | end 117 | nested_module_ultimate = Module.new 118 | nested_module_penultimate = Module.new { include nested_module_ultimate } 119 | nested_module = Module.new { include nested_module_penultimate } 120 | mixin = Module.new { include nested_module } 121 | object = Object.new 122 | object.mixin mixin 123 | assert_equal [mixin, nested_module, nested_module_penultimate, nested_module_ultimate, Mixology, PP::ObjectMixin, Kernel], (class << object; self; end).included_modules 124 | end 125 | 126 | def test_nested_modules_are_mixedin_even_if_already_mixed_in 127 | if rubinius? 128 | print "PENDING"; return 129 | end 130 | nested_module = Module.new { def foo; "foo"; end } 131 | mixin = Module.new { include nested_module } 132 | object = Object.new 133 | object.mixin nested_module 134 | object.mixin mixin 135 | assert_equal [mixin, nested_module, nested_module, Mixology, PP::ObjectMixin, Kernel], (class << object; self; end).included_modules 136 | end 137 | 138 | def test_module_is_not_unmixed_if_it_is_outside_nested_chain 139 | nested_module = Module.new 140 | mixin = Module.new { include nested_module } 141 | object = Object.new 142 | object.mixin nested_module 143 | object.mixin mixin 144 | object.unmix mixin 145 | assert_equal [nested_module, Mixology, PP::ObjectMixin, Kernel], (class << object; self; end).included_modules 146 | end 147 | 148 | def test_nested_modules_are_unmixed 149 | nested_module = Module.new 150 | mixin = Module.new { include nested_module } 151 | object = Object.new 152 | object.mixin mixin 153 | object.unmix mixin 154 | assert_equal [Mixology, PP::ObjectMixin, Kernel], (class << object; self; end).included_modules 155 | end 156 | 157 | def test_nested_modules_are_unmixed_deeply 158 | nested_module_ultimate = Module.new 159 | nested_module_penultimate = Module.new { include nested_module_ultimate } 160 | nested_module = Module.new { include nested_module_penultimate } 161 | mixin = Module.new { include nested_module } 162 | object = Object.new 163 | object.mixin mixin 164 | object.unmix mixin 165 | assert_equal [Mixology, PP::ObjectMixin, Kernel], (class << object; self; end).included_modules 166 | end 167 | 168 | def test_unrelated_modules_are_not_unmixed 169 | unrelated = Module.new 170 | nested_module = Module.new 171 | mixin = Module.new { include nested_module } 172 | object = Object.new 173 | object.mixin unrelated 174 | object.mixin mixin 175 | object.unmix mixin 176 | assert_equal [unrelated, Mixology, PP::ObjectMixin, Kernel], (class << object; self; end).included_modules 177 | end 178 | 179 | def rubinius? 180 | defined?(RUBY_ENGINE) && RUBY_ENGINE == "rbx" 181 | end 182 | 183 | end 184 | --------------------------------------------------------------------------------