votesmart 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/.gitignore +1 -0
  2. data/History.txt +20 -0
  3. data/README.rdoc +48 -0
  4. data/Rakefile +45 -0
  5. data/VERSION.yml +4 -0
  6. data/lib/mcll4r/MIT-LICENSE +20 -0
  7. data/lib/mcll4r/README +19 -0
  8. data/lib/mcll4r/mcll4r.rb +24 -0
  9. data/lib/mcll4r/mcll4r_test.rb +35 -0
  10. data/lib/vote_smart.rb +22 -0
  11. data/lib/vote_smart/address.rb +42 -0
  12. data/lib/vote_smart/candidate.rb +32 -0
  13. data/lib/vote_smart/candidate_bio.rb +16 -0
  14. data/lib/vote_smart/candidate_office.rb +15 -0
  15. data/lib/vote_smart/committee.rb +23 -0
  16. data/lib/vote_smart/common.rb +97 -0
  17. data/lib/vote_smart/district.rb +38 -0
  18. data/lib/vote_smart/election.rb +21 -0
  19. data/lib/vote_smart/leadership.rb +16 -0
  20. data/lib/vote_smart/local.rb +21 -0
  21. data/lib/vote_smart/measure.rb +16 -0
  22. data/lib/vote_smart/notes.rb +11 -0
  23. data/lib/vote_smart/npat.rb +11 -0
  24. data/lib/vote_smart/office.rb +119 -0
  25. data/lib/vote_smart/official.rb +131 -0
  26. data/lib/vote_smart/phone.rb +12 -0
  27. data/lib/vote_smart/rating.rb +43 -0
  28. data/lib/vote_smart/state.rb +29 -0
  29. data/lib/vote_smart/vote.rb +70 -0
  30. data/lib/votesmart.rb +1 -0
  31. data/script/autospec +6 -0
  32. data/script/console +10 -0
  33. data/script/destroy +14 -0
  34. data/script/generate +14 -0
  35. data/script/is_gem_built +40 -0
  36. data/spec/responses/Address.get_office.106446.js +1 -0
  37. data/spec/responses/Address.get_office.1721.js +1 -0
  38. data/spec/responses/District.get_by_office_state.7.GA.js +1 -0
  39. data/spec/responses/District.get_by_office_state.8.GA.js +1 -0
  40. data/spec/responses/District.get_by_office_state.9.GA.js +1 -0
  41. data/spec/responses/Office.get_offices_by_type.C.js +1 -0
  42. data/spec/responses/Office.get_offices_by_type.L.js +1 -0
  43. data/spec/responses/Office.get_offices_by_type.P.js +1 -0
  44. data/spec/responses/Office.get_offices_by_type.S.js +1 -0
  45. data/spec/responses/Office.get_types.js +1 -0
  46. data/spec/responses/Official.get_by_district.20451.js +1 -0
  47. data/spec/responses/Official.get_by_district.20689.js +1 -0
  48. data/spec/responses/Official.get_by_district.21397.js +1 -0
  49. data/spec/responses/Official.get_by_district.21946.js +1 -0
  50. data/spec/responses/Official.get_by_office_state.12.CO.js +1 -0
  51. data/spec/responses/Official.get_by_office_state.12.GA.js +1 -0
  52. data/spec/responses/Official.get_by_office_state.13.GA.js +1 -0
  53. data/spec/responses/Official.get_by_office_state.33.GA.js +1 -0
  54. data/spec/responses/Official.get_by_office_state.42.GA.js +1 -0
  55. data/spec/responses/Official.get_by_office_state.44.GA.js +1 -0
  56. data/spec/responses/Official.get_by_office_state.45.GA.js +1 -0
  57. data/spec/responses/Official.get_by_office_state.53.GA.js +1 -0
  58. data/spec/responses/Rating.get_sig.1863.js +1 -0
  59. data/spec/responses/State.get_state.GA.js +1 -0
  60. data/spec/responses/State.get_state_ids.js +1 -0
  61. data/spec/responses/authorization_failed.js +1 -0
  62. data/spec/spec_helper.rb +98 -0
  63. data/spec/vote_smart/district_spec.rb +36 -0
  64. data/spec/vote_smart/office_spec.rb +96 -0
  65. data/spec/vote_smart/official_spec.rb +56 -0
  66. data/spec/vote_smart/rating_spec.rb +15 -0
  67. data/spec/vote_smart/state_spec.rb +28 -0
  68. data/tasks/spec_json.rake +64 -0
  69. data/votesmart.gemspec +116 -0
  70. metadata +149 -0
