windy 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +73 -0
- data/lib/windy.rb +287 -0
- metadata +74 -0
data/README.md
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
Windy
|
2
|
+
====================
|
3
|
+
|
4
|
+
Windy is a Ruby module that allows you to easily interact with the [City of Chicago's Data Portal](http://data.cityofchicago.org/).
|
5
|
+
|
6
|
+
The city's datasets are hosted using the [Socrata](http://www.socrata.com/) software platform. This library serves as a convenience wrapper to interacting with [Socrata's Open Data API](http://dev.socrata.com/).
|
7
|
+
|
8
|
+
How to use
|
9
|
+
--------------------
|
10
|
+
|
11
|
+
Before we begin, it should be noted that there are several ways to interact with the City of Chicago's datasets. You can download the data, play with it using the Visualize or Filter functions on the city's website or query the datasets live using Socrata's API. Windy was created to assist with the third type of interaction: querying the live datasets.
|
12
|
+
|
13
|
+
We'll walk through a few steps below, and show you what Windy can do.
|
14
|
+
|
15
|
+
First off, install the Windy gem, and fire up your irb console.
|
16
|
+
|
17
|
+
$ gem install windy
|
18
|
+
$ irb
|
19
|
+
>> require 'rubygems'
|
20
|
+
>> require 'windy'
|
21
|
+
|
22
|
+
### Views
|
23
|
+
|
24
|
+
Views are at the heart of each dataset. In a nutshell, every dataset has at least one view, the original view. Users of the Data Portal are allowed to create customized views, however for the sake of this walk-through, we'll only be discussing the original view.
|
25
|
+
|
26
|
+
To get an Array of all views:
|
27
|
+
|
28
|
+
>> all_views = Windy.views
|
29
|
+
>> all_views.count
|
30
|
+
=> 537
|
31
|
+
>> first_view = views.first
|
32
|
+
|
33
|
+
If you know which view you'd like to work with, you can access it directly by the View ID. In the following example, we found the the View's ID by looking at the end of the [Towed Vehicle's dataset URL](http://data.cityofchicago.org/Government/Towed-Vehicles/ygr5-vcbg).
|
34
|
+
|
35
|
+
>> towed_vehicles = Windy.views.find_by_id("ygr5-vcbg")
|
36
|
+
|
37
|
+
Or, if you'd like to search for a view by name:
|
38
|
+
|
39
|
+
>> bike_racks = Windy.views.find_by_name("Bike Racks")
|
40
|
+
|
41
|
+
### Rows
|
42
|
+
|
43
|
+
Much like a spreadsheet, each view contains a number of rows. These, too , can be accessed using Windy.
|
44
|
+
|
45
|
+
>> racks = bike_racks.rows
|
46
|
+
|
47
|
+
We can see which columns exists for these rows.
|
48
|
+
|
49
|
+
>> racks.columns
|
50
|
+
=> ["sid", "id", "position", "created_at", "created_meta", "updated_at", "updated_meta", "meta", "rackid", "address", "ward", "community area", "community name", "totinstall", "latitude", "longitude", "historical", "f12", "f13", "location", "tags"]
|
51
|
+
|
52
|
+
We can search for rows based on column names. Just use the convenient "find_by_columnname" method:
|
53
|
+
|
54
|
+
>> first_ward_racks = racks.find_all_by_ward("1")
|
55
|
+
>> lake_view_racks = racks.find_all_by_community_name("Lake View")
|
56
|
+
|
57
|
+
And, find values for a specific row's cell.
|
58
|
+
|
59
|
+
>> first_rack = lake_view_racks.first
|
60
|
+
>> first_rack.address
|
61
|
+
=> "1000 W Addison St"
|
62
|
+
|
63
|
+
Authors
|
64
|
+
--------------------
|
65
|
+
|
66
|
+
* Sam Stephenson <<sstephenson@gmail.com>>
|
67
|
+
* Scott Robbin <<srobbin@gmail.com>>
|
68
|
+
|
69
|
+
<a rel="license" href="http://creativecommons.org/publicdomain/zero/1.0/">
|
70
|
+
<img src="http://i.creativecommons.org/p/zero/1.0/88x31.png" style="border-style: none;" alt="CC0" />
|
71
|
+
</a>
|
72
|
+
|
73
|
+
To the extent possible under law, the authors have waived all copyright and related or neighboring rights to this work.
|
data/lib/windy.rb
ADDED
@@ -0,0 +1,287 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'multi_json'
|
3
|
+
|
4
|
+
module Windy
|
5
|
+
VERSION = '0.1.0'
|
6
|
+
|
7
|
+
class Base
|
8
|
+
def self.root
|
9
|
+
"http://data.cityofchicago.org"
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :connection
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@connection = Faraday.new(:url => self.class.root)
|
16
|
+
end
|
17
|
+
|
18
|
+
def body
|
19
|
+
@body ||= connection.get(path) do |request|
|
20
|
+
prepare_request(request)
|
21
|
+
# warn "Fetching #{request.to_env(@connection)[:url]}"
|
22
|
+
end.body
|
23
|
+
end
|
24
|
+
|
25
|
+
def prepare_request(request)
|
26
|
+
end
|
27
|
+
|
28
|
+
def json
|
29
|
+
@json ||= MultiJson.decode(body)
|
30
|
+
end
|
31
|
+
|
32
|
+
def inspect
|
33
|
+
"#<#{self.class.name} #{self.class.root}#{path}>"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module Finders
|
38
|
+
def [](index)
|
39
|
+
to_a[index]
|
40
|
+
end
|
41
|
+
|
42
|
+
def respond_to?(method)
|
43
|
+
method.to_s[/^find(_all)?_by_./] || super
|
44
|
+
end
|
45
|
+
|
46
|
+
def method_missing(method, *args, &block)
|
47
|
+
if attribute = method.to_s[/^(find(_all)?)_by_(.+)$/, 3]
|
48
|
+
value = args.first
|
49
|
+
send($1) { |record| record.send(attribute) == value }
|
50
|
+
else
|
51
|
+
super
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class PaginatedCollection < Base
|
57
|
+
include Enumerable, Finders
|
58
|
+
|
59
|
+
def initialize
|
60
|
+
@pages ||= {}
|
61
|
+
end
|
62
|
+
|
63
|
+
def page(number)
|
64
|
+
@pages[number] ||= Page.new(self, number)
|
65
|
+
end
|
66
|
+
|
67
|
+
def each_page
|
68
|
+
number = 1
|
69
|
+
loop do
|
70
|
+
page = self.page(number)
|
71
|
+
break if page.count.zero?
|
72
|
+
yield page
|
73
|
+
number += 1
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def each(&block)
|
78
|
+
each_page do |page|
|
79
|
+
page.each(&block)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class Collection < Base
|
85
|
+
include Enumerable, Finders
|
86
|
+
|
87
|
+
def each(&block)
|
88
|
+
records.each(&block)
|
89
|
+
end
|
90
|
+
|
91
|
+
def record_attributes
|
92
|
+
json
|
93
|
+
end
|
94
|
+
|
95
|
+
def records
|
96
|
+
@records ||= record_attributes.map do |attributes|
|
97
|
+
create_record(attributes)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def create_record(attributes)
|
102
|
+
record_class.new(attributes)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class Page < Collection
|
107
|
+
attr_accessor :collection, :number
|
108
|
+
|
109
|
+
def initialize(collection, number)
|
110
|
+
@collection = collection
|
111
|
+
@number = number
|
112
|
+
super()
|
113
|
+
end
|
114
|
+
|
115
|
+
def path
|
116
|
+
collection.path
|
117
|
+
end
|
118
|
+
|
119
|
+
def record_class
|
120
|
+
collection.record_class
|
121
|
+
end
|
122
|
+
|
123
|
+
def prepare_request(request)
|
124
|
+
request.params['limit'] = 200
|
125
|
+
request.params['page'] = @number
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class Record
|
130
|
+
undef_method(:id) if method_defined?(:id)
|
131
|
+
|
132
|
+
attr_reader :attributes
|
133
|
+
|
134
|
+
def initialize(attributes)
|
135
|
+
self.attributes = attributes
|
136
|
+
end
|
137
|
+
|
138
|
+
def attributes=(attributes)
|
139
|
+
@attributes = Windy.underscore(attributes)
|
140
|
+
end
|
141
|
+
|
142
|
+
def respond_to?(method)
|
143
|
+
@attributes.has_key?(method.to_s) || super
|
144
|
+
end
|
145
|
+
|
146
|
+
def method_missing(method, *args, &block)
|
147
|
+
if respond_to?(method)
|
148
|
+
@attributes[method.to_s]
|
149
|
+
else
|
150
|
+
super
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def inspect
|
155
|
+
"#<#{self.class.name}:#{id}>"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
class Views < PaginatedCollection
|
160
|
+
def path
|
161
|
+
"/api/views"
|
162
|
+
end
|
163
|
+
|
164
|
+
def record_class
|
165
|
+
View
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
class View < Record
|
170
|
+
def columns
|
171
|
+
@columns ||= Columns.new(id)
|
172
|
+
end
|
173
|
+
|
174
|
+
def rows
|
175
|
+
@rows ||= Rows.new(id)
|
176
|
+
end
|
177
|
+
|
178
|
+
def inspect
|
179
|
+
super[0..-2] + " #{name.inspect}>"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
class Columns < Collection
|
184
|
+
def initialize(id)
|
185
|
+
@id = id
|
186
|
+
super()
|
187
|
+
end
|
188
|
+
|
189
|
+
def path
|
190
|
+
"/api/views/#{@id}/columns.json"
|
191
|
+
end
|
192
|
+
|
193
|
+
def record_class
|
194
|
+
Column
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
class Column < Record
|
199
|
+
def inspect
|
200
|
+
"#<#{self.class.name}:#{id} #{name.inspect}>"
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
class Rows < Collection
|
205
|
+
def initialize(id)
|
206
|
+
@id = id
|
207
|
+
super()
|
208
|
+
end
|
209
|
+
|
210
|
+
def path
|
211
|
+
"/api/views/#{@id}/rows.json"
|
212
|
+
end
|
213
|
+
|
214
|
+
def record_class
|
215
|
+
Row
|
216
|
+
end
|
217
|
+
|
218
|
+
def record_attributes
|
219
|
+
json['data']
|
220
|
+
end
|
221
|
+
|
222
|
+
def columns
|
223
|
+
@columns ||= json['meta']['view']['columns'].map { |column| column['name'].downcase }
|
224
|
+
end
|
225
|
+
|
226
|
+
def column_index(name)
|
227
|
+
columns.index(name.to_s.downcase)
|
228
|
+
end
|
229
|
+
|
230
|
+
def create_record(attributes)
|
231
|
+
record_class.new(self, attributes)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
class Row
|
236
|
+
undef_method(:id) if method_defined?(:id)
|
237
|
+
|
238
|
+
attr_reader :collection, :values
|
239
|
+
|
240
|
+
def initialize(collection, values)
|
241
|
+
@collection = collection
|
242
|
+
@values = values
|
243
|
+
end
|
244
|
+
|
245
|
+
def [](index_or_name)
|
246
|
+
if index_or_name.is_a?(Integer)
|
247
|
+
values[index_or_name]
|
248
|
+
elsif index = collection.column_index(index_or_name)
|
249
|
+
values[index]
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def method_missing(method, *args, &block)
|
254
|
+
name = method.to_s.gsub('_', ' ')
|
255
|
+
if collection.column_index(name)
|
256
|
+
self[name]
|
257
|
+
else
|
258
|
+
super
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def length
|
263
|
+
values.length
|
264
|
+
end
|
265
|
+
|
266
|
+
def to_a
|
267
|
+
values
|
268
|
+
end
|
269
|
+
|
270
|
+
def inspect
|
271
|
+
"#<#{self.class.name} #{values.inspect}>"
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def self.views
|
276
|
+
@views ||= Views.new
|
277
|
+
end
|
278
|
+
|
279
|
+
def self.underscore(hash)
|
280
|
+
Hash.new.tap do |result|
|
281
|
+
hash.each do |original_key, value|
|
282
|
+
new_key = original_key.gsub(/([a-z])([A-Z])/) { "#{$1}_#{$2.downcase}" }
|
283
|
+
result[new_key] = value.is_a?(Hash) ? underscore(value) : value
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: windy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Sam Stephenson
|
9
|
+
- Scott Robbin
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2011-07-16 00:00:00.000000000 -05:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: faraday
|
18
|
+
requirement: &70118986024880 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: '0.7'
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: *70118986024880
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: multi_json
|
29
|
+
requirement: &70118986023900 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '1.0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: *70118986023900
|
38
|
+
description: Windy is a Ruby module that allows you to easily interact with the City
|
39
|
+
of Chicago's Data Portal.
|
40
|
+
email:
|
41
|
+
- sstephenson@gmail.com
|
42
|
+
- srobbin@gmail.com
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- README.md
|
48
|
+
- lib/windy.rb
|
49
|
+
has_rdoc: true
|
50
|
+
homepage: http://github.com/chicago/windy
|
51
|
+
licenses: []
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
requirements: []
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.6.2
|
71
|
+
signing_key:
|
72
|
+
specification_version: 3
|
73
|
+
summary: Ruby interface to the City of Chicago's Data Portal API
|
74
|
+
test_files: []
|