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.
@@ -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