@@ -0,0 +1 @@
1
+ pkg/
@@ -0,0 +1,20 @@
1
+ == 0.3.0 2010-03-25
2
+
3
+ * 3 major enhancement:
4
+ * Rename the project/gem to just 'votesmart'
5
+ * Move http access to Patron for performance
6
+ * Add retry logic on HTTP failure, with exponential backoff
7
+
8
+ * 2 minor enhancements:
9
+ * Add Official#party to reflect the PVS officeParties data
10
+ * Sig descriptions:
11
+ * sometimes included UTF control characters for punctuation, which are now translated to ascii
12
+ * often started and ended in a quote, which is now avoided
13
+
14
+ * 1 bug fix:
15
+ * Fix that Rating.get_sig_list would blow up on a bad parameter
16
+
17
+ == 0.0.1 2009-02-10
18
+
19
+ * 1 major enhancement:
20
+ * Initial release
@@ -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.
@@ -0,0 +1,45 @@
1
+ %w[rubygems rake rake/clean fileutils].each { |f| require f }
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |s|
6
+ s.name = "votesmart"
7
+ s.summary = "A wrapper for the Project Vote Smart API"
8
+ s.email = "ben.woosley@gmail.com"
9
+ s.homepage = "http://github.com/Empact/votesmart"
10
+ s.description = "A wrapper for the Project Vote Smart API"
11
+ s.authors = ["Dan Cunning", "Ben Woosley"]
12
+ s.add_dependency 'patron', '>=0.4.6'
13
+ end
14
+ Jeweler::GemcutterTasks.new
15
+ rescue LoadError
16
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
17
+ end
18
+
19
+ Dir['tasks/**/*.rake'].each { |t| load t }
20
+
21
+ require 'spec/rake/spectask'
22
+ Spec::Rake::SpecTask.new(:spec) do |spec|
23
+ spec.libs << 'lib' << 'spec'
24
+ spec.spec_files = FileList['spec/**/*_spec.rb']
25
+ end
26
+
27
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.pattern = 'spec/**/*_spec.rb'
30
+ spec.rcov = true
31
+ end
32
+
33
+ task :spec => :check_dependencies
34
+
35
+ task :default => :spec
36
+
37
+ require 'rake/rdoctask'
38
+ Rake::RDocTask.new do |rdoc|
39
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
40
+
41
+ rdoc.rdoc_dir = 'rdoc'
42
+ rdoc.title = "votesmart #{version}"
43
+ rdoc.rdoc_files.include('README*')
44
+ rdoc.rdoc_files.include('lib/**/*.rb')
45
+ end
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 3
4
+ :patch: 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.
@@ -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,97 @@
1
+ require 'json'
2
+ require 'cgi'
3
+ require 'patron'
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
+ h.map do |(key, value)|
64
+ "&#{key.to_s}=#{CGI::escape(value.to_s)}" if value
65
+ end.compact.join
66
+ end
67
+
68
+ def self.session
69
+ @session ||= Patron::Session.new
70
+ end
71
+
72
+ # Use the Net::HTTP and JSON libraries to make the API call
73
+ #
74
+ # Usage:
75
+ # District.get_json_data("http://someurl.com") # returns Hash of data or nil
76
+ def self.get_json_data(url)
77
+ response = session.get(url)
78
+ if response.status != 200
79
+ raise RequestFailed.new("Request was not OK: #{response.class}: #{url} #{response.body}")
80
+ end
81
+ JSON.parse(response.body)
82
+ rescue
83
+ @json_retries ||= 0
84
+ if @json_retries < 5
85
+ puts "Retrying #{url}"
86
+ sleep(2 ** @json_retries)
87
+ @json_retries += 1
88
+ retry
89
+ end
90
+ raise
91
+ ensure
92
+ @json_retries = 0
93
+ end
94
+
95
+ end
96
+
97
+ end