※この記事はここからの転載になります。
Ruby でメタプログラミングをやっているとどうしても『mixin したモジュールを削除したい』という欲求にかられるのでつくりました。
インストール
$ gem install unmixer
使い方
require "unmixer"
# Unmixer は refinements を使用してるので using して使用します
using Unmixer
module M1; end
module M2; end
module M3; end
class X
include M1
prepend M2
end
p X.ancestors
# => [M2, X, M1, Object, Kernel, BasicObject]
# include したモジュールを削除
X.instance_eval { uninclude M1 }
p X.ancestors
# => [M2, X, Object, Kernel, BasicObject]
# include したモジュール以外は削除できない
X.instance_eval { uninclude M2 }
p X.ancestors
# => [M2, X, Object, Kernel, BasicObject]
# prepend したモジュールを削除
X.instance_eval { unprepend M2 }
p X.ancestors
# => [X, Object, Kernel, BasicObject]
X.extend M3
p X.singleton_class.ancestors
# => [#<Class:X>, M3, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
# extend したモジュールを削除
X.unextend M3
p X.singleton_class.ancestors
# => [#<Class:X>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
# #extend にブロックを渡した場合、ブロック内でのみ mixin される
X.extend M1 do
# mixin only in block.
p X.singleton_class.ancestors
# => [#<Class:X>, M1, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
p X.singleton_class.ancestors.include? M1
# => true
end
p X.singleton_class.ancestors
# => [#<Class:X>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
p X.singleton_class.ancestors.include? M1
# => false
元々は uninclude という gem があったのですが、こちらは比較的新しい Ruby では動作しなかったのでそれを焼き直した形になります。
現状は、Ruby 2.1 〜2.4 であれば動作すると思います。
ただ、実装が結構無理なりなので今後のサポートや意図しない動作が発生する可能性はあります。
今回初めて Ruby で C拡張のコードを書いたんですが、やはり C言語つらいですね…。
最初は C 側で全部を実装していたんですが、途中でつらくなったので C 側は最小限の処理のみ実装して結局 Ruby 側でガシガシ実装しました。
多少不安なところはあるんですが、やはり mixin を削除できる機能はかなり便利です。
言語仕様側で導入されないかなあ…。