dillo 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.md +22 -0
- data/lib/dillo.rb +324 -0
- metadata +128 -0
data/README.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#Dillo
|
2
|
+
|
3
|
+
Ruby library for working with the City of Austin [Data Portal](http://data.austintexas.gov). This library is derived from [Windy](https://github.com/Chicago/windy) which is a library for interacting with Chicago's [Socrata](http://www.socrata.com) powered data portal.
|
4
|
+
|
5
|
+
##Installing
|
6
|
+
|
7
|
+
$ gem install dillo
|
8
|
+
|
9
|
+
##Future Plans
|
10
|
+
|
11
|
+
+ Full RDoc documentation
|
12
|
+
+ Rake tasks for running tests and misc.
|
13
|
+
+ More tests
|
14
|
+
+ Example applications
|
15
|
+
|
16
|
+
##Issues
|
17
|
+
|
18
|
+
Please use the issue tracker here on github.
|
19
|
+
|
20
|
+
##Contributions
|
21
|
+
|
22
|
+
Any contributions are welcome, there is not an specific requirements or standards being used at the moment.
|
data/lib/dillo.rb
ADDED
@@ -0,0 +1,324 @@
|
|
1
|
+
require 'faraday_middleware'
|
2
|
+
require 'multi_json'
|
3
|
+
|
4
|
+
module Dillo
|
5
|
+
VERSION = '0.1'
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_accessor :app_token, :debug
|
9
|
+
end
|
10
|
+
|
11
|
+
module Response
|
12
|
+
class RaiseClientError < Faraday::Response::Middleware
|
13
|
+
def on_complete(env)
|
14
|
+
case env[:status].to_i
|
15
|
+
when 403
|
16
|
+
raise env[:response_headers][:x_error_message]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class SocrataAppTokenMiddleware < Faraday::Middleware
|
23
|
+
def call(env)
|
24
|
+
raise "You must specify an app token" if !Dillo.app_token
|
25
|
+
env[:request_headers]["X-App-Token"] = Dillo.app_token
|
26
|
+
@app.call env
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Base
|
31
|
+
def self.root
|
32
|
+
"http://data.austintexas.gov"
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :connection
|
36
|
+
|
37
|
+
def initialize
|
38
|
+
@connection = Faraday.new(:url => self.class.root) do |builder|
|
39
|
+
builder.use SocrataAppTokenMiddleware
|
40
|
+
builder.use Dillo::Response::RaiseClientError
|
41
|
+
builder.use FaradayMiddleware::ParseJson, :content_type => /\bjson$/
|
42
|
+
|
43
|
+
# Enable logger output with Dillojamie.phillips@austintexas.gov.debug = true
|
44
|
+
if Dillo.debug
|
45
|
+
builder.response :logger
|
46
|
+
end
|
47
|
+
|
48
|
+
builder.adapter :net_http
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def body
|
53
|
+
@body ||= connection.get(path) do |request|
|
54
|
+
prepare_request(request)
|
55
|
+
# warn "Fetching #{request.to_env(@connection)[:url]}"
|
56
|
+
end.body
|
57
|
+
end
|
58
|
+
|
59
|
+
def prepare_request(request)
|
60
|
+
end
|
61
|
+
|
62
|
+
def json
|
63
|
+
# For some reason ruby 1.9.x seems to be trying to parse the
|
64
|
+
# API JSON output as ASCII instead of UTF-8
|
65
|
+
body.force_encoding("UTF-8") unless body.nil? || !body.respond_to?(:force_encoding)
|
66
|
+
@json ||= MultiJson.decode(body)
|
67
|
+
end
|
68
|
+
|
69
|
+
def inspect
|
70
|
+
"#<#{self.class.name} #{self.class.root}#{path}>"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
module Finders
|
75
|
+
def [](index)
|
76
|
+
to_a[index]
|
77
|
+
end
|
78
|
+
|
79
|
+
def respond_to?(method)
|
80
|
+
method.to_s[/^find(_all)?_by_./] || super
|
81
|
+
end
|
82
|
+
|
83
|
+
def method_missing(method, *args, &block)
|
84
|
+
if attribute = method.to_s[/^(find(_all)?)_by_(.+)$/, 3]
|
85
|
+
value = args.first
|
86
|
+
send($1) { |record| record.send(attribute) == value }
|
87
|
+
else
|
88
|
+
super
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class PaginatedCollection < Base
|
94
|
+
include Enumerable, Finders
|
95
|
+
|
96
|
+
def initialize
|
97
|
+
@pages ||= {}
|
98
|
+
end
|
99
|
+
|
100
|
+
def page(number)
|
101
|
+
@pages[number] ||= Page.new(self, number)
|
102
|
+
end
|
103
|
+
|
104
|
+
def each_page
|
105
|
+
number = 1
|
106
|
+
loop do
|
107
|
+
page = self.page(number)
|
108
|
+
break if page.count.zero?
|
109
|
+
yield page
|
110
|
+
number += 1
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def each(&block)
|
115
|
+
each_page do |page|
|
116
|
+
page.each(&block)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class Collection < Base
|
122
|
+
include Enumerable, Finders
|
123
|
+
|
124
|
+
def each(&block)
|
125
|
+
records.each(&block)
|
126
|
+
end
|
127
|
+
|
128
|
+
def record_attributes
|
129
|
+
json
|
130
|
+
end
|
131
|
+
|
132
|
+
def records
|
133
|
+
@records ||= record_attributes.map do |attributes|
|
134
|
+
create_record(attributes)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def create_record(attributes)
|
139
|
+
record_class.new(attributes)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
class Page < Collection
|
144
|
+
attr_accessor :collection, :number
|
145
|
+
|
146
|
+
def initialize(collection, number)
|
147
|
+
@collection = collection
|
148
|
+
@number = number
|
149
|
+
super()
|
150
|
+
end
|
151
|
+
|
152
|
+
def path
|
153
|
+
collection.path
|
154
|
+
end
|
155
|
+
|
156
|
+
def record_class
|
157
|
+
collection.record_class
|
158
|
+
end
|
159
|
+
|
160
|
+
def prepare_request(request)
|
161
|
+
request.params['limit'] = 200
|
162
|
+
request.params['page'] = @number
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
class Record
|
167
|
+
undef_method(:id) if method_defined?(:id)
|
168
|
+
|
169
|
+
attr_reader :attributes
|
170
|
+
|
171
|
+
def initialize(attributes)
|
172
|
+
self.attributes = attributes
|
173
|
+
end
|
174
|
+
|
175
|
+
def attributes=(attributes)
|
176
|
+
@attributes = Dillo.underscore(attributes)
|
177
|
+
end
|
178
|
+
|
179
|
+
def respond_to?(method)
|
180
|
+
@attributes.has_key?(method.to_s) || super
|
181
|
+
end
|
182
|
+
|
183
|
+
def method_missing(method, *args, &block)
|
184
|
+
if respond_to?(method)
|
185
|
+
@attributes[method.to_s]
|
186
|
+
else
|
187
|
+
super
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def inspect
|
192
|
+
"#<#{self.class.name}:#{id}>"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
class Views < PaginatedCollection
|
197
|
+
def path
|
198
|
+
"/api/views"
|
199
|
+
end
|
200
|
+
|
201
|
+
def record_class
|
202
|
+
View
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
class View < Record
|
207
|
+
def columns
|
208
|
+
@columns ||= Columns.new(id)
|
209
|
+
end
|
210
|
+
|
211
|
+
def rows
|
212
|
+
@rows ||= Rows.new(id)
|
213
|
+
end
|
214
|
+
|
215
|
+
def inspect
|
216
|
+
super[0..-2] + " #{name.inspect}>"
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
class Columns < Collection
|
221
|
+
def initialize(id)
|
222
|
+
@id = id
|
223
|
+
super()
|
224
|
+
end
|
225
|
+
|
226
|
+
def path
|
227
|
+
"/api/views/#{@id}/columns.json"
|
228
|
+
end
|
229
|
+
|
230
|
+
def record_class
|
231
|
+
Column
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
class Column < Record
|
236
|
+
def inspect
|
237
|
+
"#<#{self.class.name}:#{id} #{name.inspect}>"
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
class Rows < Collection
|
242
|
+
def initialize(id)
|
243
|
+
@id = id
|
244
|
+
super()
|
245
|
+
end
|
246
|
+
|
247
|
+
def path
|
248
|
+
"/api/views/#{@id}/rows.json"
|
249
|
+
end
|
250
|
+
|
251
|
+
def record_class
|
252
|
+
Row
|
253
|
+
end
|
254
|
+
|
255
|
+
def record_attributes
|
256
|
+
json['data']
|
257
|
+
end
|
258
|
+
|
259
|
+
def columns
|
260
|
+
@columns ||= json['meta']['view']['columns'].map { |column| column['name'].downcase }
|
261
|
+
end
|
262
|
+
|
263
|
+
def column_index(name)
|
264
|
+
columns.index(name.to_s.downcase)
|
265
|
+
end
|
266
|
+
|
267
|
+
def create_record(attributes)
|
268
|
+
record_class.new(self, attributes)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
class Row
|
273
|
+
undef_method(:id) if method_defined?(:id)
|
274
|
+
|
275
|
+
attr_reader :collection, :values
|
276
|
+
|
277
|
+
def initialize(collection, values)
|
278
|
+
@collection = collection
|
279
|
+
@values = values
|
280
|
+
end
|
281
|
+
|
282
|
+
def [](index_or_name)
|
283
|
+
if index_or_name.is_a?(Integer)
|
284
|
+
values[index_or_name]
|
285
|
+
elsif index = collection.column_index(index_or_name)
|
286
|
+
values[index]
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def method_missing(method, *args, &block)
|
291
|
+
name = method.to_s.gsub('_', ' ')
|
292
|
+
if collection.column_index(name)
|
293
|
+
self[name]
|
294
|
+
else
|
295
|
+
super
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def length
|
300
|
+
values.length
|
301
|
+
end
|
302
|
+
|
303
|
+
def to_a
|
304
|
+
values
|
305
|
+
end
|
306
|
+
|
307
|
+
def inspect
|
308
|
+
"#<#{self.class.name} #{values.inspect}>"
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
def self.views
|
313
|
+
@views ||= Views.new
|
314
|
+
end
|
315
|
+
|
316
|
+
def self.underscore(hash)
|
317
|
+
Hash.new.tap do |result|
|
318
|
+
hash.each do |original_key, value|
|
319
|
+
new_key = original_key.gsub(/([a-z])([A-Z])/) { "#{$1}_#{$2.downcase}" }
|
320
|
+
result[new_key] = value.is_a?(Hash) ? underscore(value) : value
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dillo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jamie Phillips
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-19 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: faraday_middleware
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0.8'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.8'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: multi_json
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '1.0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '1.0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.6'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.6'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: simplecov
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0.4'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0.4'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: webmock
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '1.7'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '1.7'
|
94
|
+
description: Dillo is a Ruby module that allows you to easily interact with the City
|
95
|
+
of Austin's Data Portal.
|
96
|
+
email:
|
97
|
+
- jamie.phillips@austintexas.gov
|
98
|
+
executables: []
|
99
|
+
extensions: []
|
100
|
+
extra_rdoc_files: []
|
101
|
+
files:
|
102
|
+
- README.md
|
103
|
+
- lib/dillo.rb
|
104
|
+
homepage: http://github.com/phillipsj/dillo
|
105
|
+
licenses: []
|
106
|
+
post_install_message:
|
107
|
+
rdoc_options: []
|
108
|
+
require_paths:
|
109
|
+
- lib
|
110
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
111
|
+
none: false
|
112
|
+
requirements:
|
113
|
+
- - ! '>='
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ! '>='
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
requirements: []
|
123
|
+
rubyforge_project:
|
124
|
+
rubygems_version: 1.8.24
|
125
|
+
signing_key:
|
126
|
+
specification_version: 3
|
127
|
+
summary: Ruby interface to the City of Austin's Data Portal API
|
128
|
+
test_files: []
|