mocrata 0.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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.yardopts +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +61 -0
- data/Rakefile +1 -0
- data/lib/mocrata/configuration.rb +37 -0
- data/lib/mocrata/dataset.rb +131 -0
- data/lib/mocrata/dataset_url.rb +84 -0
- data/lib/mocrata/request.rb +94 -0
- data/lib/mocrata/response.rb +84 -0
- data/lib/mocrata/version.rb +5 -0
- data/lib/mocrata.rb +56 -0
- data/mocrata.gemspec +28 -0
- data/spec/lib/mocrata/configuration_spec.rb +28 -0
- data/spec/lib/mocrata/dataset_spec.rb +134 -0
- data/spec/lib/mocrata/dataset_url_spec.rb +77 -0
- data/spec/lib/mocrata/request_spec.rb +125 -0
- data/spec/lib/mocrata/response_spec.rb +132 -0
- data/spec/lib/mocrata_spec.rb +31 -0
- data/spec/spec_helper.rb +12 -0
- metadata +143 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6ea5004761040d9a269f778003915705495aeb80
|
4
|
+
data.tar.gz: 29f11a520caf96642656e6e0268b643f3d284cae
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d887cafd67ad5669aa6560c169eba34464db314df0763ebbc5dd697b22bf2b32d5f0312dc192d8323e9630cea1d1b03c420f33934fa08a855dab19792639820f
|
7
|
+
data.tar.gz: 6ac06af00a4d45b148edd57adabf239f2d26e653f1ded892f9b36acd82ad99d94631858f3d7a6d6d20e6593880ff83120b1f27d2a917dcac8e5322b4169b1bc3
|
data/.gitignore
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup=markdown
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Heather Rivers
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# Mocrata
|
2
|
+
|
3
|
+
Mocrata is a [SODA](http://dev.socrata.com/) (Socrata Open Data API) client
|
4
|
+
developed by [Mode Analytics](https://modeanalytics.com).
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'mocrata'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install mocrata
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
### Setup
|
23
|
+
|
24
|
+
```
|
25
|
+
Mocrata.configure do |config|
|
26
|
+
config.app_token = 'yourtoken' # optional Socrata application token
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
### Accessing data
|
31
|
+
|
32
|
+
```
|
33
|
+
dataset = Mocrata::Dataset.new('http://soda.demo.socrata.com/resource/6xzm-fzcu')
|
34
|
+
|
35
|
+
dataset.csv
|
36
|
+
=> [["Sally", 10], ["Earl", 2]]
|
37
|
+
|
38
|
+
dataset.json
|
39
|
+
=> [{"name"=>"Sally", "age"=>10}, {"name"=>"Earl", "age"=>2}]
|
40
|
+
|
41
|
+
dataset.fields
|
42
|
+
=> {"name"=>"text", "age"=>"number"}
|
43
|
+
```
|
44
|
+
|
45
|
+
### Iterating through rows
|
46
|
+
|
47
|
+
```
|
48
|
+
dataset.each_row(:csv) do |row|
|
49
|
+
# do something with the row
|
50
|
+
end
|
51
|
+
|
52
|
+
dataset.each_row(:json) { |row| ... }
|
53
|
+
```
|
54
|
+
|
55
|
+
## Contributing
|
56
|
+
|
57
|
+
1. Fork it
|
58
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
59
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
60
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
61
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
module Mocrata
|
4
|
+
# @attr [String] app_token A Socrata application token
|
5
|
+
#
|
6
|
+
class Configuration
|
7
|
+
# The maximum number of rows allowed per request by Socrata
|
8
|
+
MAX_PER_PAGE = 1000
|
9
|
+
|
10
|
+
attr_accessor :app_token
|
11
|
+
|
12
|
+
# @return [Integer] the value of the `per_page` configuration option
|
13
|
+
#
|
14
|
+
def per_page
|
15
|
+
@per_page ||= MAX_PER_PAGE
|
16
|
+
end
|
17
|
+
|
18
|
+
# Sets the value of the `per_page` configuration option
|
19
|
+
#
|
20
|
+
# @param value [Integer] the number of results per page {http://dev.socrata.com SODA} resource url
|
21
|
+
#
|
22
|
+
# @return [Integer] the value
|
23
|
+
#
|
24
|
+
# @raise [Mocrata::Configuration::ConfigurationError] if the value is invalid
|
25
|
+
#
|
26
|
+
def per_page=(value)
|
27
|
+
if value > MAX_PER_PAGE
|
28
|
+
message = "Per page #{value} exceeds maximum value of #{MAX_PER_PAGE}"
|
29
|
+
raise ConfigurationError.new(message)
|
30
|
+
end
|
31
|
+
|
32
|
+
@per_page = value
|
33
|
+
end
|
34
|
+
|
35
|
+
class ConfigurationError < StandardError; end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
module Mocrata
|
4
|
+
# A Mocrata::Dataset instance represents a SODA dataset and provides
|
5
|
+
# interfaces for reading its metadata and contents in supported formats.
|
6
|
+
#
|
7
|
+
class Dataset
|
8
|
+
# Construct a new Dataset instance
|
9
|
+
#
|
10
|
+
# @param url [String] valid {http://dev.socrata.com SODA} resource url
|
11
|
+
#
|
12
|
+
# @return [Mocrata::Dataset] the instance
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# dataset = Mocrata::Dataset.new('http://data.sfgov.org/resource/funx-qxxn')
|
16
|
+
#
|
17
|
+
def initialize(url)
|
18
|
+
@url = url
|
19
|
+
end
|
20
|
+
|
21
|
+
# Iterate through each row of the dataset
|
22
|
+
#
|
23
|
+
# @param format [Symbol, String] the format, `:json` or `:csv`
|
24
|
+
#
|
25
|
+
# @yield [Array<Array>] row of values
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# dataset.each_row(:json) do |row|
|
29
|
+
# # do something with the row
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
def each_row(format, &block)
|
33
|
+
each_page(format) do |page|
|
34
|
+
page.each(&block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Iterate through each page of the dataset
|
39
|
+
#
|
40
|
+
# @param format [Symbol, String] the format, `:json` or `:csv`
|
41
|
+
# @param per_page [optional, Integer] the number of rows to return for each page
|
42
|
+
#
|
43
|
+
# @yield [Array<Array>] page of rows
|
44
|
+
#
|
45
|
+
# @example
|
46
|
+
# dataset.each_page(:csv) do |page|
|
47
|
+
# # do something with the page
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
def each_page(format, per_page = nil, &block)
|
51
|
+
page = 1
|
52
|
+
per_page ||= Mocrata.config.per_page
|
53
|
+
|
54
|
+
while true
|
55
|
+
rows = send(format, :page => page, :per_page => per_page)
|
56
|
+
yield rows
|
57
|
+
break if rows.size < per_page
|
58
|
+
page += 1
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# The contents of the dataset in CSV format
|
63
|
+
#
|
64
|
+
# @param params [optional, Hash] hash of options to pass along to the HTTP request
|
65
|
+
#
|
66
|
+
# @option params [Integer] :page the page to request
|
67
|
+
# @option params [Integer] :per_page the number of rows to return for each page
|
68
|
+
#
|
69
|
+
# @return [Array<Array>] the array of rows
|
70
|
+
#
|
71
|
+
# @example
|
72
|
+
# dataset.csv(:page => 2, :per_page => 10)
|
73
|
+
#
|
74
|
+
def csv(params = {})
|
75
|
+
get(:csv, params).body
|
76
|
+
end
|
77
|
+
|
78
|
+
# The contents of the dataset in JSON format
|
79
|
+
#
|
80
|
+
# @param params [optional, Hash] hash of options to pass along to the HTTP request
|
81
|
+
#
|
82
|
+
# @option params [Integer] :page the page to request
|
83
|
+
# @option params [Integer] :per_page the number of rows to return for each page
|
84
|
+
#
|
85
|
+
# @return [Array<Hash>] the array of rows
|
86
|
+
#
|
87
|
+
# @example
|
88
|
+
# dataset.json(:page => 2, :per_page => 10)
|
89
|
+
#
|
90
|
+
def json(params = {})
|
91
|
+
get(:json, params).body
|
92
|
+
end
|
93
|
+
|
94
|
+
# Get the headers associated with the dataset
|
95
|
+
#
|
96
|
+
# @return [Hash] a hash of headers
|
97
|
+
#
|
98
|
+
def headers
|
99
|
+
# SODA doesn't support HEAD requests, unfortunately
|
100
|
+
@headers ||= get(:json, :per_page => 0).headers
|
101
|
+
end
|
102
|
+
|
103
|
+
# A hash of field names and types from headers
|
104
|
+
#
|
105
|
+
# @return [Hash] a hash of field names and types
|
106
|
+
#
|
107
|
+
def fields
|
108
|
+
Hash[field_names.zip(field_types)]
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
attr_reader :url
|
114
|
+
|
115
|
+
def get(format, params = {})
|
116
|
+
Mocrata::Request.new(base_url, format, params).response
|
117
|
+
end
|
118
|
+
|
119
|
+
def base_url
|
120
|
+
@base_url ||= Mocrata::DatasetUrl.new(url).normalize
|
121
|
+
end
|
122
|
+
|
123
|
+
def field_names
|
124
|
+
headers.fetch('x-soda2-fields', [])
|
125
|
+
end
|
126
|
+
|
127
|
+
def field_types
|
128
|
+
headers.fetch('x-soda2-types', [])
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
module Mocrata
|
4
|
+
# @attr_reader [String] original the original Socrata dataset URL
|
5
|
+
#
|
6
|
+
class DatasetUrl
|
7
|
+
attr_reader :original
|
8
|
+
|
9
|
+
# Construct a new DatasetUrl instance
|
10
|
+
#
|
11
|
+
# @param original [String] the original Socrata dataset URL
|
12
|
+
#
|
13
|
+
# @return [Mocrata::DatasetUrl] the instance
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# url = Mocrata::DatasetUrl.new('http://data.sfgov.org/resource/funx-qxxn')
|
17
|
+
#
|
18
|
+
def initialize(original)
|
19
|
+
@original = original
|
20
|
+
end
|
21
|
+
|
22
|
+
# Normalize a Socrata dataset URL. Ensures https protocol. Removes query
|
23
|
+
# string and fragment, if any.
|
24
|
+
#
|
25
|
+
# @return [String] the normalized URL
|
26
|
+
#
|
27
|
+
def normalize
|
28
|
+
uri = URI(self.class.ensure_protocol(original))
|
29
|
+
|
30
|
+
uri.scheme = 'https'
|
31
|
+
uri.fragment = nil
|
32
|
+
uri.query = nil
|
33
|
+
|
34
|
+
self.class.strip_format(uri.to_s)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Validate the original URL against the expected Socrata dataset URL
|
38
|
+
# pattern
|
39
|
+
#
|
40
|
+
# @raise [Mocrata::DatasetUrl::InvalidError] if the URL is invalid
|
41
|
+
#
|
42
|
+
def validate!
|
43
|
+
unless original =~ VALID_PATTERN
|
44
|
+
raise InvalidError.new("Invalid URL: #{original.inspect}")
|
45
|
+
end
|
46
|
+
|
47
|
+
true
|
48
|
+
end
|
49
|
+
|
50
|
+
class << self
|
51
|
+
# Ensure that a URL has a valid protocol
|
52
|
+
#
|
53
|
+
# @param url [String] the url with or without protocol
|
54
|
+
#
|
55
|
+
# @return [String] the url with protocol
|
56
|
+
#
|
57
|
+
def ensure_protocol(url)
|
58
|
+
if url =~ /\A\/\//
|
59
|
+
url = "https:#{url}"
|
60
|
+
elsif url !~ /\Ahttps?:\/\//
|
61
|
+
url = "https://#{url}"
|
62
|
+
end
|
63
|
+
|
64
|
+
url
|
65
|
+
end
|
66
|
+
|
67
|
+
# Strip explicit format from a given URL if present
|
68
|
+
#
|
69
|
+
# @param url [String] the url with or without format
|
70
|
+
#
|
71
|
+
# @return [String] the url without format
|
72
|
+
#
|
73
|
+
def strip_format(url)
|
74
|
+
url.gsub(/\.[a-zA-Z]+\Z/, '')
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
VALID_PATTERN = /\/resource\//
|
81
|
+
|
82
|
+
class InvalidError < StandardError; end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
require 'cgi'
|
4
|
+
require 'csv'
|
5
|
+
require 'json'
|
6
|
+
require 'net/https'
|
7
|
+
|
8
|
+
module Mocrata
|
9
|
+
# @attr_reader [String] url the request URL
|
10
|
+
# @attr_reader [Symbol] format the request format, `:json` or `:csv`
|
11
|
+
# @attr_reader [Hash] params the requst params
|
12
|
+
#
|
13
|
+
class Request
|
14
|
+
attr_reader :url, :format, :params
|
15
|
+
|
16
|
+
# Construct a new Request instance
|
17
|
+
#
|
18
|
+
# @param url [String] the request URL
|
19
|
+
# @param format [Symbol] the request format, `:json` or `:csv`
|
20
|
+
# @param params [Hash] the requst params
|
21
|
+
#
|
22
|
+
# @return [Mocrata::Request] the instance
|
23
|
+
#
|
24
|
+
def initialize(url, format, params = {})
|
25
|
+
@url = url
|
26
|
+
@format = format
|
27
|
+
@params = params
|
28
|
+
end
|
29
|
+
|
30
|
+
# Perform the HTTP GET request
|
31
|
+
#
|
32
|
+
# @return [Mocrata::Response] the validated response
|
33
|
+
#
|
34
|
+
def response
|
35
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
36
|
+
|
37
|
+
request.add_field('Accept', content_type)
|
38
|
+
request.add_field('X-App-Token', Mocrata.config.app_token)
|
39
|
+
|
40
|
+
response = http.request(request)
|
41
|
+
|
42
|
+
Mocrata::Response.new(response).tap(&:validate!)
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [String] the content type for the specified format
|
46
|
+
#
|
47
|
+
# @raise [Mocrata::Request::RequestError] if the format is not supported
|
48
|
+
#
|
49
|
+
def content_type
|
50
|
+
Mocrata::CONTENT_TYPES.fetch(format, nil).tap do |type|
|
51
|
+
raise RequestError.new("Invalid format: #{format}") unless type
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def http
|
58
|
+
@http ||= Net::HTTP.new(uri.host, uri.port).tap do |http|
|
59
|
+
http.use_ssl = true
|
60
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def soda_params
|
65
|
+
@soda_params ||= {}.tap do |soda|
|
66
|
+
limit = params.fetch(:per_page, Mocrata.config.per_page)
|
67
|
+
page = params.fetch(:page, 1)
|
68
|
+
|
69
|
+
soda[:$limit] = limit
|
70
|
+
soda[:$offset] = (page - 1) * limit
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def uri
|
75
|
+
@uri ||= URI(url).dup.tap do |uri|
|
76
|
+
uri.query = self.class.query_string(soda_params)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class << self
|
81
|
+
# Construct a query string from a hash
|
82
|
+
#
|
83
|
+
# @param hash [Hash] the hash of parmas
|
84
|
+
#
|
85
|
+
# @return [String] the query string
|
86
|
+
#
|
87
|
+
def query_string(hash)
|
88
|
+
hash.map { |k, v| "#{k}=#{CGI::escape(v.to_s)}" }.join('&')
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class RequestError < StandardError; end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
require 'json'
|
4
|
+
require 'csv'
|
5
|
+
|
6
|
+
module Mocrata
|
7
|
+
class Response
|
8
|
+
# Construct a new Response instance
|
9
|
+
#
|
10
|
+
# @param http_response [Net::HTTPResponse] the http response
|
11
|
+
#
|
12
|
+
# @return [Mocrata::Response] the instance
|
13
|
+
#
|
14
|
+
def initialize(http_response)
|
15
|
+
@http_response = http_response
|
16
|
+
end
|
17
|
+
|
18
|
+
# Perform certain checks against the HTTP response and raise an exception
|
19
|
+
# if necessary
|
20
|
+
#
|
21
|
+
# @return [true]
|
22
|
+
#
|
23
|
+
# @raise [Mocrata::Response::ResponseError] if the response is invalid
|
24
|
+
#
|
25
|
+
def validate!
|
26
|
+
if content_type == :json
|
27
|
+
if body.respond_to?(:has_key?) && body.has_key?('error')
|
28
|
+
raise ResponseError.new("API error: #{body['message']}")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
# HTTP headers with certain values parsed as JSON
|
36
|
+
#
|
37
|
+
# @return [Hash] the header keys and values
|
38
|
+
#
|
39
|
+
def headers
|
40
|
+
@headers ||= {}.tap do |result|
|
41
|
+
http_response.each_header do |key, value|
|
42
|
+
value = JSON.parse(value) if JSON_HEADERS.include?(key)
|
43
|
+
|
44
|
+
result[key] = value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# The HTTP response body, processed according to content type
|
50
|
+
#
|
51
|
+
# @return [Array] the parsed body
|
52
|
+
#
|
53
|
+
def body
|
54
|
+
send(content_type)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
# SODA headers that are always encoded as JSON
|
60
|
+
JSON_HEADERS = %w(x-soda2-fields x-soda2-types)
|
61
|
+
|
62
|
+
attr_reader :http_response
|
63
|
+
|
64
|
+
def content_type
|
65
|
+
type = headers['content-type']
|
66
|
+
|
67
|
+
CONTENT_TYPES.each do |key, value|
|
68
|
+
return key if type && type.start_with?(value)
|
69
|
+
end
|
70
|
+
|
71
|
+
raise ResponseError.new("Unexpected content type: #{type}")
|
72
|
+
end
|
73
|
+
|
74
|
+
def csv
|
75
|
+
CSV.parse(http_response.body)[1..-1] # exclude header
|
76
|
+
end
|
77
|
+
|
78
|
+
def json
|
79
|
+
JSON.parse(http_response.body)
|
80
|
+
end
|
81
|
+
|
82
|
+
class ResponseError < StandardError; end
|
83
|
+
end
|
84
|
+
end
|
data/lib/mocrata.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# Mocrata is a [SODA](http://dev.socrata.com/) (Socrata Open Data API) client
|
4
|
+
# developed by [Mode Analytics](https://modeanalytics.com).
|
5
|
+
#
|
6
|
+
module Mocrata
|
7
|
+
# Supported Socrata content types
|
8
|
+
#
|
9
|
+
# @see http://dev.socrata.com/docs/formats/ Socrata format documentation
|
10
|
+
#
|
11
|
+
# @todo Add support for application/rdf+xml
|
12
|
+
#
|
13
|
+
CONTENT_TYPES = {
|
14
|
+
:json => 'application/json',
|
15
|
+
:csv => 'text/csv'
|
16
|
+
}
|
17
|
+
|
18
|
+
class << self
|
19
|
+
# Set Mocrata configuration values
|
20
|
+
#
|
21
|
+
# @yield [Mocrata::Configuration] the configuration instance
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# Mocrata.configure do |config|
|
25
|
+
# config.app_token = 'yourtoken'
|
26
|
+
# config.per_page = 1000
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
def configure(&block)
|
30
|
+
yield config
|
31
|
+
end
|
32
|
+
|
33
|
+
# The Mocrata configuration instance
|
34
|
+
#
|
35
|
+
# @return [Mocrata::Configuration] the configuration instance
|
36
|
+
#
|
37
|
+
def config
|
38
|
+
@config ||= Mocrata::Configuration.new
|
39
|
+
end
|
40
|
+
|
41
|
+
# Remove Mocrata configuration instance variable
|
42
|
+
#
|
43
|
+
def reset
|
44
|
+
if instance_variable_defined?(:@config)
|
45
|
+
remove_instance_variable(:@config)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
require 'mocrata/configuration'
|
52
|
+
require 'mocrata/dataset'
|
53
|
+
require 'mocrata/dataset_url'
|
54
|
+
require 'mocrata/request'
|
55
|
+
require 'mocrata/response'
|
56
|
+
require 'mocrata/version'
|
data/mocrata.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'mocrata/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "mocrata"
|
8
|
+
spec.version = Mocrata::VERSION
|
9
|
+
spec.authors = ["Heather Rivers"]
|
10
|
+
spec.email = ["heather@modeanalytics.com"]
|
11
|
+
spec.description = %q{Mode's SODA client}
|
12
|
+
spec.summary = %q{Mocrata is a SODA (Socrata Open Data API) client developed by Mode Analytics}
|
13
|
+
spec.homepage = "https://github.com/mode/mocrata"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
spec.add_development_dependency "simplecov"
|
25
|
+
spec.add_development_dependency "rdoc"
|
26
|
+
|
27
|
+
spec.required_ruby_version = "~> 2.0"
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Mocrata::Configuration do
|
6
|
+
let :config do
|
7
|
+
Mocrata::Configuration.new
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#per_page' do
|
11
|
+
it 'has default value' do
|
12
|
+
expect(config.per_page).to eq(1000)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#per_page=' do
|
17
|
+
it 'overrides default value' do
|
18
|
+
expect(config.per_page).to eq(1000)
|
19
|
+
config.per_page = 50
|
20
|
+
expect(config.per_page).to eq(50)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'raises exception if max value is exceeded' do
|
24
|
+
expect { config.per_page = 1001 }.to raise_error(
|
25
|
+
Mocrata::Configuration::ConfigurationError)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Mocrata::Dataset do
|
6
|
+
let :dataset do
|
7
|
+
Mocrata::Dataset.new('')
|
8
|
+
end
|
9
|
+
|
10
|
+
let :rows do
|
11
|
+
(0..5).map do |i|
|
12
|
+
{ "key_#{i}" => "value_#{i}" }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
let :pages do
|
17
|
+
[rows[0..3], rows[4..5]]
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#each_row' do
|
21
|
+
it 'yields rows' do
|
22
|
+
expect(dataset).to receive(:each_page)
|
23
|
+
.and_yield(pages[0])
|
24
|
+
.and_yield(pages[1])
|
25
|
+
|
26
|
+
expect { |b|
|
27
|
+
dataset.each_row(:json, &b)
|
28
|
+
}.to yield_successive_args(*rows)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#each_page' do
|
33
|
+
it 'yields pages' do
|
34
|
+
expect(dataset).to receive(:json).and_return(*pages)
|
35
|
+
|
36
|
+
expect { |b|
|
37
|
+
dataset.each_page(:json, 4, &b)
|
38
|
+
}.to yield_successive_args(*pages)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#get' do
|
43
|
+
it 'returns response' do
|
44
|
+
dataset = Mocrata::Dataset.new(
|
45
|
+
'https://data.sfgov.org/resource/funx-qxxn')
|
46
|
+
|
47
|
+
response = Mocrata::Response.new(true)
|
48
|
+
|
49
|
+
expect_any_instance_of(Mocrata::Request).to receive(
|
50
|
+
:response).and_return(response)
|
51
|
+
|
52
|
+
expect(dataset.send(:get, :csv)).to eq(response)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#csv' do
|
57
|
+
it 'returns csv body' do
|
58
|
+
response = Mocrata::Response.new(true)
|
59
|
+
|
60
|
+
expect(response).to receive(:body).and_return([])
|
61
|
+
expect(dataset).to receive(:get).and_return(response)
|
62
|
+
|
63
|
+
expect(dataset.csv).to eq([])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '#json' do
|
68
|
+
it 'returns json body' do
|
69
|
+
response = Mocrata::Response.new(true)
|
70
|
+
|
71
|
+
expect(response).to receive(:body).and_return([])
|
72
|
+
expect(dataset).to receive(:get).and_return(response)
|
73
|
+
|
74
|
+
expect(dataset.json).to eq([])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '#headers' do
|
79
|
+
it 'builds headers' do
|
80
|
+
response = Mocrata::Response.new(true)
|
81
|
+
|
82
|
+
expect(response).to receive(:headers).and_return('foo' => 'bar')
|
83
|
+
expect(dataset).to receive(:get).and_return(response)
|
84
|
+
|
85
|
+
expect(dataset.headers).to eq('foo' => 'bar')
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '#fields' do
|
90
|
+
it 'builds empty map' do
|
91
|
+
expect(dataset).to receive(:field_names).and_return([])
|
92
|
+
expect(dataset).to receive(:field_types).and_return([])
|
93
|
+
|
94
|
+
expect(dataset.fields).to eq({})
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'builds map' do
|
98
|
+
expect(dataset).to receive(:field_names).and_return(['key1', 'key2'])
|
99
|
+
expect(dataset).to receive(:field_types).and_return(['val1', 'val2'])
|
100
|
+
|
101
|
+
expect(dataset.fields).to eq('key1' => 'val1', 'key2' => 'val2')
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe '#field_names' do
|
106
|
+
it 'handles missing header' do
|
107
|
+
expect(dataset).to receive(:headers).and_return({})
|
108
|
+
|
109
|
+
expect(dataset.send(:field_names)).to eq([])
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'returns header if present' do
|
113
|
+
expect(dataset).to receive(:headers).and_return(
|
114
|
+
'x-soda2-fields' => ['name1', 'name2'])
|
115
|
+
|
116
|
+
expect(dataset.send(:field_names)).to eq(['name1', 'name2'])
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe '#field_types' do
|
121
|
+
it 'handles missing header' do
|
122
|
+
expect(dataset).to receive(:headers).and_return({})
|
123
|
+
|
124
|
+
expect(dataset.send(:field_types)).to eq([])
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'returns header if present' do
|
128
|
+
expect(dataset).to receive(:headers).and_return(
|
129
|
+
'x-soda2-types' => ['type1', 'type2'])
|
130
|
+
|
131
|
+
expect(dataset.send(:field_types)).to eq(['type1', 'type2'])
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Mocrata::DatasetUrl do
|
6
|
+
describe '#validate!' do
|
7
|
+
it 'returns true for valid url' do
|
8
|
+
url = Mocrata::DatasetUrl.new(
|
9
|
+
'data.sfgov.org/resource/funx-qxxn.csv?limit=100#foo')
|
10
|
+
|
11
|
+
expect(url.validate!).to eq(true)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'raises exception for invalid url' do
|
15
|
+
url = Mocrata::DatasetUrl.new('data.sfgov.org/nope/funx-qxxn.csv')
|
16
|
+
|
17
|
+
expect {
|
18
|
+
url.validate!
|
19
|
+
}.to raise_error(Mocrata::DatasetUrl::InvalidError)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#normalize' do
|
24
|
+
it 'normalizes original url' do
|
25
|
+
url = Mocrata::DatasetUrl.new(
|
26
|
+
'data.sfgov.org/resource/funx-qxxn.csv?limit=100#foo')
|
27
|
+
|
28
|
+
expect(url.normalize).to eq('https://data.sfgov.org/resource/funx-qxxn')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '.ensure_protocol' do
|
33
|
+
it 'adds missing protocol' do
|
34
|
+
url = Mocrata::DatasetUrl.ensure_protocol('data.sfgov.org/')
|
35
|
+
|
36
|
+
expect(url).to eq('https://data.sfgov.org/')
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'adds protocol to schemeless url' do
|
40
|
+
url = Mocrata::DatasetUrl.ensure_protocol('//data.sfgov.org/')
|
41
|
+
|
42
|
+
expect(url).to eq('https://data.sfgov.org/')
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'preserves http protocol' do
|
46
|
+
url = Mocrata::DatasetUrl.ensure_protocol('http://data.sfgov.org/')
|
47
|
+
|
48
|
+
expect(url).to eq('http://data.sfgov.org/')
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'preserves https protocol' do
|
52
|
+
url = Mocrata::DatasetUrl.ensure_protocol('https://data.sfgov.org/')
|
53
|
+
|
54
|
+
expect(url).to eq('https://data.sfgov.org/')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '.strip_format' do
|
59
|
+
it 'preserves url without format' do
|
60
|
+
url = Mocrata::DatasetUrl.strip_format('data.sfgov.org/resource/foo')
|
61
|
+
|
62
|
+
expect(url).to eq('data.sfgov.org/resource/foo')
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'strips format at end of url' do
|
66
|
+
url = Mocrata::DatasetUrl.strip_format('data.sfgov.org/resource/foo.bar')
|
67
|
+
|
68
|
+
expect(url).to eq('data.sfgov.org/resource/foo')
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'preserves format if not at end of url' do
|
72
|
+
url = Mocrata::DatasetUrl.strip_format('data.sfgov.org/resource/foo.bar/baz')
|
73
|
+
|
74
|
+
expect(url).to eq('data.sfgov.org/resource/foo.bar/baz')
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Mocrata::Request do
|
6
|
+
describe '#response' do
|
7
|
+
it 'forms response' do
|
8
|
+
request = Mocrata::Request.new(
|
9
|
+
'https://data.sfgov.org/resource/funx-qxxn', :json)
|
10
|
+
|
11
|
+
expect_any_instance_of(Mocrata::Response).to receive(
|
12
|
+
:validate!).and_return(true)
|
13
|
+
|
14
|
+
expect(request.send(:http)).to receive(:request).and_return(true)
|
15
|
+
|
16
|
+
expect(request.response).to be_an_instance_of(Mocrata::Response)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#content_type' do
|
21
|
+
it 'raises exception with null format' do
|
22
|
+
request = Mocrata::Request.new(
|
23
|
+
'https://data.sfgov.org/resource/funx-qxxn', nil)
|
24
|
+
|
25
|
+
expect {
|
26
|
+
request.content_type
|
27
|
+
}.to raise_error(Mocrata::Request::RequestError)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'raises exception with invalid format' do
|
31
|
+
request = Mocrata::Request.new(
|
32
|
+
'https://data.sfgov.org/resource/funx-qxxn', :nope)
|
33
|
+
|
34
|
+
expect {
|
35
|
+
request.content_type
|
36
|
+
}.to raise_error(Mocrata::Request::RequestError)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'returns valid content type' do
|
40
|
+
request = Mocrata::Request.new(
|
41
|
+
'https://data.sfgov.org/resource/funx-qxxn', :csv)
|
42
|
+
|
43
|
+
expect(request.content_type).to eq('text/csv')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#http' do
|
48
|
+
it 'uses ssl' do
|
49
|
+
request = Mocrata::Request.new(
|
50
|
+
'https://data.sfgov.org/resource/funx-qxxn', nil)
|
51
|
+
|
52
|
+
expect(request.send(:http).use_ssl?).to be true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#soda_params' do
|
57
|
+
it 'is formed with default params' do
|
58
|
+
request = Mocrata::Request.new('', nil)
|
59
|
+
|
60
|
+
expect(request.send(:soda_params)).to eq(:$limit => 1000, :$offset => 0)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'is formed with custom params' do
|
64
|
+
request = Mocrata::Request.new('', nil, :page => 5, :per_page => 100)
|
65
|
+
|
66
|
+
expect(request.send(:soda_params)).to eq(:$limit => 100, :$offset => 400)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'ignores custom params' do
|
70
|
+
request = Mocrata::Request.new('', nil, :wat => 'nope')
|
71
|
+
|
72
|
+
expect(request.send(:soda_params)).to eq(:$limit => 1000, :$offset => 0)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe '#uri' do
|
77
|
+
before :each do
|
78
|
+
expect(Mocrata.config).to receive(:per_page).and_return(10)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'is formed with default parameters' do
|
82
|
+
request = Mocrata::Request.new(
|
83
|
+
'https://data.sfgov.org/resource/funx-qxxn', nil)
|
84
|
+
|
85
|
+
result = request.send(:uri).to_s
|
86
|
+
|
87
|
+
expect(result).to eq(
|
88
|
+
'https://data.sfgov.org/resource/funx-qxxn?$limit=10&$offset=0')
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'is formed with custom parameters' do
|
92
|
+
request = Mocrata::Request.new(
|
93
|
+
'https://data.sfgov.org/resource/funx-qxxn', nil,
|
94
|
+
:per_page => 5,
|
95
|
+
:page => 3)
|
96
|
+
|
97
|
+
result = request.send(:uri).to_s
|
98
|
+
|
99
|
+
expect(result).to eq(
|
100
|
+
'https://data.sfgov.org/resource/funx-qxxn?$limit=5&$offset=10')
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe '.query_string' do
|
105
|
+
it 'forms empty query string' do
|
106
|
+
expect(Mocrata::Request.query_string({})).to eq('')
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'forms simple query string' do
|
110
|
+
result = Mocrata::Request.query_string(:foo => 'bar')
|
111
|
+
expect(result).to eq('foo=bar')
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'forms complex string' do
|
115
|
+
result = Mocrata::Request.query_string(:foo => 'bar', :bar => 'baz')
|
116
|
+
expect(result).to eq('foo=bar&bar=baz')
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'escapes values' do
|
120
|
+
result = Mocrata::Request.query_string(:foo => '"\'Stop!\' said Fred"')
|
121
|
+
|
122
|
+
expect(result).to eq('foo=%22%27Stop%21%27+said+Fred%22')
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Mocrata::Response do
|
6
|
+
let :response do
|
7
|
+
Mocrata::Response.new(true)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#validate!' do
|
11
|
+
it 'returns true without content type' do
|
12
|
+
expect(response).to receive(:content_type).and_return(nil)
|
13
|
+
expect(response.validate!).to be true
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'returns true with csv content' do
|
17
|
+
expect(response).to receive(:content_type).and_return(:csv)
|
18
|
+
expect(response.validate!).to be true
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'returns true with json array' do
|
22
|
+
expect(response).to receive(:content_type).and_return(:json)
|
23
|
+
expect(response).to receive(:body).and_return([])
|
24
|
+
expect(response.validate!).to be true
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'returns true with json array' do
|
28
|
+
expect(response).to receive(:content_type).and_return(:json)
|
29
|
+
expect(response).to receive(:body).at_least(:once).and_return({})
|
30
|
+
expect(response.validate!).to be true
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'raises exception with json error' do
|
34
|
+
expect(response).to receive(:content_type).and_return(:json)
|
35
|
+
expect(response).to receive(:body).at_least(:once).and_return(
|
36
|
+
'error' => true,
|
37
|
+
'message' => 'something went wrong')
|
38
|
+
|
39
|
+
expect { response.validate! }.to raise_error(
|
40
|
+
Mocrata::Response::ResponseError)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#content_type' do
|
45
|
+
it 'detects csv' do
|
46
|
+
expect(response).to receive(:headers).and_return(
|
47
|
+
'content-type' => 'text/csv')
|
48
|
+
|
49
|
+
expect(response.send(:content_type)).to eq(:csv)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'detects csv with junk at the end' do
|
53
|
+
expect(response).to receive(:headers).and_return(
|
54
|
+
'content-type' => 'text/csv; charset=utf-8')
|
55
|
+
|
56
|
+
expect(response.send(:content_type)).to eq(:csv)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'detects json' do
|
60
|
+
expect(response).to receive(:headers).and_return(
|
61
|
+
'content-type' => 'application/json')
|
62
|
+
|
63
|
+
expect(response.send(:content_type)).to eq(:json)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'raises exception for unrecognized content type' do
|
67
|
+
expect(response).to receive(:headers).and_return(
|
68
|
+
'content-type' => 'text/html')
|
69
|
+
|
70
|
+
expect { response.send(:content_type) }.to raise_error(
|
71
|
+
Mocrata::Response::ResponseError)
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'raises exception for absent content type' do
|
75
|
+
expect(response).to receive(:headers).and_return({})
|
76
|
+
expect { response.send(:content_type) }.to raise_error(
|
77
|
+
Mocrata::Response::ResponseError)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '#csv' do
|
82
|
+
it 'parses body and excludes header' do
|
83
|
+
csv = "\"header1\"\n\"row1\"\n\"row2\""
|
84
|
+
expect(response.send(:http_response)).to receive(:body).and_return(csv)
|
85
|
+
expect(response.send(:csv)).to eq([['row1'], ['row2']])
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '#json' do
|
90
|
+
it 'parses body' do
|
91
|
+
json = '[{"key1":"val1"}]'
|
92
|
+
expect(response.send(:http_response)).to receive(:body).and_return(json)
|
93
|
+
expect(response.send(:json)).to eq([{'key1' => 'val1'}])
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe '#body' do
|
98
|
+
it 'returns json' do
|
99
|
+
expect(response).to receive(:content_type).and_return(:json)
|
100
|
+
expect(response).to receive(:json).and_return([])
|
101
|
+
expect(response.body).to eq([])
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'returns csv' do
|
105
|
+
expect(response).to receive(:content_type).and_return(:csv)
|
106
|
+
expect(response).to receive(:csv).and_return([])
|
107
|
+
expect(response.body).to eq([])
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe '#headers' do
|
112
|
+
it 'preserves non json headers' do
|
113
|
+
expect(response.send(:http_response)).to receive(:each_header)
|
114
|
+
.and_yield('x-foo-header', 'foo')
|
115
|
+
.and_yield('x-bar-header', 'bar')
|
116
|
+
|
117
|
+
expect(response.headers).to eq(
|
118
|
+
'x-foo-header' => 'foo',
|
119
|
+
'x-bar-header' => 'bar')
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'parses json headers' do
|
123
|
+
expect(response.send(:http_response)).to receive(:each_header)
|
124
|
+
.and_yield('x-foo-header', 'foo')
|
125
|
+
.and_yield('x-soda2-fields', '{"name":"value"}')
|
126
|
+
|
127
|
+
expect(response.headers).to eq(
|
128
|
+
'x-foo-header' => 'foo',
|
129
|
+
'x-soda2-fields' => { 'name' => 'value' })
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Mocrata do
|
6
|
+
after :each do
|
7
|
+
Mocrata.reset
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '.configure' do
|
11
|
+
it 'sets configuration variables' do
|
12
|
+
expect_any_instance_of(Mocrata::Configuration).to receive(:setting=).once
|
13
|
+
|
14
|
+
Mocrata.configure do |config|
|
15
|
+
config.setting = 'value'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '.config' do
|
21
|
+
it 'instantiates and memoizes configuration instance' do
|
22
|
+
expect(Mocrata.instance_variable_get(:@config)).to be_nil
|
23
|
+
|
24
|
+
expect(Mocrata.config).to be_an_instance_of(Mocrata::Configuration)
|
25
|
+
|
26
|
+
config = Mocrata.instance_variable_get(:@config)
|
27
|
+
|
28
|
+
expect(config).to be_an_instance_of(Mocrata::Configuration)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mocrata
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Heather Rivers
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-07-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: simplecov
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rdoc
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Mode's SODA client
|
84
|
+
email:
|
85
|
+
- heather@modeanalytics.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- .gitignore
|
91
|
+
- .yardopts
|
92
|
+
- Gemfile
|
93
|
+
- LICENSE.txt
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- lib/mocrata.rb
|
97
|
+
- lib/mocrata/configuration.rb
|
98
|
+
- lib/mocrata/dataset.rb
|
99
|
+
- lib/mocrata/dataset_url.rb
|
100
|
+
- lib/mocrata/request.rb
|
101
|
+
- lib/mocrata/response.rb
|
102
|
+
- lib/mocrata/version.rb
|
103
|
+
- mocrata.gemspec
|
104
|
+
- spec/lib/mocrata/configuration_spec.rb
|
105
|
+
- spec/lib/mocrata/dataset_spec.rb
|
106
|
+
- spec/lib/mocrata/dataset_url_spec.rb
|
107
|
+
- spec/lib/mocrata/request_spec.rb
|
108
|
+
- spec/lib/mocrata/response_spec.rb
|
109
|
+
- spec/lib/mocrata_spec.rb
|
110
|
+
- spec/spec_helper.rb
|
111
|
+
homepage: https://github.com/mode/mocrata
|
112
|
+
licenses:
|
113
|
+
- MIT
|
114
|
+
metadata: {}
|
115
|
+
post_install_message:
|
116
|
+
rdoc_options: []
|
117
|
+
require_paths:
|
118
|
+
- lib
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ~>
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '2.0'
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - '>='
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
requirements: []
|
130
|
+
rubyforge_project:
|
131
|
+
rubygems_version: 2.0.0
|
132
|
+
signing_key:
|
133
|
+
specification_version: 4
|
134
|
+
summary: Mocrata is a SODA (Socrata Open Data API) client developed by Mode Analytics
|
135
|
+
test_files:
|
136
|
+
- spec/lib/mocrata/configuration_spec.rb
|
137
|
+
- spec/lib/mocrata/dataset_spec.rb
|
138
|
+
- spec/lib/mocrata/dataset_url_spec.rb
|
139
|
+
- spec/lib/mocrata/request_spec.rb
|
140
|
+
- spec/lib/mocrata/response_spec.rb
|
141
|
+
- spec/lib/mocrata_spec.rb
|
142
|
+
- spec/spec_helper.rb
|
143
|
+
has_rdoc:
|