0
+ # A class for returing errors from the freebase api.
0
+ # For more infomation see the freebase documentation:
0
+ # http://www.freebase.com/view/help/guid/9202a8c04000641f800000000544e139#mqlreaderrors
0
+ class MqlReadError < ArgumentError
0
+ attr_accessor :code, :freebase_message
0
+ def initialize(code,message)
0
+ self.freebase_message = message
0
+ "#{code}: #{freebase_message}"
0
+ # Encapsulates a Freebase result, enables method-based access to the returned values.
0
+ # result = mqlread(:type => "/music/artist", :name => "The Police", :id => nil)
0
+ # result.id => "/topic/en/the_police"
0
+ def initialize(result)
0
+ @result = result.symbolize_keys!
0
+ # result.type is reserved in ruby. Call result.fb_type to access :type instead.
0
+ # returns the first element of an array if it is one
0
+ # this for handling generic mql queries like [{}] that return only a single value
0
+ # converts a returned value from freebase into the corresponding ruby object
0
+ # This is done first by the core data type and then by the type attribute for an object
0
+ # The casing is done using a method dispatch pattern which
0
+ # should make it easy to mix-in new behaviors and type support
0
+ resultify_method = "resultify_#{v.class.to_s.downcase}".to_sym
0
+ v = send(resultify_method, v) if respond_to? resultify_method
0
+ # resultifies each value in the array
0
+ def resultify_array(v)
0
+ v.map{|vv| resultify(vv)}
0
+ # resultifies an object hash
0
+ vtype = indifferent_access(v,:type)
0
+ resultify_value(vtype,v)
0
+ Logger.debug "What's This: #{v.inspect}"
0
+ elsif vtype.is_a? Array
0
+ "Freebase::Types#{vtype.first.classify}".constantize.new(v) #TODO: Union these types
0
+ "Freebase::Types#{vtype.classify}".constantize.new(v)
0
+ #decides if a type is just an expanded simple value object
0
+ ['/type/text', '/type/datetime'].include?(t)
0
+ # dispatches to a value method for the type
0
+ # or returns the simple value if it doesn't exist
0
+ # for example /type/text would dispatch to resultify_value_type_text
0
+ def resultify_value(vtype,v)
0
+ resultify_method = "resultify_value#{vtype.gsub(/\//,'_')}".to_sym
0
+ if respond_to? resultify_method
0
+ send(resultify_method, v)
0
+ indifferent_access(v,:value)
0
+ #provides method based access to the result properties
0
+ def method_missing(name, *args)
0
+ raise NoMethodError.new(name.to_s) unless args.length == 0
0
+ if @result.has_key?(name)
0
+ resultify @result[name]
0
+ elsif @result.has_key?((singularized_name = name.to_s.singularize.to_sym)) and @result[singularized_name].is_a?(Array)
0
+ resultify @result[singularized_name]
0
+ raise NoMethodError.new(name.to_s)
0
+ def indifferent_access(h,k)
0
+ h[k] || h[k.to_s] if (h.has_key?(k) || h.has_key?(k.to_s))
0
+ # the configuration class controls access to the freebase.yml configuration file.
0
+ # it will load the rails-environment specific configuration
0
+ attr_accessor :filename
0
+ DEFAULTS = {:host => 'sandbox.freebase.com', :debug => true, :trace => false}
0
+ @configuration = {}.reverse_merge!(DEFAULTS)
0
+ configure_rails if defined?(RAILS_ROOT)
0
+ @filename = "#{RAILS_ROOT}/config/freebase.yml"
0
+ unless File.exists? @filename
0
+ puts "WARNING: #{RAILS_ROOT}/config/freebase.yml configuration file is not found. Using sandbox.freebase.com."
0
+ set_all YAML.load_file(@filename)[RAILS_ENV].symbolize_keys!
0
+ def set_all(opts = {})
0
+ opts.each {|k,v| self[k] = v}
0
+ # logging service. Is it a bad idea?
0
+ #TODO: log4r or rails logging?
0
+ [:trace, :debug, :warn, :error].each do |level|
0
+ def self.#{level}(message = nil)
0
+ if Configuration.instance[:#{level}]
0
+ SERVICES = { :mqlread => '/api/service/mqlread',
0
+ :mqlwrite => '/api/service/mqlwrite',
0
+ :login => '/api/account/login',
0
+ :upload => '/api/service/upload'
0
+ # get the service url for the specified service.
0
+ "http://#{Configuration.instance[:host]}#{SERVICES[svc]}"
0
+ SERVICES.each_key do |k|
0
+ define_method("#{k}_service_url") do
0
+ # raise an error if the inner response envelope is encoded as an error
0
+ def handle_read_error(inner)
0
+ unless inner['code'].starts_with?('/api/status/ok')
0
+ Logger.error "<<< Received Error: #{inner.inspect}"
0
+ error = inner['messages'][0]
0
+ raise MqlReadError.new(error['code'], error['message'])
0
+ # perform a mqlread and return the results
0
+ # Specify :raw => true if you don't want the results converted into a FreebaseResult object.
0
+ def mqlread(query, options = {})
0
+ Logger.trace {">>> Sending Query: #{query.inspect}"}
0
+ envelope = { :qname => {:query => query }}
0
+ response = http_request mqlread_service_url, :queries => envelope.to_json
0
+ result = ActiveSupport::JSON.decode(response)
0
+ inner = result['qname']
0
+ handle_read_error(inner)
0
+ Logger.trace {"<<< Received Response: #{inner['result'].inspect}"}
0
+ inner['result'] ? FreebaseResult.new(inner['result']) : nil
0
+ def params_to_string(parameters)
0
+ parameters.keys.map {|k| "#{URI.encode(k.to_s)}=#{URI.encode(parameters[k])}" }.join('&')
0
+ def http_request(url, parameters = {})
0
+ params = params_to_string(parameters)
0
+ url << '?'+params unless params.blank?
0
+ returning(Net::HTTP.get_response(URI.parse(url)).body) do |response|
0
+ fname = "#{MD5.md5(params)}.mql"
0
+ open(fname,"w") do |f|
0
+ "Wrote response to #{fname}"
0
\ No newline at end of file