windy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README.md +73 -0
  2. data/lib/windy.rb +287 -0
  3. metadata +74 -0
@@ -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.
@@ -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: []