freebase 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +25 -0
- data/lib/core_extensions.rb +17 -0
- data/lib/freebase.rb +160 -0
- data/lib/freebase/api.rb +222 -0
- data/test/freebase_test.rb +36 -0
- metadata +74 -0
data/README
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
= Freebase
|
2
|
+
This Ruby-on-Rails plugin provides access to the Freebase API (http://www.freebase.com). Freebase is a collaborative, semantic database similar to Wikipedia only for structured data. Freebase.com provides a JSON-over-HTTP API that this library uses.
|
3
|
+
|
4
|
+
Currently only reads are implemented. Contributions, API Suggestions, bug reports are welcome!
|
5
|
+
|
6
|
+
This code is ALPHA. The API will change, features will be added. It probably has a bug or two in it. Use it at your own peril.
|
7
|
+
|
8
|
+
Author:: Christopher Eppstein (mailto:chris@eppsteins.net)
|
9
|
+
Copyright:: Copyright (c) 2007 Christopher Eppstein
|
10
|
+
License:: Released under the MIT license
|
11
|
+
|
12
|
+
== Installation
|
13
|
+
Install the plugin:
|
14
|
+
script/plugin install svn://rubyforge.org/var/svn/freebaseapi/trunk/freebase
|
15
|
+
|
16
|
+
Then copy freebase.yml to your rails config directory
|
17
|
+
|
18
|
+
== Usage Examples
|
19
|
+
See the following examples:
|
20
|
+
* albums.rb[link:../examples/albums.rb]
|
21
|
+
|
22
|
+
== Contributors
|
23
|
+
* Pat Allan (mailto:pat@freelancing-gods.com) provided code snippets that
|
24
|
+
exemplified automatic freebase class creation when the class is first referenced.
|
25
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Need to modify the inbuilt Module class, to catch const_missing calls.
|
2
|
+
class Module
|
3
|
+
|
4
|
+
# New version of const_missing which catches requests made to the
|
5
|
+
# Metaweb module/namespace - and generates modules to represent
|
6
|
+
# data domains if necessary.
|
7
|
+
def const_missing_with_freebase_support(class_id)
|
8
|
+
if self.name[/^Freebase::Types/]
|
9
|
+
new_freebase_type(class_id)
|
10
|
+
else
|
11
|
+
const_missing_without_freebase_support(class_id)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
alias_method_chain :const_missing, :freebase_support
|
16
|
+
|
17
|
+
end
|
data/lib/freebase.rb
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
# (c) Copyright 2007 Chris Eppstein. All Rights Reserved.
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'activesupport'
|
5
|
+
require 'net/http'
|
6
|
+
require 'core_extensions'
|
7
|
+
|
8
|
+
module Freebase
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'freebase/api'
|
12
|
+
|
13
|
+
module Freebase
|
14
|
+
# This module is a namespace scope for the Freebase domains
|
15
|
+
module Types
|
16
|
+
# Automagically creates modules for domains and classes for types
|
17
|
+
# for matching namespaces to Freebase's domain/type naming structure.
|
18
|
+
# Shouldn't need to be called manually because this is called by the module
|
19
|
+
# whenever the constant is missing.
|
20
|
+
def new_freebase_type(name)
|
21
|
+
Freebase::Api::Logger.trace {"new freebase module = #{name}"}
|
22
|
+
self.const_set(name, Module.new do
|
23
|
+
def new_freebase_type(name)
|
24
|
+
Freebase::Api::Logger.trace {"new freebase class = #{name}"}
|
25
|
+
klass = self.const_set(name, Class.new(Freebase::Base))
|
26
|
+
klass.class_eval do
|
27
|
+
cattr_accessor :properties, :schema_loaded
|
28
|
+
end
|
29
|
+
returning(klass) do |tc|
|
30
|
+
tc.load_schema! unless tc.schema_loaded?
|
31
|
+
Freebase::Api::Logger.trace { "Attempting Mixin include: #{tc.name.sub(/Types/,"Mixins")}" }
|
32
|
+
begin
|
33
|
+
tc.send(:include, tc.name.sub(/Types/,"Mixins").constantize)
|
34
|
+
rescue NameError => e
|
35
|
+
Freebase::Api::Logger.trace "failed: #{e}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module_function :new_freebase_type
|
41
|
+
end)
|
42
|
+
end
|
43
|
+
|
44
|
+
module_function :new_freebase_type
|
45
|
+
end
|
46
|
+
|
47
|
+
# Add a module within this module that corresponds to a freebase class within Freebase::Types
|
48
|
+
# and the methods will be mixed in automatically. E.g.:
|
49
|
+
# module Freebase::Mixins::Music
|
50
|
+
# module Track
|
51
|
+
# def formatted_length
|
52
|
+
# "#{self.length.to_i / 60}:#{sprintf("%02i", self.length.to_i % 60)}"
|
53
|
+
# end
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# Will be mixed in to the Freebase::Types:Music:Track class
|
58
|
+
module Mixins
|
59
|
+
end
|
60
|
+
|
61
|
+
# This is the base class for all dynamically defined Freebase Types.
|
62
|
+
class Base < Api::FreebaseResult
|
63
|
+
extend Api
|
64
|
+
alias_method :attributes, :result
|
65
|
+
def self.schema_loaded?
|
66
|
+
self.schema_loaded || false
|
67
|
+
end
|
68
|
+
def self.freebase_type
|
69
|
+
@freebase_type ||= self.name["Freebase::Types".length..self.name.length].underscore
|
70
|
+
end
|
71
|
+
def self.load_schema!
|
72
|
+
self.properties = {}
|
73
|
+
propobjs = mqlread(:type => '/type/type', :id => self.freebase_type, :properties => [{:name => nil, :id => nil, :type => nil, :expected_type => nil}]).properties
|
74
|
+
propobjs.each {|propobj|
|
75
|
+
self.properties[propobj.id.split(/\//).last.to_sym] = propobj
|
76
|
+
}
|
77
|
+
self.schema_loaded = true
|
78
|
+
end
|
79
|
+
def self.find(*args)
|
80
|
+
options = args.extract_options!
|
81
|
+
case args.first
|
82
|
+
when :first
|
83
|
+
raise ArgumentError.new("Too many arguments for find(:first)") if args.size > 1
|
84
|
+
find_first(options)
|
85
|
+
when :all
|
86
|
+
raise ArgumentError.new("Too many arguments for find(:all)") if args.size > 1
|
87
|
+
find_all(options)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
def self.add_required_query_attributes(conditions)
|
91
|
+
case conditions
|
92
|
+
when Array
|
93
|
+
conditions.map! {|c| add_required_query_attributes(c)}
|
94
|
+
when Hash
|
95
|
+
if conditions.delete(:fb_object)
|
96
|
+
conditions.reverse_merge!(:type => [], :id => nil) unless conditions.has_key?(:*)
|
97
|
+
else
|
98
|
+
conditions.reverse_merge!(:type => nil) unless conditions.has_key?(:*)
|
99
|
+
end
|
100
|
+
conditions.each {|k,v| add_required_query_attributes(v) unless k == :*}
|
101
|
+
else
|
102
|
+
conditions
|
103
|
+
end
|
104
|
+
end
|
105
|
+
# Don't to call this directly. find(:first, options) will be dispatched here.
|
106
|
+
# This method is provided for extensibility
|
107
|
+
def self.find_first(options = {})
|
108
|
+
conditions = options.fetch(:conditions, {}).reverse_merge(:type => self.freebase_type, :name=>nil, :* => [{}], :limit => 1)
|
109
|
+
add_required_query_attributes(conditions)
|
110
|
+
self.new(mqlread(conditions, :raw => true))
|
111
|
+
end
|
112
|
+
|
113
|
+
# Don't to call this directly. find(:all, options) will be dispatched here.
|
114
|
+
# This method is provided for extensibility
|
115
|
+
def self.find_all(options = {})
|
116
|
+
query = options.fetch(:conditions, {}).merge(:type => self.freebase_type, :name=>nil, :* => [{}])
|
117
|
+
query[:limit] = options[:limit] if options[:limit]
|
118
|
+
add_required_query_attributes(query)
|
119
|
+
mqlread([query], :raw => true).map{|i| self.new(i)}
|
120
|
+
end
|
121
|
+
|
122
|
+
# ActiveRecord:Base-like to_s for the class
|
123
|
+
def self.to_s
|
124
|
+
if respond_to?(:properties) && !self.properties.blank?
|
125
|
+
%Q{#<#{name} #{self.properties.map{|k,v| "#{k}:#{v.expected_type}"}.join(", ")}>}
|
126
|
+
else
|
127
|
+
"#<#{name}>"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# (re)load all properties of this object
|
132
|
+
def reload
|
133
|
+
query = {:id => self.id, :type=>self.class.freebase_type, :name=> nil, :limit => 1}
|
134
|
+
self.class.properties.each do |k,v|
|
135
|
+
query[k] = [{}] unless query.has_key?(k)
|
136
|
+
end
|
137
|
+
@result = self.class.mqlread(query, :raw => true).symbolize_keys!
|
138
|
+
Freebase::Api::Logger.trace { @result.inspect }
|
139
|
+
return self
|
140
|
+
end
|
141
|
+
|
142
|
+
# access the properties of this object, lazy loading associations as required.
|
143
|
+
def method_missing(name,*args)
|
144
|
+
if self.class.properties.has_key?(name)
|
145
|
+
reload unless attributes.has_key?(name)
|
146
|
+
resultify attributes[name]
|
147
|
+
elsif self.class.properties.has_key?((singularized_name = name.to_s.singularize.to_sym))
|
148
|
+
reload unless attributes.has_key?(singularized_name)
|
149
|
+
resultify attributes[singularized_name]
|
150
|
+
else
|
151
|
+
super
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# If the object has a name, return it, otherwise the id.
|
156
|
+
def to_s
|
157
|
+
respond_to?(:name) ? name : id
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
data/lib/freebase/api.rb
ADDED
@@ -0,0 +1,222 @@
|
|
1
|
+
module Freebase::Api
|
2
|
+
# A class for returing errors from the freebase api.
|
3
|
+
# For more infomation see the freebase documentation:
|
4
|
+
# http://www.freebase.com/view/help/guid/9202a8c04000641f800000000544e139#mqlreaderrors
|
5
|
+
class MqlReadError < ArgumentError
|
6
|
+
attr_accessor :code, :freebase_message
|
7
|
+
def initialize(code,message)
|
8
|
+
self.code = code
|
9
|
+
self.freebase_message = message
|
10
|
+
end
|
11
|
+
def message
|
12
|
+
"#{code}: #{freebase_message}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Encapsulates a Freebase result, enables method-based access to the returned values.
|
17
|
+
# E.g.
|
18
|
+
# result = mqlread(:type => "/music/artist", :name => "The Police", :id => nil)
|
19
|
+
# result.id => "/topic/en/the_police"
|
20
|
+
class FreebaseResult
|
21
|
+
|
22
|
+
attr_accessor :result
|
23
|
+
|
24
|
+
def initialize(result)
|
25
|
+
@result = result.symbolize_keys!
|
26
|
+
end
|
27
|
+
|
28
|
+
def id
|
29
|
+
@result[:id]
|
30
|
+
end
|
31
|
+
|
32
|
+
# result.type is reserved in ruby. Call result.fb_type to access :type instead.
|
33
|
+
def fb_type
|
34
|
+
@result[:type]
|
35
|
+
end
|
36
|
+
|
37
|
+
# returns the first element of an array if it is one
|
38
|
+
# this for handling generic mql queries like [{}] that return only a single value
|
39
|
+
def depluralize(v)
|
40
|
+
Array(v).first
|
41
|
+
end
|
42
|
+
|
43
|
+
# converts a returned value from freebase into the corresponding ruby object
|
44
|
+
# This is done first by the core data type and then by the type attribute for an object
|
45
|
+
# The casing is done using a method dispatch pattern which
|
46
|
+
# should make it easy to mix-in new behaviors and type support
|
47
|
+
def resultify(v)
|
48
|
+
resultify_method = "resultify_#{v.class.to_s.downcase}".to_sym
|
49
|
+
v = send(resultify_method, v) if respond_to? resultify_method
|
50
|
+
return v
|
51
|
+
end
|
52
|
+
|
53
|
+
# resultifies each value in the array
|
54
|
+
def resultify_array(v)
|
55
|
+
v.map{|vv| resultify(vv)}
|
56
|
+
end
|
57
|
+
|
58
|
+
# resultifies an object hash
|
59
|
+
def resultify_hash(v)
|
60
|
+
vtype = indifferent_access(v,:type)
|
61
|
+
if value_type? vtype
|
62
|
+
resultify_value(vtype,v)
|
63
|
+
elsif vtype.blank?
|
64
|
+
Logger.debug "What's This: #{v.inspect}"
|
65
|
+
FreebaseResult.new(v)
|
66
|
+
elsif vtype.is_a? Array
|
67
|
+
"Freebase::Types#{vtype.first.classify}".constantize.new(v) #TODO: Union these types
|
68
|
+
else
|
69
|
+
"Freebase::Types#{vtype.classify}".constantize.new(v)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
#decides if a type is just an expanded simple value object
|
74
|
+
def value_type?(t)
|
75
|
+
['/type/text', '/type/datetime'].include?(t)
|
76
|
+
end
|
77
|
+
|
78
|
+
# dispatches to a value method for the type
|
79
|
+
# or returns the simple value if it doesn't exist
|
80
|
+
# for example /type/text would dispatch to resultify_value_type_text
|
81
|
+
def resultify_value(vtype,v)
|
82
|
+
resultify_method = "resultify_value#{vtype.gsub(/\//,'_')}".to_sym
|
83
|
+
if respond_to? resultify_method
|
84
|
+
send(resultify_method, v)
|
85
|
+
else
|
86
|
+
indifferent_access(v,:value)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
#provides method based access to the result properties
|
91
|
+
def method_missing(name, *args)
|
92
|
+
raise NoMethodError.new(name.to_s) unless args.length == 0
|
93
|
+
if @result.has_key?(name)
|
94
|
+
resultify @result[name]
|
95
|
+
elsif @result.has_key?((singularized_name = name.to_s.singularize.to_sym)) and @result[singularized_name].is_a?(Array)
|
96
|
+
resultify @result[singularized_name]
|
97
|
+
else
|
98
|
+
raise NoMethodError.new(name.to_s)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
protected
|
103
|
+
def indifferent_access(h,k)
|
104
|
+
h[k] || h[k.to_s] if (h.has_key?(k) || h.has_key?(k.to_s))
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
# the configuration class controls access to the freebase.yml configuration file.
|
110
|
+
# it will load the rails-environment specific configuration
|
111
|
+
class Configuration
|
112
|
+
|
113
|
+
include Singleton
|
114
|
+
|
115
|
+
attr_accessor :filename
|
116
|
+
|
117
|
+
DEFAULTS = {:host => 'sandbox.freebase.com', :debug => true, :trace => false}
|
118
|
+
|
119
|
+
def initialize
|
120
|
+
@configuration = {}.reverse_merge!(DEFAULTS)
|
121
|
+
configure_rails if defined?(RAILS_ROOT)
|
122
|
+
end
|
123
|
+
|
124
|
+
def configure_rails
|
125
|
+
@filename = "#{RAILS_ROOT}/config/freebase.yml"
|
126
|
+
unless File.exists? @filename
|
127
|
+
puts "WARNING: #{RAILS_ROOT}/config/freebase.yml configuration file is not found. Using sandbox.freebase.com."
|
128
|
+
else
|
129
|
+
set_all YAML.load_file(@filename)[RAILS_ENV].symbolize_keys!
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def set_all(opts = {})
|
134
|
+
opts.each {|k,v| self[k] = v}
|
135
|
+
end
|
136
|
+
|
137
|
+
def []=(k,v)
|
138
|
+
@configuration[k] = v
|
139
|
+
end
|
140
|
+
|
141
|
+
def [](k)
|
142
|
+
@configuration[k]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# logging service. Is it a bad idea?
|
147
|
+
class Logger
|
148
|
+
#TODO: log4r or rails logging?
|
149
|
+
[:trace, :debug, :warn, :error].each do |level|
|
150
|
+
eval %Q{
|
151
|
+
def self.#{level}(message = nil)
|
152
|
+
if Configuration.instance[:#{level}]
|
153
|
+
puts message || yield
|
154
|
+
end
|
155
|
+
end
|
156
|
+
}
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
SERVICES = { :mqlread => '/api/service/mqlread',
|
161
|
+
:mqlwrite => '/api/service/mqlwrite',
|
162
|
+
:login => '/api/account/login',
|
163
|
+
:upload => '/api/service/upload'
|
164
|
+
}
|
165
|
+
|
166
|
+
# get the service url for the specified service.
|
167
|
+
def service_url(svc)
|
168
|
+
"http://#{Configuration.instance[:host]}#{SERVICES[svc]}"
|
169
|
+
end
|
170
|
+
|
171
|
+
SERVICES.each_key do |k|
|
172
|
+
define_method("#{k}_service_url") do
|
173
|
+
service_url(k)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# raise an error if the inner response envelope is encoded as an error
|
178
|
+
def handle_read_error(inner)
|
179
|
+
unless inner['code'].starts_with?('/api/status/ok')
|
180
|
+
Logger.error "<<< Received Error: #{inner.inspect}"
|
181
|
+
error = inner['messages'][0]
|
182
|
+
raise MqlReadError.new(error['code'], error['message'])
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
|
188
|
+
# perform a mqlread and return the results
|
189
|
+
# Specify :raw => true if you don't want the results converted into a FreebaseResult object.
|
190
|
+
def mqlread(query, options = {})
|
191
|
+
Logger.trace {">>> Sending Query: #{query.inspect}"}
|
192
|
+
envelope = { :qname => {:query => query }}
|
193
|
+
response = http_request mqlread_service_url, :queries => envelope.to_json
|
194
|
+
result = ActiveSupport::JSON.decode(response)
|
195
|
+
inner = result['qname']
|
196
|
+
handle_read_error(inner)
|
197
|
+
Logger.trace {"<<< Received Response: #{inner['result'].inspect}"}
|
198
|
+
if options[:raw]
|
199
|
+
inner['result']
|
200
|
+
else
|
201
|
+
inner['result'] ? FreebaseResult.new(inner['result']) : nil
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
protected
|
206
|
+
def params_to_string(parameters)
|
207
|
+
parameters.keys.map {|k| "#{URI.encode(k.to_s)}=#{URI.encode(parameters[k])}" }.join('&')
|
208
|
+
end
|
209
|
+
def http_request(url, parameters = {})
|
210
|
+
params = params_to_string(parameters)
|
211
|
+
url << '?'+params unless params.blank?
|
212
|
+
returning(Net::HTTP.get_response(URI.parse(url)).body) do |response|
|
213
|
+
Logger.trace do
|
214
|
+
fname = "#{MD5.md5(params)}.mql"
|
215
|
+
open(fname,"w") do |f|
|
216
|
+
f << response
|
217
|
+
end
|
218
|
+
"Wrote response to #{fname}"
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.dirname(__FILE__)+'/test_helper'
|
3
|
+
|
4
|
+
class FreebaseTest < Test::Unit::TestCase
|
5
|
+
# Replace this with your real tests.
|
6
|
+
def test_load_music_artist
|
7
|
+
Freebase::Types::Music::Artist
|
8
|
+
end
|
9
|
+
def test_find_first_artist
|
10
|
+
assert_kind_of Freebase::Types::Music::Artist,
|
11
|
+
(artist = Freebase::Types::Music::Artist.find(:first,:conditions => {:name => "The Police"}))
|
12
|
+
assert_equal 20, artist.albums.size
|
13
|
+
end
|
14
|
+
def test_association_preloading
|
15
|
+
artist = Freebase::Types::Music::Artist.find(:first,
|
16
|
+
:conditions => {
|
17
|
+
:name => {:value => ARGV[0], :lang => {:name => "English"}},
|
18
|
+
:album => [{
|
19
|
+
:fb_object => true,
|
20
|
+
:name => {:value => nil, :lang => {:name => "English"}},
|
21
|
+
:release_date => nil,
|
22
|
+
:track => [{
|
23
|
+
:fb_object => true,
|
24
|
+
:length => nil,
|
25
|
+
:name => {:value => nil, :lang => {:name => "English"}}
|
26
|
+
}]
|
27
|
+
}]
|
28
|
+
}
|
29
|
+
)
|
30
|
+
quiesce(:find, :reload) do
|
31
|
+
assert_equal 14, artist.albums.size
|
32
|
+
assert_equal 8, artist.albums.first.tracks.size
|
33
|
+
#artist.albums.first.producer
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: freebase
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chris Eppstein
|
8
|
+
autorequire: freebase
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-02-03 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activesupport
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.0.2
|
23
|
+
version:
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: json
|
26
|
+
version_requirement:
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: 1.1.2
|
32
|
+
version:
|
33
|
+
description:
|
34
|
+
email: chris@eppsteins.net
|
35
|
+
executables: []
|
36
|
+
|
37
|
+
extensions: []
|
38
|
+
|
39
|
+
extra_rdoc_files:
|
40
|
+
- README
|
41
|
+
files:
|
42
|
+
- lib/core_extensions.rb
|
43
|
+
- lib/freebase
|
44
|
+
- lib/freebase/api.rb
|
45
|
+
- lib/freebase.rb
|
46
|
+
- README
|
47
|
+
has_rdoc: true
|
48
|
+
homepage: http://rubyforge.org/projects/freebaseapi/
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
version:
|
66
|
+
requirements: []
|
67
|
+
|
68
|
+
rubyforge_project: freebaseapi
|
69
|
+
rubygems_version: 0.9.5
|
70
|
+
signing_key:
|
71
|
+
specification_version: 2
|
72
|
+
summary: Ruby wrapper for the Freebase.com API that makes interacting with freebase.com in your Ruby on Rails application as easy as using Active Record. Freebase is a free, collaborative semantic database.
|
73
|
+
test_files:
|
74
|
+
- test/freebase_test.rb
|