ere
ere

Reputation: 1779

ActiveResource choking on freebase json request

I've got ActiveResource setup to consume the freebase api in json, and it should work fine except that the json freebase returns causes ActiveResource to blowup.

NoMethodError: undefined method `collect!' for #<Hash:0x007fd674831dd0>

How can I define a custom json parser to fix whatever is wrong?

class Freebase < ActiveResource::Base
  self.site = "https://www.googleapis.com/"
  self.format = :json

  def self.search(word)
    self.find(:all, :from => "/freebase/v1/search/", :params => { :query => word })
  end

   #https://www.googleapis.com/freebase/v1/search?query=nirvana

  #Freebase.get('search', :query => 'nirvana')

end

Json being returned:

https://www.googleapis.com/freebase/v1/search?query=nirvana

{"status":"200 OK","result":[{"mid":"/m/05b3c","name":"Nirvana","notable":{"name":"Belief","id":"/religion/belief"},"lang":"en","score":67.540009},{"mid":"/m/0b1zz","name":"Nirvana","notable":{"name":"Musical Artist","id":"/music/artist"},"lang":"en","score":64.311432},{"mid":"/m/092bf5","name":"Buddhism","notable":{"name":"Religion","id":"/religion/religion"},"lang":"en","score":33.647118},{"mid":"/m/02_6qh","name":"Nirvana","notable":{"name":"Film","id":"/film/film"},"lang":"en","score":30.068491},{"mid":"/m/01h89tx","name":"Nirvana","notable":{"name":"Musical Album","id":"/music/album"},"lang":"en","score":27.799274},{"mid":"/m/01rn9fm","name":"Nirvana","notable":{"name":"Musical Group","id":"/music/musical_group"},"lang":"en","score":27.445602},{"mid":"/m/015k7","name":"Gautama Buddha","notable":{"name":"Deity","id":"/religion/deity"},"lang":"en","score":24.129679},{"mid":"/m/01rkx5","name":"Mahayana Mahaparinirvana Sutra","lang":"en","score":22.359026},{"mid":"/m/03d7q7v","name":"Nirvana","lang":"en","score":21.034473},{"mid":"/m/055ym7w","name":"Nirvana bootleg recordings","notable":{"name":"Musical Album","id":"/music/album"},"lang":"en","score":19.241596},{"mid":"/m/0122_j","name":"Nevermind","notable":{"name":"Musical Album","id":"/music/album"},"lang":"en","score":18.366383},{"mid":"/m/04n7mt","name":"Nirvana fallacy","lang":"en","score":17.212397},{"mid":"/m/0484q","name":"Kurt Cobain","notable":{"name":"Musician","id":"/m/09jwl"},"lang":"en","score":16.594929},{"mid":"/m/027_k8j","name":"Nirvana","lang":"en","score":16.336584},{"mid":"/m/0285c","name":"Dave Grohl","notable":{"name":"Musician","id":"/m/09jwl"},"lang":"en","score":16.115103},{"mid":"/m/068shv","name":"Smells Like Nirvana","notable":{"name":"Musical Album","id":"/music/album"},"lang":"en","score":15.350652},{"mid":"/m/01kq85c","name":"Manic Nirvana","notable":{"name":"Musical Album","id":"/music/album"},"lang":"en","score":15.275189},{"mid":"/m/0437sc","name":"Lithium","notable":{"name":"Composition","id":"/music/song"},"lang":"en","score":14.637386},{"mid":"/m/055kh1","name":"Mechanus","lang":"en","score":14.621847},{"mid":"/m/01f1vf","name":"Lucifer","notable":{"name":"Fictional Character","id":"/fictional_universe/fictional_character"},"lang":"en","score":13.504528}],"cursor":20,"cost":11,"hits":3104}

Using Rails 3.2.1

UPDATE:

Found this issue, but still not sure how to overcome it. https://github.com/rails/rails/issues/2318

Upvotes: 0

Views: 991

Answers (2)

ere
ere

Reputation: 1779

I followed Derek's advice and abandoned ActiveResource...

for anyone else I ended up using Faraday and Faraday_Middleware

class Freebase

  def self.search(id)
    connection = Faraday.new 'https://www.googleapis.com/freebase/v1' do |conn|

      conn.adapter Faraday.default_adapter
      conn.use FaradayMiddleware::ParseJson
      #conn.use Faraday::Response::Mashify
    end

    response = connection.get do |req|
      req.url('search', :query => id, :limit => 10)#, :filter => '(any namespace:/wikipedia/en_id namespace:/authority/imdb/title)')#  #:filter => '(any namespace:/wikipedia/en_id namespace:/authority/imdb/title namespace:/authority/netflix/movie)'  , :with => 'commons'
    end

  end

Supposedly you can use several built in modules to make rails style methods out of the returned hash, but I haven't figured that part out yet. ActiveResource seems only good at other Rails apps or RESTFUL routes that perfectly imitate it.

Upvotes: 0

Derek Harmel
Derek Harmel

Reputation: 736

The problem is that the JSON returned isn't formed the way ActiveResource expects it to be. ARes doesn't expect all of that metadata that is being returned, it is only expecting what is in the results portion of the response.

To be explicit, you're getting back:

{"status":"200 OK","result":[{"mid":"/m/05b3c","name":"Nirvana","notable":{"name" ...

But ARes wants:

[{"mid":"/m/05b3c","name":"Nirvana","notable":{"name"...

The easiest (and probably dirtiest) solution I can come up with is to overwrite the ActiveResource::Base#find_every private method in your model like so:

class Freebase < ActiveResource::Base

  self.site = "https://www.googleapis.com/"
  self.format = :json

  def self.search(word)
    self.find(:all, :from => "/freebase/v1/search/", :params => { :query => word })
  end

  private

  def self.find_every(options)
    begin
      case from = options[:from]
      when Symbol
        instantiate_collection(get(from, options[:params]))
      when String
        path = "#{from}#{query_string(options[:params])}"
        instantiate_collection(format.decode(connection.get(path, headers).body['result']) || [])
      else
        prefix_options, query_options = split_options(options[:params])
        path = collection_path(prefix_options, query_options)
        instantiate_collection( (format.decode(connection.get(path, headers).body['result']) || []), prefix_options )
      end
    rescue ActiveResource::ResourceNotFound
      # Swallowing ResourceNotFound exceptions and return nil - as per
      # ActiveRecord.
      nil
    end
  end

end

The only change I made is the .body method calls are now .body['result']. With this addition, #instantiate_collection will now receive an Array as it expects rather than a Hash.

Ultimately, while this should make the error go away, I don't know that this will solve all of your problems as you might need some of the data that gets lost with this method. My suggestion would be to abandon ActiveResource if you can and use something like RestClient and build your model around that. Another reason for this approach is that the semantics as they are now are broken. You aren't getting back a collection of Freebases. You're using the Freebase API to get search results.

Upvotes: 1

Related Questions