factual-api 0.2 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,8 @@
1
+ ## v0.3
2
+ * return hash instead of objects
3
+ * removing facets
4
+ * a little refactoring
5
+
1
6
  ## v0.2
2
7
  * refactoring
3
8
 
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 'factual_api'
9
- factual = Factual::Api.new(YOUR_KEY, YOUR_SECRET)
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 Factual::Row objects
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 Factual::Row objects
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 Factual::Table object whose fields are an array of Factual::Field objects
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
@@ -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
@@ -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.2"
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-12 00:00:00 +08:00
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/factual_api.rb
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
@@ -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