Reputation: 956
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?
Upvotes: 1
Views: 827
Reputation: 160170
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.
Upvotes: 3
Reputation: 20408
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.
Upvotes: 1
Reputation: 3387
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.
Upvotes: 1