factual-api 0.2 → 0.5
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/CHANGELOG.md +5 -0
- data/README.md +6 -12
- data/lib/factual.rb +27 -0
- data/lib/factual/api.rb +52 -0
- data/lib/factual/query.rb +105 -0
- metadata +6 -3
- data/lib/factual_api.rb +0 -239
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -5,8 +5,8 @@ This is the Factual supported Ruby driver for [Factual's public API](http://deve
|
|
5
5
|
# Installation
|
6
6
|
|
7
7
|
gem 'factual-api'
|
8
|
-
require '
|
9
|
-
factual = Factual
|
8
|
+
require 'factual'
|
9
|
+
factual = Factual.new(YOUR_KEY, YOUR_SECRET)
|
10
10
|
|
11
11
|
# Examples
|
12
12
|
|
@@ -37,7 +37,7 @@ This is the Factual supported Ruby driver for [Factual's public API](http://deve
|
|
37
37
|
# 6. Page it
|
38
38
|
query = query.page(2, :per => 10)
|
39
39
|
|
40
|
-
# 7. Finally, get response in
|
40
|
+
# 7. Finally, get response in a hash or array of hashes
|
41
41
|
query.first # return one row
|
42
42
|
query.rows # return many rows
|
43
43
|
|
@@ -56,7 +56,7 @@ This is the Factual supported Ruby driver for [Factual's public API](http://deve
|
|
56
56
|
|
57
57
|
## Resolve
|
58
58
|
|
59
|
-
# Returns resolved entities as
|
59
|
+
# Returns resolved entities as an array of hashes
|
60
60
|
query = factual.resolve("name" => "McDonalds",
|
61
61
|
"address" => "10451 Santa Monica Blvd",
|
62
62
|
"region" => "CA",
|
@@ -67,11 +67,5 @@ This is the Factual supported Ruby driver for [Factual's public API](http://deve
|
|
67
67
|
|
68
68
|
## Schema
|
69
69
|
|
70
|
-
# Returns a
|
71
|
-
factual.table("global").schema
|
72
|
-
|
73
|
-
## Facets
|
74
|
-
|
75
|
-
# Returns number of starbucks in regions thoughout the world
|
76
|
-
factual.table("global").select("country", "region").search("starbucks").min_count(2).facets()
|
77
|
-
|
70
|
+
# Returns a hash of table metadata, including an array of fields
|
71
|
+
factual.table("global").schema
|
data/lib/factual.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'oauth'
|
2
|
+
require 'factual/api'
|
3
|
+
require 'factual/query'
|
4
|
+
|
5
|
+
class Factual
|
6
|
+
def initialize(key, secret)
|
7
|
+
@api = Factual::API.new(generate_token(key, secret))
|
8
|
+
end
|
9
|
+
|
10
|
+
def crosswalk(factual_id)
|
11
|
+
Query.new(@api, :crosswalk, "places/crosswalk", :factual_id => factual_id)
|
12
|
+
end
|
13
|
+
|
14
|
+
def resolve(values)
|
15
|
+
Query.new(@api, :resolve, "places/resolve", :values => values)
|
16
|
+
end
|
17
|
+
|
18
|
+
def table(table_id_or_alias)
|
19
|
+
Query.new(@api, :read, "t/#{table_id_or_alias}")
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def generate_token(key, secret)
|
25
|
+
OAuth::AccessToken.new(OAuth::Consumer.new(key, secret))
|
26
|
+
end
|
27
|
+
end
|
data/lib/factual/api.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
class Factual
|
5
|
+
class API
|
6
|
+
API_V3_HOST = "http://api.v3.factual.com"
|
7
|
+
DRIVER_VERSION_TAG = "factual-ruby-driver-1.0"
|
8
|
+
PARAM_ALIASES = { :search => :q }
|
9
|
+
|
10
|
+
def initialize(access_token)
|
11
|
+
@access_token = access_token
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute(query)
|
15
|
+
params_with_count = query.params.merge(:include_count => true)
|
16
|
+
handle_request(query.action || :read, query.path, params_with_count)
|
17
|
+
end
|
18
|
+
|
19
|
+
def schema(query)
|
20
|
+
handle_request(:schema, query.path + "/schema", query.params)["view"]
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def handle_request(action, path, params)
|
26
|
+
response = make_request(path, params)
|
27
|
+
json = response.body
|
28
|
+
payload = JSON.parse(json)
|
29
|
+
|
30
|
+
raise StandardError.new(payload["message"]) unless payload["status"] == "ok"
|
31
|
+
|
32
|
+
payload["response"]
|
33
|
+
end
|
34
|
+
|
35
|
+
def make_request(path, params)
|
36
|
+
url = "#{API_V3_HOST}/#{path}?#{query_string(params)}"
|
37
|
+
headers = { "X-Factual-Lib" => DRIVER_VERSION_TAG }
|
38
|
+
|
39
|
+
@access_token.get(url, headers)
|
40
|
+
end
|
41
|
+
|
42
|
+
def query_string(params)
|
43
|
+
query_array = params.keys.inject([]) do |array, key|
|
44
|
+
param_alias = PARAM_ALIASES[key.to_sym] || key.to_sym
|
45
|
+
value = params[key].class == Hash ? params[key].to_json : params[key].to_s
|
46
|
+
array << "#{param_alias}=#{URI.escape(value)}"
|
47
|
+
end
|
48
|
+
|
49
|
+
query_array.join("&")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
class Factual
|
2
|
+
class Query
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
DEFAULT_LIMIT = 20
|
6
|
+
VALID_PARAMS = {
|
7
|
+
:read => [ :filters, :search, :geo, :sort, :select, :limit, :offset ],
|
8
|
+
:resolve => [ :values ],
|
9
|
+
:crosswalk => [ :factual_id ],
|
10
|
+
:schema => [ ],
|
11
|
+
:any => [ :include_count ]
|
12
|
+
}
|
13
|
+
|
14
|
+
def initialize(api, action, path, params = {})
|
15
|
+
@api = api
|
16
|
+
@action = action
|
17
|
+
@path = path
|
18
|
+
@params = params
|
19
|
+
@response = nil
|
20
|
+
@schema = nil
|
21
|
+
validate_params
|
22
|
+
end
|
23
|
+
|
24
|
+
# Attribute Readers
|
25
|
+
attr_reader :action
|
26
|
+
|
27
|
+
[:path, :params].each do |attribute|
|
28
|
+
define_method(attribute) do
|
29
|
+
instance_variable_get("@#{attribute}").clone
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Response Methods
|
34
|
+
def each(&block)
|
35
|
+
rows.each { |row| block.call(row) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def last
|
39
|
+
rows.last
|
40
|
+
end
|
41
|
+
|
42
|
+
def [](index)
|
43
|
+
rows[index]
|
44
|
+
end
|
45
|
+
|
46
|
+
def rows
|
47
|
+
(@response ||= @api.execute(self))["data"].clone
|
48
|
+
end
|
49
|
+
|
50
|
+
def total_rows
|
51
|
+
(@response ||= @api.execute(self))["total_row_count"]
|
52
|
+
end
|
53
|
+
|
54
|
+
def schema
|
55
|
+
(@schema ||= @api.schema(self)).clone
|
56
|
+
end
|
57
|
+
|
58
|
+
# Query Modifiers
|
59
|
+
VALID_PARAMS.values.flatten.uniq.each do |param|
|
60
|
+
define_method(param) do |*args|
|
61
|
+
args = args.collect{|arg| arg.is_a?(String) ? arg.strip : arg }
|
62
|
+
value = (args.length == 1) ? args.first : args.join(',')
|
63
|
+
|
64
|
+
new_params = @params.clone
|
65
|
+
new_params[param] = value
|
66
|
+
|
67
|
+
Query.new(@api, @action, @path, new_params)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def sort_desc(*args)
|
72
|
+
columns = args.map { |column| "#{column}:desc" }
|
73
|
+
|
74
|
+
new_params = @params.clone
|
75
|
+
new_params[:sort] = columns.join(',')
|
76
|
+
|
77
|
+
Query.new(@api, @action, @path, new_params)
|
78
|
+
end
|
79
|
+
|
80
|
+
def page(page_number, paging_options = {})
|
81
|
+
limit = (paging_options[:per] || paging_options["per"] || DEFAULT_LIMIT).to_i
|
82
|
+
limit = DEFAULT_LIMIT if limit < 1
|
83
|
+
|
84
|
+
page_number = page_number.to_i
|
85
|
+
page_number = 1 if page_number < 1
|
86
|
+
|
87
|
+
new_params = @params.clone
|
88
|
+
new_params[:limit] = limit
|
89
|
+
new_params[:offset] = (page_number - 1) * limit
|
90
|
+
|
91
|
+
Query.new(@api, @action, @path, new_params)
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
# Validations
|
97
|
+
def validate_params
|
98
|
+
@params.each do |param, val|
|
99
|
+
unless (VALID_PARAMS[@action] + VALID_PARAMS[:any]).include?(param)
|
100
|
+
raise StandardError.new("InvalidArgument #{param} for #{@action}")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
metadata
CHANGED
@@ -2,16 +2,17 @@
|
|
2
2
|
name: factual-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: "0.
|
5
|
+
version: "0.5"
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
|
+
- Rudiger Lippert
|
8
9
|
- Forrest Cao
|
9
10
|
- Aaron Crow
|
10
11
|
autorequire:
|
11
12
|
bindir: bin
|
12
13
|
cert_chain: []
|
13
14
|
|
14
|
-
date: 2012-01-
|
15
|
+
date: 2012-01-19 00:00:00 +08:00
|
15
16
|
default_executable:
|
16
17
|
dependencies:
|
17
18
|
- !ruby/object:Gem::Dependency
|
@@ -35,7 +36,9 @@ extensions: []
|
|
35
36
|
extra_rdoc_files: []
|
36
37
|
|
37
38
|
files:
|
38
|
-
- lib/
|
39
|
+
- lib/factual/api.rb
|
40
|
+
- lib/factual/query.rb
|
41
|
+
- lib/factual.rb
|
39
42
|
- README.md
|
40
43
|
- CHANGELOG.md
|
41
44
|
has_rdoc: true
|
data/lib/factual_api.rb
DELETED
@@ -1,239 +0,0 @@
|
|
1
|
-
require 'oauth'
|
2
|
-
require 'json'
|
3
|
-
require 'uri'
|
4
|
-
require 'ostruct'
|
5
|
-
|
6
|
-
module Factual
|
7
|
-
class Api
|
8
|
-
API_V3_HOST = "http://api.v3.factual.com"
|
9
|
-
DRIVER_VERSION_TAG = "factual-ruby-driver-1.0"
|
10
|
-
PARAM_ALIASES = { :search => :q }
|
11
|
-
|
12
|
-
# initializers
|
13
|
-
# ----------------
|
14
|
-
def initialize(key, secret)
|
15
|
-
@access_token = OAuth::AccessToken.new(
|
16
|
-
OAuth::Consumer.new(key, secret))
|
17
|
-
end
|
18
|
-
|
19
|
-
# actions
|
20
|
-
# ----------------
|
21
|
-
def crosswalk(factual_id, format = :object)
|
22
|
-
query = Query.new(self, :crosswalk, "places/crosswalk", { :factual_id => factual_id }, format)
|
23
|
-
|
24
|
-
return query
|
25
|
-
end
|
26
|
-
|
27
|
-
def resolve(values, format = :object)
|
28
|
-
query = Query.new(self, :resolve, "places/resolve", { :values => values }, format)
|
29
|
-
|
30
|
-
return query
|
31
|
-
end
|
32
|
-
|
33
|
-
def table(table_id_or_alias, format = :object)
|
34
|
-
query = Query.new(self, :read, "t/#{table_id_or_alias}", Hash.new, format)
|
35
|
-
|
36
|
-
return query
|
37
|
-
end
|
38
|
-
|
39
|
-
# requesting
|
40
|
-
# ----------------
|
41
|
-
def request(path, params)
|
42
|
-
url = "#{API_V3_HOST}/#{path}?#{query_string(params)}"
|
43
|
-
headers = {"X-Factual-Lib" => DRIVER_VERSION_TAG}
|
44
|
-
|
45
|
-
return @access_token.get(url, headers)
|
46
|
-
end
|
47
|
-
|
48
|
-
private
|
49
|
-
|
50
|
-
def query_string(params)
|
51
|
-
arr = []
|
52
|
-
params.each do |param, v|
|
53
|
-
param_alias = PARAM_ALIASES[param.to_sym] || param.to_sym
|
54
|
-
|
55
|
-
v = v.to_json if v.class == Hash
|
56
|
-
arr << "#{param_alias}=#{URI.escape(v.to_s)}"
|
57
|
-
end
|
58
|
-
return arr.join("&")
|
59
|
-
end
|
60
|
-
|
61
|
-
end
|
62
|
-
|
63
|
-
class Query
|
64
|
-
|
65
|
-
# helper functions
|
66
|
-
DEFAULT_LIMIT = 20
|
67
|
-
|
68
|
-
VALID_PARAMS = {
|
69
|
-
:read => [ :filters, :search, :geo, :sort, :select, :limit, :offset ],
|
70
|
-
:resolve => [ :values ],
|
71
|
-
:crosswalk => [ :factual_id ],
|
72
|
-
:facets => [ :filters, :search, :geo, :limit, :select, :min_count ],
|
73
|
-
:schema => [ ],
|
74
|
-
:any => [ :include_count ]
|
75
|
-
}
|
76
|
-
|
77
|
-
attr_accessor :params
|
78
|
-
|
79
|
-
# initializers
|
80
|
-
# ----------------
|
81
|
-
def initialize(api, action, path, params = nil, format = :object)
|
82
|
-
@api = api
|
83
|
-
@action = action
|
84
|
-
@path = path
|
85
|
-
@params = params || Hash.new
|
86
|
-
@format = format
|
87
|
-
end
|
88
|
-
|
89
|
-
# helper functions
|
90
|
-
# ----------------
|
91
|
-
def clone
|
92
|
-
new_query = self.class.new(@api, @action, @path, @params.clone, @format)
|
93
|
-
|
94
|
-
return new_query
|
95
|
-
end
|
96
|
-
|
97
|
-
def set_param(key, value)
|
98
|
-
@params[key] = value
|
99
|
-
end
|
100
|
-
|
101
|
-
# attributes, after 'get'
|
102
|
-
# ----------------
|
103
|
-
def first
|
104
|
-
row_data = read_response["data"].first
|
105
|
-
|
106
|
-
if (@format == :json) # or :object
|
107
|
-
return row_data
|
108
|
-
else
|
109
|
-
return Row.new(row_data)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
def rows
|
114
|
-
return read_response["data"] if (@format == :json)
|
115
|
-
|
116
|
-
return read_response["data"].collect do |row_data|
|
117
|
-
Row.new(row_data)
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
def total_count
|
122
|
-
read_response["total_row_count"]
|
123
|
-
end
|
124
|
-
|
125
|
-
|
126
|
-
def schema
|
127
|
-
unless @schema_response
|
128
|
-
@path += "/schema"
|
129
|
-
@schema_response = response(:schema)
|
130
|
-
end
|
131
|
-
|
132
|
-
view = @schema_response["view"]
|
133
|
-
fields = view["fields"]
|
134
|
-
|
135
|
-
schema = Table.new(view)
|
136
|
-
if schema && fields
|
137
|
-
schema.fields = fields.collect do |f|
|
138
|
-
Field.new(f)
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
return schema
|
143
|
-
end
|
144
|
-
|
145
|
-
def facets
|
146
|
-
unless @facets_response
|
147
|
-
@path += "/facets"
|
148
|
-
@facets_response = response(:facets)
|
149
|
-
end
|
150
|
-
columns = @facets_response["data"]
|
151
|
-
|
152
|
-
return Facet.new(columns)
|
153
|
-
end
|
154
|
-
|
155
|
-
# query builder, returns immutable ojbects
|
156
|
-
# ----------------
|
157
|
-
VALID_PARAMS.values.flatten.uniq.each do |param|
|
158
|
-
define_method(param) do |*args|
|
159
|
-
api = self.clone()
|
160
|
-
val = (args.length == 1) ? args.first : args.join(',')
|
161
|
-
|
162
|
-
api.set_param(param, val)
|
163
|
-
|
164
|
-
return api
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
# sugers
|
169
|
-
# ----------------
|
170
|
-
def sort_desc(*args)
|
171
|
-
api = self.clone
|
172
|
-
columns = args.collect{ |col|"#{col}:desc" }
|
173
|
-
api.set_param(:sort, columns.join(','))
|
174
|
-
|
175
|
-
return api
|
176
|
-
end
|
177
|
-
|
178
|
-
def page(page_num, paging_opts = {})
|
179
|
-
limit = (paging_opts[:per] || paging_opts["per"]).to_i
|
180
|
-
limit = DEFAULT_LIMIT if limit < 1
|
181
|
-
|
182
|
-
page_num = page_num.to_i
|
183
|
-
page_num = 1 if page_num < 1
|
184
|
-
offset = (page_num - 1) * limit
|
185
|
-
|
186
|
-
api = self.clone
|
187
|
-
api.set_param(:limit, limit)
|
188
|
-
api.set_param(:offset, offset)
|
189
|
-
|
190
|
-
return api
|
191
|
-
end
|
192
|
-
|
193
|
-
# requesting
|
194
|
-
# ----------------
|
195
|
-
private
|
196
|
-
|
197
|
-
def read_response
|
198
|
-
unless @read_response
|
199
|
-
# always include count for reads
|
200
|
-
@params[:include_count] = true
|
201
|
-
@read_response = response(@action || :read)
|
202
|
-
end
|
203
|
-
|
204
|
-
return @read_response
|
205
|
-
end
|
206
|
-
|
207
|
-
def check_params!(action)
|
208
|
-
@params.each do |param, val|
|
209
|
-
unless (VALID_PARAMS[action] + VALID_PARAMS[:any]).include?(param)
|
210
|
-
raise StandardError.new("InvalidArgument #{param} for #{action}")
|
211
|
-
end
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
def response(action)
|
216
|
-
check_params!(action)
|
217
|
-
|
218
|
-
res = @api.request(@path, @params)
|
219
|
-
|
220
|
-
code = res.code
|
221
|
-
json = res.body
|
222
|
-
payload = JSON.parse(json)
|
223
|
-
|
224
|
-
if payload["status"] == "ok"
|
225
|
-
return payload["response"]
|
226
|
-
else
|
227
|
-
raise StandardError.new(payload["message"])
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
end
|
232
|
-
|
233
|
-
# response classes
|
234
|
-
# ----------------
|
235
|
-
class Row < OpenStruct; end
|
236
|
-
class Facet < OpenStruct; end
|
237
|
-
class Table < OpenStruct; end
|
238
|
-
class Field < OpenStruct; end
|
239
|
-
end
|