I'm using the OMDB API to learn about using 3rd Party apis in Rails. I've setup my app so all I have to input is the movie title and 6 other attributes get populated from the OMDB API. All of the method calls to retrieve the data from the api are very similar. The only thing that changes is one word in the method name and one word in the method body. Here is one such call:
app/services/omdb_service.rb
def get_image_by_title(title)
response = HTTP.get("http://www.omdbapi.com/?t=#{title}&apikey=123456789").to_s
parsed_response = JSON.parse(response)
parsed_response['Poster']
end
The things that change are the word after get
in the method name and the word in the parsed_response['Poster']
. They will change depending on what attribute I'm trying to get back.
I thought I could use method_missing
to prevent duplication, but I'm having no success with it. Here is my method_missing
call:
app/services/omdb_service.rb
def method_missing(method, *args)
if method.to_s.end_with?('_by_title')
define_method(method) do | args |
response = HTTP.get("http://www.omdbapi.com/?t=#{args[0]}&apikey=123456789").to_s
parsed_response = JSON.parse(response)
parsed_response['args[1]']
end
end
end
Can anyone see what is wrong with my method_missing
call?
First of all, let me stress that this isn't necessarily a good use case for method_missing
because there doesn't seem to be a way to get self-explanatory method names, parameters and such. Nevertheless, I'll try to answer your question as best as I can.
First of all, you need to adopt your method naming to the things that the API gives you to reduce the number of parameters. In the example you've given, you'd want to change the method call to get_poster_by_t
because poster
is the output and t
is the input variable based on the URL and response you've shared.
Following this logic, you'd have to write method missing like so:
def method_missing(method, *args)
if method =~ /\Aget_([^_]+)_by_([^_]+)\z/
response = HTTP.get("http://www.omdbapi.com/?#{$~[2]}=#{args[0]}&apikey=123456789").to_s
parsed_response = JSON.parse(response)
parsed_response[$~[1].capitalize]
end
end
Then you should also incorporate Ruby's rules for implementing method_missing
, namely calling super
when your rule doesn't match and also overriding respond_to_missing?
. This then gives you:
def method_missing(method, *args)
if method.to_s =~ /\Aget_([^_]+)_by_([^_]+)\z/
response = HTTP.get("http://www.omdbapi.com/?#{$~[2]}=#{args[0]}&apikey=123456789").to_s
parsed_response = JSON.parse(response)
parsed_response[$~[1].capitalize]
else
super
end
end
def respond_to_missing?(method, *args)
method.to_s =~ /\Aget_([^_]+)_by_([^_]+)\z/ || super
end
Personally, I'd not use method_missing
here but instead go with an expressive method call – something like this:
def get_field_by_param(field:, param:, value:)
response = HTTP.get("http://www.omdbapi.com/?#{param}=#{value}&apikey=123456789").to_s
parsed_response = JSON.parse(response)
parsed_response[field]
end
You can then do things like get_field_by_param(field: "Poster", param: :t, value: "Whatever")
.