netroots-ruby-votesmart 0.1.1 → 0.2.0

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.
Files changed (55) hide show
  1. data/History.txt +4 -0
  2. data/README.rdoc +48 -0
  3. data/VERSION.yml +4 -0
  4. data/lib/mcll4r/MIT-LICENSE +20 -0
  5. data/lib/mcll4r/README +19 -0
  6. data/lib/mcll4r/mcll4r.rb +24 -0
  7. data/lib/mcll4r/mcll4r_test.rb +35 -0
  8. data/lib/ruby-votesmart.rb +22 -0
  9. data/lib/vote_smart/address.rb +42 -0
  10. data/lib/vote_smart/candidate.rb +32 -0
  11. data/lib/vote_smart/candidate_bio.rb +16 -0
  12. data/lib/vote_smart/candidate_office.rb +15 -0
  13. data/lib/vote_smart/committee.rb +23 -0
  14. data/lib/vote_smart/common.rb +91 -0
  15. data/lib/vote_smart/district.rb +36 -0
  16. data/lib/vote_smart/election.rb +21 -0
  17. data/lib/vote_smart/leadership.rb +16 -0
  18. data/lib/vote_smart/local.rb +21 -0
  19. data/lib/vote_smart/measure.rb +16 -0
  20. data/lib/vote_smart/notes.rb +11 -0
  21. data/lib/vote_smart/npat.rb +11 -0
  22. data/lib/vote_smart/office.rb +113 -0
  23. data/lib/vote_smart/official.rb +111 -0
  24. data/lib/vote_smart/phone.rb +12 -0
  25. data/lib/vote_smart/rating.rb +26 -0
  26. data/lib/vote_smart/state.rb +29 -0
  27. data/lib/vote_smart/vote.rb +70 -0
  28. data/spec/responses/Address.get_office.1721.js +1 -0
  29. data/spec/responses/District.get_by_office_state.7.GA.js +1 -0
  30. data/spec/responses/District.get_by_office_state.8.GA.js +1 -0
  31. data/spec/responses/District.get_by_office_state.9.GA.js +1 -0
  32. data/spec/responses/Office.get_offices_by_type.C.js +1 -0
  33. data/spec/responses/Office.get_offices_by_type.L.js +1 -0
  34. data/spec/responses/Office.get_offices_by_type.P.js +1 -0
  35. data/spec/responses/Office.get_offices_by_type.S.js +1 -0
  36. data/spec/responses/Office.get_types.js +1 -0
  37. data/spec/responses/Official.get_by_district.20451.js +1 -0
  38. data/spec/responses/Official.get_by_district.20689.js +1 -0
  39. data/spec/responses/Official.get_by_district.21946.js +1 -0
  40. data/spec/responses/Official.get_by_office_state.12.GA.js +1 -0
  41. data/spec/responses/Official.get_by_office_state.13.GA.js +1 -0
  42. data/spec/responses/Official.get_by_office_state.33.GA.js +1 -0
  43. data/spec/responses/Official.get_by_office_state.42.GA.js +1 -0
  44. data/spec/responses/Official.get_by_office_state.44.GA.js +1 -0
  45. data/spec/responses/Official.get_by_office_state.45.GA.js +1 -0
  46. data/spec/responses/Official.get_by_office_state.53.GA.js +1 -0
  47. data/spec/responses/State.get_state.GA.js +1 -0
  48. data/spec/responses/State.get_state_ids.js +1 -0
  49. data/spec/responses/authorization_failed.js +1 -0
  50. data/spec/spec_helper.rb +98 -0
  51. data/spec/vote_smart/district_spec.rb +36 -0
  52. data/spec/vote_smart/office_spec.rb +96 -0
  53. data/spec/vote_smart/official_spec.rb +44 -0
  54. data/spec/vote_smart/state_spec.rb +28 -0
  55. metadata +68 -29
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2009-02-10
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/README.rdoc ADDED
@@ -0,0 +1,48 @@
1
+ = project-vote-smart
2
+
3
+ * FIX (url)
4
+
5
+ == DESCRIPTION:
6
+
7
+ FIX (describe your package)
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * FIX (list of features or problems)
12
+
13
+ == SYNOPSIS:
14
+
15
+ FIX (code sample of usage)
16
+
17
+ == REQUIREMENTS:
18
+
19
+ * FIX (list of requirements)
20
+
21
+ == INSTALL:
22
+
23
+ * FIX (sudo gem install, anything else)
24
+
25
+ == LICENSE:
26
+
27
+ (The MIT License)
28
+
29
+ Copyright (c) 2009 FIXME full name
30
+
31
+ Permission is hereby granted, free of charge, to any person obtaining
32
+ a copy of this software and associated documentation files (the
33
+ 'Software'), to deal in the Software without restriction, including
34
+ without limitation the rights to use, copy, modify, merge, publish,
35
+ distribute, sublicense, and/or sell copies of the Software, and to
36
+ permit persons to whom the Software is furnished to do so, subject to
37
+ the following conditions:
38
+
39
+ The above copyright notice and this permission notice shall be
40
+ included in all copies or substantial portions of the Software.
41
+
42
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
43
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
44
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
45
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
46
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
47
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
48
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 2
3
+ :patch: 0
4
+ :major: 0
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Mobile Commons
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/lib/mcll4r/README ADDED
@@ -0,0 +1,19 @@
1
+ === mcll4r
2
+
3
+ - [Code on GitHub](http://github.com/mcommons/mcll4r)
4
+
5
+ === Description
6
+
7
+ Ruby client for Mobile Commons Legislative Lookup API
8
+
9
+ Based on the API described at http://congress.mcommons.com
10
+
11
+
12
+ === Authors
13
+
14
+ - Maintained by [Benjamin Stein](mailto:ben@mcommons.com), [Mal McKay](mailto:mal@mcommons.com) & [Nathan Woodhull](mailto:nathan@mcommons.com)
15
+
16
+ === License
17
+
18
+ Copyright (c) 2008 Mobile Commons
19
+ See MIT-LICENSE in this directory.
@@ -0,0 +1,24 @@
1
+ require 'rubygems'
2
+ require 'httparty'
3
+
4
+ class Mcll4r
5
+ include HTTParty
6
+ base_uri "http://congress.mcommons.com"
7
+ format :xml
8
+
9
+ def district_lookup(lat, lng)
10
+ filter_for_errors self.class.get("/districts/lookup.xml", :query=>{:lat=>lat, :lng=>lng})
11
+ end
12
+
13
+ private
14
+
15
+ def filter_for_errors(hash)
16
+ if hash['response']['error']
17
+ raise DistrictNotFound.new(hash['response']['error'])
18
+ end
19
+ hash
20
+ end
21
+
22
+ end
23
+
24
+ class DistrictNotFound < Exception; end
@@ -0,0 +1,35 @@
1
+ require 'mcll4r'
2
+ require 'test/unit'
3
+
4
+ class Mcll4rTest < Test::Unit::TestCase
5
+
6
+ def setup
7
+ @mcll4r = Mcll4r.new
8
+ end
9
+
10
+ def test_assert_we_get_back_correct_district_data
11
+ expected = {
12
+ "response" => {
13
+ "state_upper" => { "district" => "029", "display_name" => "TX 29th", "state" => "TX" },
14
+ "federal" => { "district" => "16", "display_name" => "TX 16th", "state" => "TX" },
15
+ "state_lower" => { "district" => "077", "display_name" => "TX 77th", "state" => "TX" },
16
+ "lng" => "-106.490969",
17
+ "lat" => "31.76321"
18
+ }
19
+ }
20
+ assert_equal expected, @mcll4r.district_lookup(31.76321, -106.490969)
21
+ end
22
+
23
+ def test_assert_raise_on_error
24
+ assert_raise DistrictNotFound do
25
+ @mcll4r.district_lookup(nil,nil)
26
+ end
27
+ end
28
+
29
+ def test_assert_raise_on_district_not_found
30
+ assert_raise DistrictNotFound do
31
+ @mcll4r.district_lookup( 1.0, 1.0 )
32
+ end
33
+ end
34
+
35
+ end
@@ -0,0 +1,22 @@
1
+ require 'rubygems'
2
+ require 'ym4r/google_maps/geocoding'
3
+ require 'active_support'
4
+ include Ym4r::GoogleMaps
5
+
6
+ module VoteSmart
7
+ API_URL = "http://api.votesmart.org/"
8
+ API_FORMAT = "JSON"
9
+
10
+ mattr_accessor :api_key
11
+
12
+ class RequestFailed < Exception; end
13
+ end
14
+
15
+ VoteSmart.api_key = "key"
16
+
17
+ require "#{File.dirname(__FILE__)}/mcll4r/mcll4r.rb"
18
+ require "#{File.dirname(__FILE__)}/vote_smart/common.rb"
19
+
20
+ Dir["#{File.dirname(__FILE__)}/vote_smart/*.rb"].each do |source_file|
21
+ require source_file unless source_file == "#{File.dirname(__FILE__)}/vote_smart/common.rb"
22
+ end
@@ -0,0 +1,42 @@
1
+ module VoteSmart
2
+
3
+ class Address < Common
4
+
5
+ attr_accessor :type, :street, :city, :state, :zip
6
+
7
+ set_attribute_map "type" => :type, "street" => :street, "city" => :city, "state" => :state, "zip" => :zip
8
+
9
+
10
+ # Returns a campaign office's contact information
11
+ def self.get_campaign can_id
12
+ request("Address.getCampaign", "candidateId" => can_id)
13
+ end
14
+
15
+ # Returns a campaign office's contact information
16
+ def self.get_campaign_web_address can_id
17
+ request("Address.getCampaignWebAddress", "candidateId" => can_id)
18
+ end
19
+
20
+ # Returns a campaign office's contact information
21
+ def self.get_campaign_by_election election_id
22
+ request("Address.getCampaignByElection", "electionId" => election_id)
23
+ end
24
+
25
+ # Returns an incumbent office's contact information
26
+ def self.get_office candidate_id
27
+ request("Address.getOffice", "candidateId" => candidate_id)
28
+ end
29
+
30
+ # Returns an incumbent office's contact information
31
+ def self.get_office_web_address can_id
32
+ request("Address.getOfficeWebAddress", "candidateId" => can_id)
33
+ end
34
+
35
+ # Returns a (sometimes)list of offices that fit office_id and state_id
36
+ def self.get_office_by_office_state office_id, state_id = 'NA'
37
+ request("Address.getOfficeByOfficeState", "officeId" => office_id, "stateId" => state_id)
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,32 @@
1
+ module VoteSmart
2
+
3
+ class Candidate < Common
4
+
5
+ # Returns a list of candidates/incumbents that fit the criteria
6
+ def self.get_by_office_state office_id, state_id = "NA", election_year = nil
7
+ request("Candidates.getByOfficeState", "officeId" => office_id, "stateId" => state_id, "electionYear" => election_year)
8
+ end
9
+
10
+ #Searches for candidates with exact lastname matches
11
+ def self.get_by_lastname last_name, election_year = nil
12
+ request("Candidates.getByLastname", "lastName" => last_name, "electionYear" => election_year)
13
+ end
14
+
15
+ # Searches for candidates with fuzzy lastname match
16
+ def self.get_by_levenstein last_name, election_year = nil
17
+ request("Candidates.getByLevenstein", "lastName" => last_name, "electionYear" => election_year)
18
+ end
19
+
20
+ # Returns candidates in the provided election_id
21
+ def self.get_by_election election_id
22
+ request("Candidates.getByElection", "electionId" => election_id)
23
+ end
24
+
25
+ # Returns candidates in the provided district_id
26
+ def self.get_by_district district_id, election_year = nil
27
+ request("Candidates.getByDistrict", "districtId" => district_id, "electionYear" => election_year)
28
+ end
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,16 @@
1
+ module VoteSmart
2
+
3
+ class CandidateBio < Common
4
+
5
+ # Returns basic bio details on a candidate
6
+ def self.get_bio can_id
7
+ request("CandidateBio.getBio", "candidateId" => can_id)
8
+ end
9
+
10
+ #
11
+ def self.get_addl_bio can_id
12
+ request("CandidateBio.getAddlBio", "candidateId" => can_id)
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ module VoteSmart
2
+
3
+ class CandidateOffice < Common
4
+
5
+ attr_accessor :address, :phone, :notes
6
+
7
+ def initialize attributes
8
+ self.address = Address.new(attributes["address"])
9
+ self.phone = Phone.new(attributes["phone"])
10
+ self.notes = Notes.new(attributes["notes"])
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,23 @@
1
+ module VoteSmart
2
+
3
+ class Committee < Common
4
+
5
+ # Returns committee types for use in other methods
6
+ def self.get_types
7
+ request("Committee.getTypes")
8
+ end
9
+
10
+ def self.get_committees_by_type_state type_id = nil, state_id = 'NA'
11
+ request("Committee.getCommitteesByTypeState", "typeId" => type_id, "stateId" => state_id)
12
+ end
13
+
14
+ def self.get_committee committee_id
15
+ request("Committee.getCommittee", "committeeId" => committee_id)
16
+ end
17
+
18
+ def self.get_committee_members committee_id
19
+ request("Committee.getCommitteeMembers", "committeeId" => committee_id)
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,91 @@
1
+ require 'json'
2
+ require 'cgi'
3
+ require 'net/http'
4
+ require 'ym4r/google_maps/geocoding'
5
+
6
+ module VoteSmart
7
+
8
+ class Common
9
+
10
+ class << self
11
+ attr_reader :attribute_map
12
+ end
13
+
14
+ def initialize attributes = {}
15
+ update_attributes attributes
16
+ end
17
+
18
+ def self.set_attribute_map map
19
+ @attribute_map = map
20
+ end
21
+
22
+ def update_attributes attributes
23
+ map = self.class.attribute_map
24
+ raise "map not set over-ride needed" unless map
25
+
26
+ attributes.each do |key, value|
27
+ if key.kind_of?(Symbol)
28
+ send("#{key}=", value)
29
+ else
30
+ map_to = map[key]
31
+ send("#{map_to}=", value) if map_to
32
+ end
33
+ end
34
+ end
35
+
36
+ def self.response_child response, *children
37
+ for child in children
38
+ response = response[child] if response
39
+ end
40
+
41
+ response || {}
42
+ end
43
+
44
+ def self.request(api_method, params = {})
45
+ url = construct_url api_method, params
46
+
47
+ json = get_json_data(url)
48
+
49
+ if json['error'] and json['error']['errorMessage'] == 'Authorization failed'
50
+ raise RequestFailed.new(json['error']['errorMessage'])
51
+ end
52
+
53
+ json
54
+ end
55
+
56
+ # Constructs a VoteSmart API-friendly URL
57
+ def self.construct_url(api_method, params = {})
58
+ "#{API_URL}#{api_method}?key=#{VoteSmart.api_key}&o=#{API_FORMAT}#{hash2get(params)}"
59
+ end
60
+
61
+ # Converts a hash to a GET string
62
+ def self.hash2get(h)
63
+
64
+ get_string = ""
65
+
66
+ h.each_pair do |key, value|
67
+ get_string += "&#{key.to_s}=#{CGI::escape(value.to_s)}" unless value.nil?
68
+ end
69
+
70
+ get_string
71
+
72
+ end # def hash2get
73
+
74
+
75
+ # Use the Net::HTTP and JSON libraries to make the API call
76
+ #
77
+ # Usage:
78
+ # District.get_json_data("http://someurl.com") # returns Hash of data or nil
79
+ def self.get_json_data(url)
80
+ response = Net::HTTP.get_response(URI.parse(url))
81
+ if response.class == Net::HTTPOK
82
+ result = JSON.parse(response.body)
83
+ else
84
+ raise RequestFailed.new("Request was not OK: #{response.class}: #{response.body}")
85
+ end
86
+
87
+ end # self.get_json_data
88
+
89
+ end
90
+
91
+ end
@@ -0,0 +1,36 @@
1
+ module VoteSmart
2
+
3
+ class District < Common
4
+
5
+ attr_accessor :id, :name, :office_id, :state_id
6
+
7
+ set_attribute_map "districtId" => :id, "name" => :name, "officeId" => :office_id, "stateId" => :state_id
8
+
9
+ def number
10
+ return unless name
11
+
12
+ scan = name.scan(/District (\d[0-9]*)/) || []
13
+ scan = scan.first || []
14
+ num = scan.first
15
+ num ? num.to_i : nil
16
+ end
17
+
18
+ def official
19
+ @official ||= Official.find_by_district(self)
20
+ end
21
+
22
+ def self.find_all_by_office_and_state office, state
23
+ find_all_by_office_id_and_state_id(office.id, state.id)
24
+ end
25
+
26
+ def self.find_all_by_office_id_and_state_id office_id, state_id
27
+ response_child(get_by_office_state(office_id, state_id), "districtList", "district").collect {|attributes| District.new(attributes)}
28
+ end
29
+
30
+ # Returns districts service the office and state provided
31
+ def self.get_by_office_state office_id, state_id = 'NA', district_name = ''
32
+ request("District.getByOfficeState", "officeId" => office_id, "stateId" => state_id, "districtName" => district_name)
33
+ end
34
+
35
+ end
36
+ end