【问题标题】:calling method that was undefined in ruby调用 ruby​​ 中未定义的方法
【发布时间】:2016-10-29 19:52:35
【问题描述】:

使用已定义为特殊类的外部 API,其中几乎所有标准方法都未定义用于构建 xml。其中#method_missing负责根据对象调用的缺失方法名生成元素。

基本上在类体内有一些效果:

undef_method :send

现在我想通过名称以编程方式调用方法。我想我可以使用eval "obj.#{something}",但我真的不喜欢eval。 我在想必须有一些黑暗的技术来恢复方法#send 的未定义,所以我可以将其别名为#__send__ 并再次取消定义。这样我就可以愉快地按名称调用方法而无需 eval。例如obj.__send__(:something, params)

所以我的问题是如何使用猴子补丁来恢复#send 方法。我找不到任何有这种效果的东西。甚至找不到任何人询问它。

更新:我最初的问题是没有问题的,因为无论如何有一个方法#__send__,我只知道#send。问题的另一部分是如何恢复未定义的方法。在@philomory asnwer 的帮助下,这就是我所拥有的:

[46] pry(#<CucuShift::DefaultWorld>)> class A
[46] pry(#<CucuShift::DefaultWorld>)*   def gah
[46] pry(#<CucuShift::DefaultWorld>)*     puts "gah"
[46] pry(#<CucuShift::DefaultWorld>)*   end  
[46] pry(#<CucuShift::DefaultWorld>)* end  
=> :gah
[49] pry(#<CucuShift::DefaultWorld>)> class C < A
[49] pry(#<CucuShift::DefaultWorld>)*   undef_method :gah
[49] pry(#<CucuShift::DefaultWorld>)* end  
=> C
[50] pry(#<CucuShift::DefaultWorld>)> C.new.gah
NoMethodError: undefined method `gah' for #<C:0x000000070d7918>
[57] pry(#<CucuShift::DefaultWorld>)> class C
[57] pry(#<CucuShift::DefaultWorld>)*   define_method :fff , A.instance_method(:gah)
[57] pry(#<CucuShift::DefaultWorld>)* end  
=> :fff
[58] pry(#<CucuShift::DefaultWorld>)> C.new.fff
gah

【问题讨论】:

  • 我很困惑 - 你为什么不能打电话 send
  • 愚蠢的格式 - 我的意思是__send__
  • @FrederickCheung,因为我从来不知道有 #__send__ 方法。我只知道#send。我想这解释了负面问题评级.. ruby​​ 文档真的很糟糕,无论如何,谢谢
  • Ruby 有很棒的文档,您只需要实际查看即可。
  • @13aal,我猜你从未看过其他语言的文档,例如 java。与我使用的其他语言相比,Ruby 是我在文档中定义最不明确的语言。幸运的是,它的广泛流行使得博客和论坛中的大多数主题都得到了很好的覆盖。顺便说一句,看看我在网站上提出的问题数量,看起来我实际上很聪明,只有几个问题......只要你的断言是正确的,而且我在提问之前没有做研究。

标签: ruby metaprogramming monkeypatching


【解决方案1】:

您正在寻找的黑魔法是UnboundMethod 类。像这样使用它(假设 obj 是您的构建器 API 对象):

send_method = BasicObject.instance_method(:__send__)
bound_method = send_method.bind(obj)
bound_method.call(:method_you_are_calling_dynamically,*arguments)

当然,根据你想要完成的工作,你可以直接获取你想要的特定方法并直接绑定它,比如

id_method = BasicObject.intance_method(:__id__)
bound_method = id_method.bind(obj)
bound_method.call

或者,如果构建器类正在使用method_missing,而您只想动态调度到它,则根本不需要send 或类似的东西,只需调用obj.method_missing(:my_dynamic_method_name)


更新:

值得注意的是,您不需要将 send 别名为 __send__,因为 __send__ 已经作为 BasicObject 的一部分提供,并且取消定义它是非常不寻常的。虽然我想如果你使用的是没有 BasicObject 和 __send__ 的早期版本的 Ruby,你可以这样做:

def obj.__send__(*args,&blk)
  Object.instance_method(:send).bind(self).call(*args,&blk)
end

【讨论】:

  • 嗯,从来不知道有一个开箱即用的#__send__ 方法。始终使用相同的 #send 方法。我的错!
  • 仅供参考,我没有看到 BasicObject.instance_method(:__send__) 在 ruby​​ 2.2 上工作,如果可以的话,修复它会很有用。更新以回答工作,但标记为解决方案。
  • 我刚刚在 2.2.2 上尝试过,对我来说效果很好。你看到什么错误?请注意,我写的第一个方法并没有给你一个名为send__send__ 的实际方法。他们是你所做的而不是直接写obj.sendobj.__send__
  • 我想我之前可能有错字。它确实有效。不过,我仍然看不到如何将方法还原为类本身作为实例方法。正如我上面所说的,我已经解决了我的直接问题,但是知道如何实际恢复该方法很有趣。
  • 很高兴为您提供帮助。请注意,如果您正在使用的类具有未定义的define_method,则使用define_method 添加到问题中的方法将不起作用(不过,通常这不会成为问题,因为人们通常只有undef实例方法,而不是类方法)。也就是说,我展示的方法(使用def 而不是define_method)将总是 工作(因为def 是语言关键字并且不能被否决),但它确实会带来小的性能损失(不是因为define_method 更快,而是因为我的版本在每次调用时都会调用instance_methodbindcall)。
猜你喜欢
  • 2019-12-25
  • 2010-10-15
  • 1970-01-01
  • 1970-01-01
  • 2015-10-11
  • 2016-06-30
  • 1970-01-01
  • 2012-03-13
  • 1970-01-01
相关资源
最近更新 更多