Skip to content

Instantly share code, notes, and snippets.

@hanachin
Last active December 6, 2018 15:16
Show Gist options
  • Save hanachin/fb9bb1a5c75b76b3b337b25f502d9882 to your computer and use it in GitHub Desktop.
Save hanachin/fb9bb1a5c75b76b3b337b25f502d9882 to your computer and use it in GitHub Desktop.
refineをしてるモジュール取り出せるrefinements_robbery gem ref: https://qiita.com/hanachin_/items/20c1b77936962ee07a7d
require "refinements_robbery"
class C
using Module.new {
refine(C) {
def hi
puts "hi"
end
}
}
end
# C.new.hi # NoMethodError
using *RefinementsRobbery.rob(C)
C.new.hi
# hi
class User
# Refinements で隠蔽したいメソッドを定義する
# また Module.new で動的にモジュールを定義することで
# あとから using することも防ぐ事が出来る
using Module.new {
refine User do
def name
@__name__
end
end
}
def initialize name
@__name__ = name
end
def to_s
"name is #{name}"
end
end
class UserEx < User
def meth
# using してないコンテキストなので参照できない!!
name
end
end
homu = UserEx.new "homu"
# Error: undefined local variable or method `name' for #<UserEx:0x000055998748ee48 @__name__="homu"> (NameError)
p homu.meth
ObjectSpace.each_object(Module)
p Module.new { p refine(Object) {} }
# #<refinement:Object@#<Module:0x000055981dd1f7a8>>
# #<Module:0x000055981dd1f7a8>
% cd path/to/ruby
% git grep refinement:
doc/ChangeLog-2.0.0: a refinement, returns a string in the format #<refinement:C@M>,
doc/syntax/refinements.rdoc:Here is a basic refinement:
object.c: VALUE s = rb_usascii_str_new2("#<refinement:");
test/ruby/test_refinement.rb: assert_equal("#<refinement:Integer@TestRefinement::Inspect::M>",
refined_class = rb_refinement_module_get_refined_class(klass);
if (!NIL_P(refined_class)) {
VALUE s = rb_usascii_str_new2("#<refinement:");
rb_str_concat(s, rb_inspect(refined_class));
rb_str_cat2(s, "@");
CONST_ID(id_defined_at, "__defined_at__");
defined_at = rb_attr_get(klass, id_defined_at);
rb_str_concat(s, rb_inspect(defined_at));
rb_str_cat2(s, ">");
return s;
}
return rb_str_dup(rb_class_name(klass));
def rb_intern_str(str)
rb_intern_str = Fiddle::Handle::DEFAULT["rb_intern_str"]
rb_intern_str_f = Fiddle::Function.new(rb_intern_str, [Fiddle::TYPE_VOIDP], Fiddle::TYPE_VOIDP)
rb_intern_str_f.call(Fiddle.dlwrap(str))
end
def rb_attr_get(mod, str)
id = rb_intern_str(str)
rb_attr_get = Fiddle::Handle::DEFAULT["rb_attr_get"]
rb_attr_get_f = Fiddle::Function.new(rb_attr_get, [Fiddle::TYPE_VOIDP] * 2, Fiddle::TYPE_VOIDP)
m = Fiddle.dlwrap(mod)
Fiddle.dlunwrap(rb_attr_get_f.call(m, id))
end
def defined_at(mod)
rb_attr_get(mod, "__defined_at__")
end
def rb_refinement_module_get_refined_class(mod)
rb_attr_get(mod, "__refined_class__")
end
def rob(klass)
ObjectSpace.each_object(Module).select {|m|
# 対象のクラスをrefineしているか調べる
rb_refinement_module_get_refined_class(m) == klass
}.map {|m|
# refineをまとめているRefinementsのモジュールをたどる
defined_at(m)
}.uniq
end
class User
# Refinements で隠蔽したいメソッドを定義する
# また Module.new で動的にモジュールを定義することで
# あとから using することも防ぐ事が出来る
using Module.new {
refine User do
def name
@__name__
end
end
}
def initialize name
@__name__ = name
end
def to_s
"name is #{name}"
end
end
class UserEx < User
def meth
# using してないコンテキストなので参照できない!!
name
end
end
homu = UserEx.new "homu"
# p homu.meth # undefined local variable or method `name' for #<UserEx:0x0000563939f98bc0 @__name__="homu"> (NameError)
require "refinements_robbery"
class UserEx < User
# UserのRefinementsをあとから using する
using *RefinementsRobbery.rob(User)
def meth2
# using しているコンテキストなので参照できる!!
name
end
end
homu = UserEx.new "homu"
p homu.meth2
# "homu"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment