1

I have the following test which I must pass:

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

and I am not sure how I can do it.

I tried doing:

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

but it says "define_method is undefined". Are there any ways I can define the find_by_bitrate method?

3
  • 1
    Are you sure you want to be redefining respond_to?? I suspect that's very much not what you want to do. At the very least you almost definitely do not want to be defining a method in respond_to?. Commented Oct 31, 2012 at 22:02
  • Not really. But I can't think of any other way of inserting the find_by_bitrate after the first time it's not found. Commented Oct 31, 2012 at 22:03
  • But respond_to? isn't called when trying to call a method on an object, so unless your actual app code calls respond_to? it won't define find_by_bitrate. Are you using Ruby 1.9? Commented Oct 31, 2012 at 22:05

3 Answers 3

3

You may define methods the first time they're called in method_missing.

Whether or not you should is open to some debate, but it's a better option than 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

Sanity check:

f = Foo.new
=> #<Foo:0x007fa6aa09d3c0>
f.wat
=> Method wat missing; defining.
f.wat
=> Called wat.
f2 = Foo.new
=> Called wat.
Sign up to request clarification or add additional context in comments.

Comments

1

I don't think you should be redefining respond_to? method. The point of the test is (probably) that the @library object should have a find_by_artist method defined and no find_by_bitrate until you add a song with a bitrate. I.e. the add_song method should define method find_by_bitrate when it sees a song with a bitrate (?).

Also, define_method is a private method of Class. Above, you're trying to call it from an instance method. See "Ruby: define_method vs. def", there's more on this stuff.

Comments

1

There's a lot of info missing to properly answer this. The test implies that find_by_artist is always defined even when @library is empty, but that there are dynamic methods available on other attributes (eg: bitrate) that are valid only when library contains a record with such a method.

One should not redefine respond_to? in any case. There is an explicit hook method for answering respond_to? for dynamic methods: Object#respond_to_missing?.

So a simple way to make your test pass is to be sure the @library object has a concrete method #find_by_artist and a respond to hook that checks whether any of it's elements a have the requested attribute. If I assume @library is a collection object Library which keeps an enumeration of songs in @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

This has a performance problem in that respond_to? now incurs a search of all the songs. One could optimize by keeping a set of the union of all attributes contained in @songs and updating it in methods which add/update/delete elements in the collection.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.