Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Resource should respond to a 404 with an exception. Also, return [] for

empty query results for OpenStates queries.
  • Loading branch information...
commit 49409ad19ee024ec796efb26e111ef267f26f688 1 parent c66954d
@jeff-r jeff-r authored
View
40 lib/gov_kit/open_states.rb
@@ -9,6 +9,24 @@ class OpenStatesResource < Resource
# http://rubydoc.info/gems/httparty/0.7.4/HTTParty/ClassMethods#default_params-instance_method
default_params :output => 'json', :apikey => GovKit::configuration.sunlight_apikey
base_uri GovKit::configuration.openstates_base_url
+
+ # Do a GET query, with optional parameters.
+ #
+ # OpenStates returns a 404 error when a query
+ # returns nothing.
+ #
+ # So, if a query result is a resource not found error,
+ # we return an empty set.
+ def self.get_uri(uri, options={})
+ begin
+ response = get(uri, options)
+ result = parse(response)
+ rescue ResourceNotFound
+ return []
+ end
+ result
+ end
+
end
# Ruby module for interacting with the Open States Project API
@@ -32,8 +50,7 @@ module OpenStates
#
class State < OpenStatesResource
def self.find_by_abbreviation(abbreviation)
- response = get("/metadata/#{abbreviation}/")
- parse(response)
+ get_uri("/metadata/#{abbreviation}/")
end
end
@@ -47,13 +64,12 @@ class Bill < OpenStatesResource
def self.find(state_abbrev, session, bill_id, chamber = '')
escaped_bill_id = bill_id.gsub(/ /, '%20')
escaped_session = session.gsub(/ /, '%20')
- response = get("/bills/#{state_abbrev.downcase}/#{escaped_session}/#{chamber.blank? ? '' : chamber + '/'}#{escaped_bill_id}/")
- parse(response)
+
+ get_uri("/bills/#{state_abbrev.downcase}/#{escaped_session}/#{chamber.blank? ? '' : chamber + '/'}#{escaped_bill_id}/")
end
def self.search(query, options = {})
- response = get('/bills/', :query => {:q => query}.merge(options))
- parse(response)
+ get_uri('/bills/', :query => {:q => query}.merge(options))
end
def self.latest(updated_since, ops = {})
@@ -69,13 +85,11 @@ def self.latest(updated_since, ops = {})
#
class Legislator < OpenStatesResource
def self.find(legislator_id)
- response = get("/legislators/#{legislator_id}/")
- parse(response)
+ get_uri("/legislators/#{legislator_id}/")
end
def self.search(options = {})
- response = get('/legislators/', :query => options)
- parse(response)
+ get_uri('/legislators/', :query => options)
end
end
@@ -86,13 +100,11 @@ def self.search(options = {})
#
class Committee < OpenStatesResource
def self.find(committee_id)
- response = get("/committees/#{committee_id}/")
- parse(response)
+ get_uri("/committees/#{committee_id}/")
end
def self.search(options = {})
- response = get('/committees/', :query => options)
- parse(response)
+ get_uri('/committees/', :query => options)
end
end
View
7 lib/gov_kit/resource.rb
@@ -53,8 +53,7 @@ def self.parse(response)
if response.class == HTTParty::Response
case response.response
when Net::HTTPNotFound
- return []
- # raise ResourceNotFound, "404 Not Found"
+ raise ResourceNotFound, "404 Not Found"
when Net::HTTPGone
raise ResourceNotFound, "404 Not Found"
when Net::HTTPUnauthorized
@@ -76,14 +75,14 @@ def self.parse(response)
# +record+ is a hash of values returned by a service,
# or an array of hashes.
#
- # If +record+ is a hash, return a single GovKit::Resource.
+ # If +record+ is a hash, return an array containing a single GovKit::Resource.
# If it is an array, return an array of GovKit::Resources.
#
def self.instantiate(record)
if record.is_a?(Array)
instantiate_collection(record)
else
- new(record)
+ [new(record)]
end
end
View
53 spec/fixtures/open_states/committee_find.response
@@ -0,0 +1,53 @@
+HTTP/1.1 200 OK
+Server: nginx/1.0.0
+Date: Fri, 29 Apr 2011 21:05:59 GMT
+Content-Type: application/json; charset=utf-8
+Connection: keep-alive
+Authorization:
+
+{
+ "members": [
+ {
+ "leg_id": "MDL000194",
+ "role": "member",
+ "name": "Joan Carter Conway"
+ },
+ {
+ "leg_id": "MDL000226",
+ "role": "member",
+ "name": "Paul G. Pinsky"
+ },
+ {
+ "leg_id": "MDL000379",
+ "role": "member",
+ "name": "Bill Ferguson"
+ },
+ {
+ "leg_id": "MDL000381",
+ "role": "member",
+ "name": "J. B. Jennings"
+ },
+ {
+ "leg_id": "MDL000384",
+ "role": "member",
+ "name": "Karen S. Montgomery"
+ },
+ {
+ "leg_id": "MDL000387",
+ "role": "member",
+ "name": "Ronald N. Young"
+ }
+ ],
+ "updated_at": "2011-04-29 01:19:52",
+ "parent_id": "MDC000009",
+ "state": "md",
+ "sources": [
+ {
+ "url": "http://www.msa.md.gov/msa/mdmanual/05sen/html/com/02eco.html"
+ }
+ ],
+ "subcommittee": "ENVIRONMENT SUBCOMMITTEE",
+ "committee": "EDUCATION, HEALTH & ENVIRONMENTAL AFFAIRS COMMITTEE",
+ "chamber": "upper",
+ "id": "MDC000012"
+}
View
190 spec/fixtures/open_states/committee_query.response
@@ -0,0 +1,190 @@
+HTTP/1.1 200 OK
+Server: nginx/1.0.0
+Date: Fri, 29 Apr 2011 20:52:00 GMT
+Content-Type: application/json; charset=utf-8
+Connection: keep-alive
+
+[
+ {
+ "updated_at": "2011-04-26 01:20:17",
+ "parent_id": "MDC000009",
+ "state": "md",
+ "subcommittee": "ALCOHOLIC BEVERAGES SUBCOMMITTEE",
+ "committee": "EDUCATION, HEALTH & ENVIRONMENTAL AFFAIRS COMMITTEE",
+ "chamber": "upper",
+ "id": "MDC000009"
+ },
+ {
+ "updated_at": "2011-04-29 01:19:52",
+ "parent_id": "MDC000003",
+ "state": "md",
+ "subcommittee": "CAPITAL BUDGET SUBCOMMITTEE",
+ "committee": "BUDGET & TAXATION COMMITTEE",
+ "chamber": "upper",
+ "id": "MDC000004"
+ },
+ {
+ "updated_at": "2011-04-29 01:19:52",
+ "parent_id": "MDC000009",
+ "state": "md",
+ "subcommittee": "ENVIRONMENT SUBCOMMITTEE",
+ "committee": "EDUCATION, HEALTH & ENVIRONMENTAL AFFAIRS COMMITTEE",
+ "chamber": "upper",
+ "id": "MDC000012"
+ },
+ {
+ "updated_at": "2011-04-29 01:19:52",
+ "parent_id": "MDC000009",
+ "state": "md",
+ "subcommittee": "ETHICS & ELECTION LAW SUBCOMMITTEE",
+ "committee": "EDUCATION, HEALTH & ENVIRONMENTAL AFFAIRS COMMITTEE",
+ "chamber": "upper",
+ "id": "MDC000013"
+ },
+ {
+ "updated_at": "2011-04-29 01:19:52",
+ "parent_id": null,
+ "state": "md",
+ "subcommittee": null,
+ "committee": "FINANCE COMMITTEE",
+ "chamber": "upper",
+ "id": "MDC000015"
+ },
+ {
+ "updated_at": "2011-04-29 01:19:52",
+ "parent_id": "MDC000015",
+ "state": "md",
+ "subcommittee": "HEALTH SUBCOMMITTEE",
+ "committee": "FINANCE COMMITTEE",
+ "chamber": "upper",
+ "id": "MDC000016"
+ },
+ {
+ "updated_at": "2011-04-29 01:19:53",
+ "parent_id": "MDC000015",
+ "state": "md",
+ "subcommittee": "TRANSPORTATION SUBCOMMITTEE",
+ "committee": "FINANCE COMMITTEE",
+ "chamber": "upper",
+ "id": "MDC000017"
+ },
+ {
+ "updated_at": "2011-04-29 01:19:53",
+ "parent_id": null,
+ "state": "md",
+ "subcommittee": null,
+ "committee": "EXECUTIVE NOMINATIONS COMMITTEE",
+ "chamber": "upper",
+ "id": "MDC000018"
+ },
+ {
+ "updated_at": "2011-04-29 01:19:52",
+ "parent_id": "MDC000003",
+ "state": "md",
+ "subcommittee": "PUBLIC SAFETY, TRANSPORTATION & ENVIRONMENT SUBCOMMITTEE",
+ "committee": "BUDGET & TAXATION COMMITTEE",
+ "chamber": "upper",
+ "id": "MDC000007"
+ },
+ {
+ "created_at": "2011-01-29 00:13:19",
+ "updated_at": "2011-04-29 01:19:52",
+ "parent_id": "MDC000009",
+ "state": "md",
+ "subcommittee": "HEALTH OCCUPATIONS SUBCOMMITTEE",
+ "committee": "EDUCATION, HEALTH & ENVIRONMENTAL AFFAIRS COMMITTEE",
+ "chamber": "upper",
+ "id": "MDC000068"
+ },
+ {
+ "updated_at": "2011-04-29 01:19:52",
+ "parent_id": null,
+ "state": "md",
+ "subcommittee": null,
+ "committee": "BUDGET & TAXATION COMMITTEE",
+ "chamber": "upper",
+ "id": "MDC000003"
+ },
+ {
+ "updated_at": "2011-04-29 01:19:53",
+ "parent_id": null,
+ "state": "md",
+ "subcommittee": null,
+ "committee": "SPECIAL COMMITTEE ON SUBSTANCE ABUSE",
+ "chamber": "upper",
+ "id": "MDC000019"
+ },
+ {
+ "created_at": "2011-01-29 00:13:19",
+ "updated_at": "2011-04-29 01:19:52",
+ "parent_id": "MDC000009",
+ "state": "md",
+ "subcommittee": "LABOR LICENSING, & REGULATION SUBCOMMITTEE",
+ "committee": "EDUCATION, HEALTH & ENVIRONMENTAL AFFAIRS COMMITTEE",
+ "chamber": "upper",
+ "id": "MDC000069"
+ },
+ {
+ "updated_at": "2011-04-29 01:19:52",
+ "parent_id": "MDC000003",
+ "state": "md",
+ "subcommittee": "HEALTH & HUMAN SERVICES SUBCOMMITTEE",
+ "committee": "BUDGET & TAXATION COMMITTEE",
+ "chamber": "upper",
+ "id": "MDC000067"
+ },
+ {
+ "updated_at": "2011-04-29 01:19:52",
+ "parent_id": "MDC000003",
+ "state": "md",
+ "subcommittee": "EDUCATION, BUSINESS & ADMINISTRATION SUBCOMMITTEE",
+ "committee": "BUDGET & TAXATION COMMITTEE",
+ "chamber": "upper",
+ "id": "MDC000066"
+ },
+ {
+ "updated_at": "2011-04-29 01:19:52",
+ "parent_id": "MDC000003",
+ "state": "md",
+ "subcommittee": "PENSIONS SUBCOMMITTEE",
+ "committee": "BUDGET & TAXATION COMMITTEE",
+ "chamber": "upper",
+ "id": "MDC000006"
+ },
+ {
+ "updated_at": "2011-04-29 01:19:52",
+ "parent_id": null,
+ "state": "md",
+ "subcommittee": null,
+ "committee": "EDUCATION, HEALTH & ENVIRONMENTAL AFFAIRS COMMITTEE",
+ "chamber": "upper",
+ "id": "MDC000008"
+ },
+ {
+ "updated_at": "2011-04-29 01:19:52",
+ "parent_id": "MDC000009",
+ "state": "md",
+ "subcommittee": "EDUCATION SUBCOMMITTEE",
+ "committee": "EDUCATION, HEALTH & ENVIRONMENTAL AFFAIRS COMMITTEE",
+ "chamber": "upper",
+ "id": "MDC000011"
+ },
+ {
+ "updated_at": "2011-04-29 01:19:52",
+ "parent_id": null,
+ "state": "md",
+ "subcommittee": null,
+ "committee": "RULES COMMITTEE",
+ "chamber": "upper",
+ "id": "MDC000001"
+ },
+ {
+ "updated_at": "2011-04-29 01:19:52",
+ "parent_id": null,
+ "state": "md",
+ "subcommittee": null,
+ "committee": "JUDICIAL PROCEEDINGS COMMITTEE",
+ "chamber": "upper",
+ "id": "MDC000002"
+ }
+]
View
94 spec/open_states_spec.rb
@@ -13,8 +13,12 @@ module GovKit::OpenStates
before(:all) do
base_uri = GovKit::OpenStatesResource.base_uri.gsub(/\./, '\.')
+ # An array of uris and filenames
+ # Use FakeWeb to intercept net requests;
+ # if a requested uri matches one of the following,
+ # then return the contents of the corresponding file
+ # as the result.
urls = [
- ['/metadata/ca/\?', 'state.response'],
['/bills/ca/20092010/AB667/', 'bill.response'],
['/bills/\?', 'bill_query.response'],
['/bills/latest/\?', 'bill_query.response'],
@@ -22,9 +26,36 @@ module GovKit::OpenStates
['/legislators/410/\?', '410.response'],
['/legislators/401/\?', '401.response'],
['/legislators/404/\?', '404.response'],
- ['/legislators/\?', 'legislator_query.response']
+ ['/legislators/\?.*state=zz.*', '404.response'],
+ ['/legislators/\?.*state=ca.*', 'legislator.response'],
+ ['/committees/MDC000012/', 'committee_find.response'],
+ ['/committees/\?.*state=md', 'committee_query.response'],
+ ['/metadata/ca/\?', 'state.response']
]
+ # First convert each of the uri strings above into regexp's before
+ # passing them on to register_uri.
+ #
+ # Internally, before checking if a new uri string matches one of the registered uri's,
+ # FakeWeb normalizes it by parsing it with URI.parse(string), and then
+ # calling URI.normalize on the resulting URI. This appears to reorder any
+ # query parameters alphabetically by key.
+ #
+ # So the uri
+ # http://openstates.sunlightlabs.com/api/v1/legislators/?state=zz&output=json&apikey=
+ # would actually not match a registered uri of
+ # ['/legislators/\?state=zz', '404.response'],
+ # or
+ # ['/legislators/\?state=zz*', '404.response'],
+ # or even
+ # ['/legislators/\?state=zz&output=json&apikey=', '404.response'],
+ #
+ # But it would match a registered uri of
+ # ['/legislators/\?apikey=&output=json&state=zz', '404.response'],
+ # or
+ # ['/legislators/\?(.*)state=zz(.*)', '404.response'],
+
+
urls.each do |u|
FakeWeb.register_uri(:get, %r|#{base_uri}#{u[0]}|, :response => File.join(FIXTURES_DIR, 'open_states', u[1]))
end
@@ -37,7 +68,6 @@ module GovKit::OpenStates
end
it "should raise NotAuthorized if the api key is not valid" do
- # The Open States API returns a 401 Not Authorized if the API key is invalid.
lambda do
@legislator = Legislator.find(401)
end.should raise_error(GovKit::NotAuthorized)
@@ -52,9 +82,13 @@ module GovKit::OpenStates
it "should find a state by abbreviation" do
lambda do
- @state = State.find_by_abbreviation('ca')
+ @states = State.find_by_abbreviation('ca')
end.should_not raise_error
+ @states.should be_an_instance_of(Array)
+ @states.length.should eql(1)
+ @state = @states[0]
+
@state.should be_an_instance_of(State)
@state.name.should == "California"
@state.sessions.size.should == 8
@@ -66,9 +100,13 @@ module GovKit::OpenStates
context "#find" do
it "should find a bill by state abbreviation, session, chamber, bill_id" do
lambda do
- @bill = Bill.find('ca', '20092010', 'lower', 'AB667')
+ @bills = Bill.find('ca', '20092010', 'lower', 'AB667')
end.should_not raise_error
+ @bills.should be_an_instance_of(Array)
+ @bills.length.should eql(1)
+ @bill = @bills[0]
+
@bill.should be_an_instance_of(Bill)
@bill.title.should include("An act to amend Section 1750.1 of the Business and Professions Code, and to amend Section 104830 of")
end
@@ -105,17 +143,19 @@ module GovKit::OpenStates
context "#find" do
it "should find a specific legislator" do
lambda do
- @legislator = Legislator.find(2462)
+ @legislators = Legislator.find(2462)
end.should_not raise_error
+ @legislators.should be_an_instance_of(Array)
+ @legislators.length.should eql(1)
+ @legislator = @legislators[0]
+
@legislator.should be_an_instance_of(Legislator)
@legislator.first_name.should == "Dave"
@legislator.last_name.should == "Cox"
end
it "should return an empty array if the legislator is not found" do
- # The OpenStates server returns a 404 error.
- # resource.rb parses that and returns an empty array.
@legislator = Legislator.find(404)
@legislator.should eql([])
@@ -133,8 +173,46 @@ module GovKit::OpenStates
l.should be_an_instance_of(Legislator)
end
end
+
+ it "should return an empty array if no legislators are found" do
+ lambda do
+ @legislators = Legislator.search(:state => 'zz')
+ end.should_not raise_error
+
+ @legislators.should be_an_instance_of(Array)
+ @legislators.length.should eql(0)
+ end
end
end
+
+ describe Committee do
+ context "#find" do
+ it "should find a specific committee" do
+ lambda do
+ @committees = Committee.find( 'MDC000012' )
+ end.should_not raise_error
+
+ @committees.should be_an_instance_of(Array)
+ @committees.length.should eql(1)
+ com = @committees[0]
+ com.should be_an_instance_of(Committee)
+ com['id'].should eql('MDC000012')
+ end
+ end
+ context "#search" do
+ it "should return an array of committees" do
+ lambda do
+ @committees = Committee.search( :state => 'md', :chamber => 'upper' )
+ end.should_not raise_error
+
+ @committees.should be_an_instance_of(Array)
+ @committees.length.should eql(20)
+ com = @committees[0]
+ com.should be_an_instance_of(Committee)
+ com['id'].should eql('MDC000009')
+ end
+ end
+ end
end
end
Please sign in to comment.
Something went wrong with that request. Please try again.