Ruby define_method

时间:2012-10-31 21:54:48

标签: ruby metaprogramming

我必须通过以下测试:

def test_can_find_by_arbitrary_fields
  assert @library.respond_to? :find_by_artist
  assert !@library.respond_to?(:find_by_bitrate)
  @library.add_song({ :artist => 'Green Day',
                     :name => 'American Idiot',
                     :bitrate => 192 })
  assert @library.respond_to?(:find_by_bitrate)
end

我不确定如何做到这一点。

我尝试过:

def respond_to?(method)
     if self.public_methods.include? method
         true
     elsif (method == :find_by_bitrate)
         define_method :find_by_bitrate, ->(default = nrb)  { @songs.select |a| a[:bitrate] == nrb }       
         false
     else
         false
end

但它说“define_method未定义”。我有什么方法可以定义find_by_bitrate方法吗?

3 个答案:

答案 0 :(得分:3)

您可以在method_missing第一次调用方法时定义方法。

是否可以讨论,但它比respond_to?更好。

class Foo
  def method_missing(sym)
    puts "Method missing; defining."
    self.class.send(:define_method, sym) do
      puts "Called #{sym}."
    end
  end
end

完整性检查:

f = Foo.new
=> #<Foo:0x007fa6aa09d3c0>
f.wat
=> Method wat missing; defining.
f.wat
=> Called wat.
f2 = Foo.new
=> Called wat.

答案 1 :(得分:1)

我认为你不应该重新定义respond_to?方法。测试的重点是(可能)@library对象应该定义find_by_artist方法而不是find_by_bitrate,直到您添加比特率的歌曲。即add_song方法在看到带有比特率(?)的歌曲时应该定义方法find_by_bitrate

此外,define_methodClass的私有方法。上面,你试图从实例方法调用它。请参阅“Ruby: define_method vs. def”,其中有更多内容。

答案 2 :(得分:1)

为了正确回答这个问题,有很多信息缺失。测试意味着即使find_by_artist为空,也始终定义@library,但是其他属性(例如:bitrate)上有可用的动态方法仅在库包含具有此类方法的记录时才有效

在任何情况下都不应重新定义respond_to?。有一个明确的钩子方法来回答respond_to?对于动态方法:Object#respond_to_missing?

因此,确保测试通过的一种简单方法是确保@library对象具有一个具体的方法#find_by_artist和一个响应钩子,以检查它的任何元素是否具有所请求的属性。如果我认为@library是一个集合对象Library,它会在@songs

中保留歌曲的枚举
class Library
  def find_by_artist artist
    @songs.select { |song| song['artist'] == artist }
  end

  def method_missing meth, arg
    m = /^find_by_(.+)$/.match meth.to_s
    return super unless attr = m && m[1]

    @songs.select { |song| song[attr] ==  arg }
  end

  def respond_to_missing? meth, include_private
    m = /^find_by_(.+)$/.match meth.to_s
    return super unless attr = m && m[1]

    @songs.any? { |song| song.has_key? attr }
  end
end

这有一个性能问题,即respond_to?现在招致所有歌曲的搜索。可以通过保留@songs中包含的所有属性的一组联合并在添加/更新/删除集合中的元素的方法中更新它来进行优化。