factual-api 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/CHANGELOG.md +9 -0
- data/README.md +73 -0
- data/lib/factual_api.rb +229 -0
- metadata +70 -0
data/CHANGELOG.md
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
## v0.1
|
2
|
+
* initial version
|
3
|
+
** a table agnostic read api, usage as: api.table(table_alias).rows
|
4
|
+
** normal (but powerful) filters, usage as: api.table(:global).filters(:country => {'$sw' => 'u'}, :region => 'ca')
|
5
|
+
** general mapping of ALL v3 api params, e.g. geo, query, sort, select, limit, offset
|
6
|
+
** places sugers, usage as: api.crosswalk(FACTUAL_ID); api.resolve(:name => 'factual inc.', :region => 'ca')
|
7
|
+
** facets
|
8
|
+
** different format, usage as: json_api = Factual::Api.new( KEY, SECRET, :json)
|
9
|
+
** immutable api objects
|
data/README.md
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# Introduction
|
2
|
+
|
3
|
+
This is the Factual supported Ruby driver for [Factual's public API](http://developer.factual.com/display/docs/Factual+Developer+APIs+Version+3).
|
4
|
+
|
5
|
+
# Installation
|
6
|
+
|
7
|
+
gem 'factual-api'
|
8
|
+
require 'factual_api'
|
9
|
+
factual = Factual::Api.new(YOUR_KEY, YOUR_SECRET)
|
10
|
+
|
11
|
+
# Examples
|
12
|
+
|
13
|
+
## Quick Sample
|
14
|
+
|
15
|
+
# Returns Places with names beginning with "Star"
|
16
|
+
factual.table("places").filters("name" => {"$bw" => "Star"}).rows
|
17
|
+
|
18
|
+
## Read (with all features)
|
19
|
+
|
20
|
+
# 1. Specify the table Global
|
21
|
+
query = factual.table("global")
|
22
|
+
|
23
|
+
# 2. Filter results in country US (For more filters syntax, refer to [Core API - Row Filters](http://developer.factual.com/display/docs/Core+API+-+Row+Filters))
|
24
|
+
query = query.filters("country" => "US")
|
25
|
+
|
26
|
+
# 3. Search for "sushi" or "sashimi" (For more search syntax, refer to [Core API - Search Filters](http://developer.factual.com/display/docs/Core+API+-+Search+Filters))
|
27
|
+
query = query.search("sushi", "sashimi")
|
28
|
+
|
29
|
+
# 4. Filter by Geo (For more geo syntax, refer to [Core API - Geo Filters](http://developer.factual.com/display/docs/Core+API+-+Geo+Filters))
|
30
|
+
query = query.geo("$circle" => {"$center" => [34.06021, -118.41828], "$meters" => 5000})
|
31
|
+
|
32
|
+
# 5. Sort it
|
33
|
+
query = query.sort("name") # ascending
|
34
|
+
query = query.sort_desc("name") # descending
|
35
|
+
query = query.sort("address", "name") # sort by multiple columns
|
36
|
+
|
37
|
+
# 6. Page it
|
38
|
+
query = query.page(2, :per => 10)
|
39
|
+
|
40
|
+
# 7. Finally, get response in Factual::Row objects
|
41
|
+
query.first # return one row
|
42
|
+
query.rows # return many rows
|
43
|
+
|
44
|
+
# 8. Returns total row counts that matches the criteria
|
45
|
+
query.total_count
|
46
|
+
|
47
|
+
# You can chain the query methods, like this
|
48
|
+
factual.table("places").filters("region" => "CA").search("sushi", "sashimi").geo("$circle" => {"$center" => [34.06021, -118.41828], "$meters" => 5000}).sort("name").page(2, :per => 10).rows
|
49
|
+
|
50
|
+
## Crosswalk
|
51
|
+
|
52
|
+
# Concordance information of a place
|
53
|
+
FACTUAL_ID = "110ace9f-80a7-47d3-9170-e9317624ebd9"
|
54
|
+
factual.crosswalk(FACTUAL_ID)
|
55
|
+
|
56
|
+
## Resolve
|
57
|
+
|
58
|
+
# Returns resolved entities as Factual::Row objects
|
59
|
+
factual.resolve("name" => "McDonalds",
|
60
|
+
"address" => "10451 Santa Monica Blvd",
|
61
|
+
"region" => "CA",
|
62
|
+
"postcode" => "90025")
|
63
|
+
|
64
|
+
## Schema
|
65
|
+
|
66
|
+
# Returns a Factual::Table object whose fields are an array of Factual::Field objects
|
67
|
+
factual.table("global").schema()
|
68
|
+
|
69
|
+
## Facets
|
70
|
+
|
71
|
+
# Returns number of starbucks in regions thoughout the world
|
72
|
+
factual.table("global").select("country", "region").search("starbucks").min_count(2).facets()
|
73
|
+
|
data/lib/factual_api.rb
ADDED
@@ -0,0 +1,229 @@
|
|
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
|
+
|
11
|
+
DEFAULT_LIMIT = 20
|
12
|
+
PARAM_ALIASES = { :search => :q }
|
13
|
+
|
14
|
+
VALID_PARAMS = {
|
15
|
+
:read => [ :filters, :search, :geo, :sort, :select, :limit, :offset ],
|
16
|
+
:resolve => [ :values ],
|
17
|
+
:crosswalk => [ :factual_id ],
|
18
|
+
:facets => [ :filters, :search, :geo, :limit, :select, :min_count ],
|
19
|
+
:schema => [ ],
|
20
|
+
:any => [ :include_count ]
|
21
|
+
}
|
22
|
+
|
23
|
+
attr_accessor :access_token, :path, :params, :action, :format
|
24
|
+
|
25
|
+
# initializers
|
26
|
+
# ----------------
|
27
|
+
def initialize(key, secret, format = :object)
|
28
|
+
@access_token = OAuth::AccessToken.new(
|
29
|
+
OAuth::Consumer.new(key, secret))
|
30
|
+
|
31
|
+
@format = format
|
32
|
+
@params = Hash.new
|
33
|
+
end
|
34
|
+
|
35
|
+
# helper functions
|
36
|
+
# ----------------
|
37
|
+
def self.clone(api)
|
38
|
+
new_api = self.new(nil, nil)
|
39
|
+
|
40
|
+
new_api.access_token = api.access_token
|
41
|
+
new_api.path = api.path
|
42
|
+
new_api.action = api.action
|
43
|
+
new_api.format = api.format
|
44
|
+
new_api.params = api.params.clone
|
45
|
+
|
46
|
+
return new_api
|
47
|
+
end
|
48
|
+
|
49
|
+
def set_param(key, value)
|
50
|
+
@params[key] = value
|
51
|
+
end
|
52
|
+
|
53
|
+
# attributes, after 'get'
|
54
|
+
# ----------------
|
55
|
+
def first
|
56
|
+
row_data = response["data"].first
|
57
|
+
|
58
|
+
if (@format == :json) # or :hash ?
|
59
|
+
return row_data
|
60
|
+
else
|
61
|
+
return Row.new(row_data)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def schema
|
66
|
+
@path += "/schema"
|
67
|
+
@action = :schema
|
68
|
+
|
69
|
+
view = response["view"]
|
70
|
+
fields = view["fields"]
|
71
|
+
|
72
|
+
schema = Table.new(view)
|
73
|
+
if schema && fields
|
74
|
+
schema.fields = fields.collect do |f|
|
75
|
+
Field.new(f)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
return schema
|
80
|
+
end
|
81
|
+
|
82
|
+
def facets
|
83
|
+
@path += "/facets"
|
84
|
+
@action = :facets
|
85
|
+
columns = response["data"]
|
86
|
+
|
87
|
+
return Facet.new(columns)
|
88
|
+
end
|
89
|
+
|
90
|
+
def total_count
|
91
|
+
response["total_row_count"]
|
92
|
+
end
|
93
|
+
|
94
|
+
def rows
|
95
|
+
return response["data"] if (@format == :json)
|
96
|
+
|
97
|
+
return response["data"].collect do |row_data|
|
98
|
+
Row.new(row_data)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# query builder, returns immutable ojbects
|
103
|
+
# ----------------
|
104
|
+
VALID_PARAMS.values.flatten.uniq.each do |param|
|
105
|
+
define_method(param) do |*args|
|
106
|
+
api = self.class.clone(self)
|
107
|
+
val = (args.length == 1) ? args.first : args.join(',')
|
108
|
+
|
109
|
+
api.set_param(param, val)
|
110
|
+
|
111
|
+
return api
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# sugers
|
116
|
+
# ----------------
|
117
|
+
def sort_desc(*args)
|
118
|
+
api = self.class.clone(self)
|
119
|
+
columns = args.collect{ |col|"#{col}:desc" }
|
120
|
+
api.set_param(:sort, columns.join(','))
|
121
|
+
|
122
|
+
return api
|
123
|
+
end
|
124
|
+
|
125
|
+
def page(page_num, paging_opts = {})
|
126
|
+
limit = (paging_opts[:per] || paging_opts["per"]).to_i
|
127
|
+
limit = DEFAULT_LIMIT if limit < 1
|
128
|
+
|
129
|
+
page_num = page_num.to_i
|
130
|
+
page_num = 1 if page_num < 1
|
131
|
+
offset = (page_num - 1) * limit
|
132
|
+
|
133
|
+
api = self.class.clone(self)
|
134
|
+
api.set_param(:limit, limit)
|
135
|
+
api.set_param(:offset, offset)
|
136
|
+
|
137
|
+
return api
|
138
|
+
end
|
139
|
+
|
140
|
+
# actions
|
141
|
+
# ----------------
|
142
|
+
def crosswalk(factual_id)
|
143
|
+
api = self
|
144
|
+
|
145
|
+
api.path = "places/crosswalk"
|
146
|
+
api.action = :crosswalk
|
147
|
+
api.params = { :factual_id => factual_id }
|
148
|
+
|
149
|
+
return api
|
150
|
+
end
|
151
|
+
|
152
|
+
def resolve(values)
|
153
|
+
api = self
|
154
|
+
|
155
|
+
api.action = :resolve
|
156
|
+
api.path = "places/resolve"
|
157
|
+
api.params = { :values => values }
|
158
|
+
|
159
|
+
return api
|
160
|
+
end
|
161
|
+
|
162
|
+
def table(table_id_or_alias)
|
163
|
+
api = self
|
164
|
+
if @response
|
165
|
+
api = self.class.clone(self)
|
166
|
+
end
|
167
|
+
|
168
|
+
api.path = "t/#{table_id_or_alias}"
|
169
|
+
api.action = :read
|
170
|
+
|
171
|
+
return api
|
172
|
+
end
|
173
|
+
|
174
|
+
private
|
175
|
+
|
176
|
+
# real requesting
|
177
|
+
# ----------------
|
178
|
+
def response
|
179
|
+
@response ||= {}
|
180
|
+
return @response[@action] if @response[@action]
|
181
|
+
|
182
|
+
# always include count for reads
|
183
|
+
@params[:include_count] = true unless @action == :schema
|
184
|
+
|
185
|
+
res = request()
|
186
|
+
|
187
|
+
code = res.code
|
188
|
+
json = res.body
|
189
|
+
payload = JSON.parse(json)
|
190
|
+
|
191
|
+
if payload["status"] == "ok"
|
192
|
+
@response[@action] = payload["response"]
|
193
|
+
else
|
194
|
+
raise StandardError.new(payload["message"])
|
195
|
+
end
|
196
|
+
|
197
|
+
return @response[@action]
|
198
|
+
end
|
199
|
+
|
200
|
+
def request
|
201
|
+
url = "#{API_V3_HOST}/#{@path}?#{query_string}"
|
202
|
+
headers = {"X-Factual-Lib" => DRIVER_VERSION_TAG}
|
203
|
+
|
204
|
+
return @access_token.get(url, headers)
|
205
|
+
end
|
206
|
+
|
207
|
+
def query_string()
|
208
|
+
arr = []
|
209
|
+
@params.each do |param, v|
|
210
|
+
unless (VALID_PARAMS[@action] + VALID_PARAMS[:any]).include?(param)
|
211
|
+
raise StandardError.new("InvalidArgument #{param} for #{@action}")
|
212
|
+
end
|
213
|
+
param_alias = PARAM_ALIASES[param.to_sym] || param.to_sym
|
214
|
+
|
215
|
+
v = v.to_json if v.class == Hash
|
216
|
+
arr << "#{param_alias}=#{URI.escape(v.to_s)}"
|
217
|
+
end
|
218
|
+
return arr.join("&")
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
|
223
|
+
# response classes
|
224
|
+
# ----------------
|
225
|
+
class Row < OpenStruct; end
|
226
|
+
class Facet < OpenStruct; end
|
227
|
+
class Table < OpenStruct; end
|
228
|
+
class Field < OpenStruct; end
|
229
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: factual-api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: "0.1"
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Forrest Cao
|
9
|
+
- Aaron Crow
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
|
14
|
+
date: 2012-01-12 00:00:00 +08:00
|
15
|
+
default_executable:
|
16
|
+
dependencies:
|
17
|
+
- !ruby/object:Gem::Dependency
|
18
|
+
name: rspec
|
19
|
+
prerelease: false
|
20
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
21
|
+
none: false
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: "0"
|
26
|
+
type: :development
|
27
|
+
version_requirements: *id001
|
28
|
+
description: Factual's official Ruby driver for the Factual public API.
|
29
|
+
email:
|
30
|
+
- aaron@factual.com
|
31
|
+
executables: []
|
32
|
+
|
33
|
+
extensions: []
|
34
|
+
|
35
|
+
extra_rdoc_files: []
|
36
|
+
|
37
|
+
files:
|
38
|
+
- lib/factual_api.rb
|
39
|
+
- README.md
|
40
|
+
- CHANGELOG.md
|
41
|
+
has_rdoc: true
|
42
|
+
homepage: http://github.com/Factual/factual-ruby-driver
|
43
|
+
licenses: []
|
44
|
+
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.3.6
|
62
|
+
requirements: []
|
63
|
+
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.6.2
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: Ruby driver for Factual
|
69
|
+
test_files: []
|
70
|
+
|