diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9008ca4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +pkg/ +Gemfile.lock diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..d024291 --- /dev/null +++ b/Gemfile @@ -0,0 +1,16 @@ +source "http://www.rubygems.org" + +gem 'patron', '>=0.4.6' + +# Add dependencies to develop your gem here. +# Include everything needed to run rake, tests, features, etc. +group :development do + gem "rspec", "< 2" + gem "rdoc", "~> 3.12" + gem "bundler", "~> 1.0.0" + gem "jeweler", "~> 1.8.3" + + # optional runtime dependencies, required for tests + gem 'ym4r' + gem 'mcll4r' +end diff --git a/History.txt b/History.txt index 55fba27..535e167 100644 --- a/History.txt +++ b/History.txt @@ -1,3 +1,35 @@ +== 0.3.3 2010-10-06 + +* 1 minor enhancement: + * Rate-limit parallel requests to 20/second. Note that higher rates is acceptable for off hours, as per Clinton Adams, 2010-10-06: "Could you lower that to 20/second if it's occurring between 8AM-8PM EST? In the off hours 200/second is fine." + +== 0.3.2 2010-10-05 + +* 1 major enhancement + * Add support for parallel requests via Typhoeus. Currently only available on Candiate.get_bio and Rating.get_candidate_rating + +== 0.3.1 2010-06-19 + +* 2 major enhancements: + * Add Candidate.get_by_zip, District.get_by_zip, Election.get_by_zip and Official.get_by_zip (dwillis) + * Add Official.get_statewide (empact) + +== 0.3.0 2010-03-25 + +* 3 major enhancement: + * Rename the project/gem to just 'votesmart' + * Move http access to Patron for performance + * Add retry logic on HTTP failure, with exponential backoff + +* 2 minor enhancements: + * Add Official#party to reflect the PVS officeParties data + * Sig descriptions: + * sometimes included UTF control characters for punctuation, which are now translated to ascii + * often started and ended in a quote, which is now avoided + +* 1 bug fix: + * Fix that Rating.get_sig_list would blow up on a bad parameter + == 0.0.1 2009-02-10 * 1 major enhancement: diff --git a/Rakefile b/Rakefile index 58f8b0b..3b9b9b3 100644 --- a/Rakefile +++ b/Rakefile @@ -1,26 +1,38 @@ %w[rubygems rake rake/clean fileutils].each { |f| require f } -require 'spec' -require 'spec/rake/spectask' -require File.dirname(__FILE__) + '/lib/ruby-votesmart' -begin - require 'jeweler' - Jeweler::Tasks.new do |s| - s.name = "ruby-votesmart" - s.summary = "A wrapper for the Project Vote Smart API" - s.email = "dancunning@gmail.com" - s.homepage = "http://github.com/netroots/ruby-votesmart" - s.description = "A wrapper for the Project Vote Smart API" - s.authors = ["Dan Cunning"] - end -rescue LoadError - puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com" +require 'jeweler' +Jeweler::Tasks.new do |s| + s.name = "votesmart" + s.summary = "A wrapper for the Project Vote Smart API" + s.email = "ben.woosley@gmail.com" + s.homepage = "http://github.com/Empact/votesmart" + s.description = "A wrapper for the Project Vote Smart API" + s.authors = ["Dan Cunning", "Ben Woosley"] end +Jeweler::GemcutterTasks.new Dir['tasks/**/*.rake'].each { |t| load t } -Spec::Rake::SpecTask.new +require 'spec/rake/spectask' +Spec::Rake::SpecTask.new(:spec) do |spec| + spec.libs << 'lib' << 'spec' + spec.spec_files = FileList['spec/**/*_spec.rb'] +end -#Cucumber::Rake::Task.new +Spec::Rake::SpecTask.new(:rcov) do |spec| + spec.libs << 'lib' << 'spec' + spec.pattern = 'spec/**/*_spec.rb' + spec.rcov = true +end -#task :default => [:features] \ No newline at end of file +task :default => :spec + +require 'rdoc/task' +RDoc::Task.new do |rdoc| + version = File.exist?('VERSION') ? File.read('VERSION') : "" + + rdoc.rdoc_dir = 'rdoc' + rdoc.title = "votesmart #{version}" + rdoc.rdoc_files.include('README*') + rdoc.rdoc_files.include('lib/**/*.rb') +end diff --git a/VERSION.yml b/VERSION.yml index e4ba134..e04aeaf 100644 --- a/VERSION.yml +++ b/VERSION.yml @@ -1,4 +1,5 @@ ---- +--- :major: 0 -:minor: 2 -:patch: 4 +:minor: 4 +:patch: 1 +:build: diff --git a/lib/mcll4r/MIT-LICENSE b/lib/mcll4r/MIT-LICENSE deleted file mode 100755 index e050db1..0000000 --- a/lib/mcll4r/MIT-LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2008 Mobile Commons - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/mcll4r/README b/lib/mcll4r/README deleted file mode 100644 index 73a86f8..0000000 --- a/lib/mcll4r/README +++ /dev/null @@ -1,19 +0,0 @@ -=== mcll4r - -- [Code on GitHub](http://github.com/mcommons/mcll4r) - -=== Description - -Ruby client for Mobile Commons Legislative Lookup API - -Based on the API described at http://congress.mcommons.com - - -=== Authors - -- Maintained by [Benjamin Stein](mailto:ben@mcommons.com), [Mal McKay](mailto:mal@mcommons.com) & [Nathan Woodhull](mailto:nathan@mcommons.com) - -=== License - -Copyright (c) 2008 Mobile Commons -See MIT-LICENSE in this directory. diff --git a/lib/mcll4r/mcll4r.rb b/lib/mcll4r/mcll4r.rb deleted file mode 100644 index 00e237a..0000000 --- a/lib/mcll4r/mcll4r.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'rubygems' -require 'httparty' - -class Mcll4r - include HTTParty - base_uri "http://congress.mcommons.com" - format :xml - - def district_lookup(lat, lng) - filter_for_errors self.class.get("/districts/lookup.xml", :query=>{:lat=>lat, :lng=>lng}) - end - -private - - def filter_for_errors(hash) - if hash['response']['error'] - raise DistrictNotFound.new(hash['response']['error']) - end - hash - end - -end - -class DistrictNotFound < Exception; end \ No newline at end of file diff --git a/lib/mcll4r/mcll4r_test.rb b/lib/mcll4r/mcll4r_test.rb deleted file mode 100644 index 17c98fa..0000000 --- a/lib/mcll4r/mcll4r_test.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'mcll4r' -require 'test/unit' - -class Mcll4rTest < Test::Unit::TestCase - - def setup - @mcll4r = Mcll4r.new - end - - def test_assert_we_get_back_correct_district_data - expected = { - "response" => { - "state_upper" => { "district" => "029", "display_name" => "TX 29th", "state" => "TX" }, - "federal" => { "district" => "16", "display_name" => "TX 16th", "state" => "TX" }, - "state_lower" => { "district" => "077", "display_name" => "TX 77th", "state" => "TX" }, - "lng" => "-106.490969", - "lat" => "31.76321" - } - } - assert_equal expected, @mcll4r.district_lookup(31.76321, -106.490969) - end - - def test_assert_raise_on_error - assert_raise DistrictNotFound do - @mcll4r.district_lookup(nil,nil) - end - end - - def test_assert_raise_on_district_not_found - assert_raise DistrictNotFound do - @mcll4r.district_lookup( 1.0, 1.0 ) - end - end - -end diff --git a/lib/ruby-votesmart.rb b/lib/ruby-votesmart.rb deleted file mode 100644 index c72be2b..0000000 --- a/lib/ruby-votesmart.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'rubygems' -require 'ym4r/google_maps/geocoding' -require 'active_support' -include Ym4r::GoogleMaps - -module VoteSmart - API_URL = "http://api.votesmart.org/" - API_FORMAT = "JSON" - - mattr_accessor :api_key - - class RequestFailed < Exception; end -end - -VoteSmart.api_key = "key" - -require "#{File.dirname(__FILE__)}/mcll4r/mcll4r.rb" -require "#{File.dirname(__FILE__)}/vote_smart/common.rb" - -Dir["#{File.dirname(__FILE__)}/vote_smart/*.rb"].each do |source_file| - require source_file unless source_file == "#{File.dirname(__FILE__)}/vote_smart/common.rb" -end diff --git a/lib/vote_smart.rb b/lib/vote_smart.rb new file mode 100644 index 0000000..1839300 --- /dev/null +++ b/lib/vote_smart.rb @@ -0,0 +1,18 @@ +module VoteSmart + API_URL = "http://api.votesmart.org/" + API_FORMAT = "JSON" + + class << self + attr_accessor :api_key + end + + class RequestFailed < StandardError; end +end + +VoteSmart.api_key = "key" + +require "#{File.dirname(__FILE__)}/vote_smart/common" + +Dir["#{File.dirname(__FILE__)}/vote_smart/*.rb"].each do |source_file| + require source_file unless source_file == "#{File.dirname(__FILE__)}/vote_smart/common.rb" +end diff --git a/lib/vote_smart/candidate.rb b/lib/vote_smart/candidate.rb index e6b4e31..04ea736 100644 --- a/lib/vote_smart/candidate.rb +++ b/lib/vote_smart/candidate.rb @@ -26,7 +26,10 @@ def self.get_by_election election_id def self.get_by_district district_id, election_year = nil request("Candidates.getByDistrict", "districtId" => district_id, "electionYear" => election_year) end - + + # Returns candidates in the provided zip code, with optional zip+4 and stage + def self.get_by_zip zip5, election_year=nil, zip4=nil, stage_id=nil + request("Candidates.getByZip", "zip5" => zip5, "electionYear" => election_year, "zip4" => zip4, "stageId" => stage_id) + end end - end diff --git a/lib/vote_smart/candidate_bio.rb b/lib/vote_smart/candidate_bio.rb index 5d8ba8f..9630d2e 100644 --- a/lib/vote_smart/candidate_bio.rb +++ b/lib/vote_smart/candidate_bio.rb @@ -3,8 +3,8 @@ module VoteSmart class CandidateBio < Common # Returns basic bio details on a candidate - def self.get_bio can_id - request("CandidateBio.getBio", "candidateId" => can_id) + def self.get_bio can_id, &block + request("CandidateBio.getBio", {"candidateId" => can_id}, &block) end # diff --git a/lib/vote_smart/common.rb b/lib/vote_smart/common.rb index c172c39..9ea24f9 100644 --- a/lib/vote_smart/common.rb +++ b/lib/vote_smart/common.rb @@ -1,24 +1,117 @@ require 'json' require 'cgi' -require 'net/http' -require 'ym4r/google_maps/geocoding' +require 'patron' module VoteSmart - + module ParallelQueries + def session + @session ||= Typhoeus::Hydra.new(:max_concurrency => 20) + end + + def request(api_method, params = {}) + raise "on_complete block required" unless block_given? + + url = construct_url api_method, params + + new_request = Typhoeus::Request.new(url) + new_request.on_complete do |response| + next if response.body.size < 2 + json = JSON.parse(response.body) + + if json['error'] and json['error']['errorMessage'] == 'Authorization failed' + raise RequestFailed.new(json['error']['errorMessage']) + end + + yield json + end + session.queue new_request + end + + def run + session.run + end + end + class Common class << self attr_reader :attribute_map + + def parallelize! + gem 'typhoeus' + extend ParallelQueries + end + + def set_attribute_map map + @attribute_map = map + end + + def response_child response, *children + for child in children + response = response[child] if response + end + + response || {} + end + + # Constructs a VoteSmart API-friendly URL + def construct_url(api_method, params = {}) + "#{API_URL}#{api_method}?key=#{VoteSmart.api_key}&o=#{API_FORMAT}#{hash2get(params)}" + end + + # Converts a hash to a GET string + def hash2get(h) + h.map do |(key, value)| + "&#{key.to_s}=#{CGI::escape(value.to_s)}" if value + end.compact.join + end + + def request(api_method, params = {}) + url = construct_url api_method, params + + json = get_json_data(url) + + if json['error'] and json['error']['errorMessage'] == 'Authorization failed' + raise RequestFailed.new(json['error']['errorMessage']) + end + + json + end + + def session + @session ||= Patron::Session.new.tap do |session| + session.timeout = 15 + end + end + + # Use the Net::HTTP and JSON libraries to make the API call + # + # Usage: + # District.get_json_data("http://someurl.com") # returns Hash of data or nil + def get_json_data(url) + response = session.get(url) + if response.status != 200 + raise RequestFailed.new("Request was not OK: #{response.class}: #{url} #{response.body}") + end + JSON.parse(response.body) + rescue + @json_retries ||= 0 + if @json_retries < 5 + puts "Retrying #{url}" + sleep(2 ** @json_retries) + @json_retries += 1 + retry + end + raise + ensure + @json_retries = 0 + end end def initialize attributes = {} update_attributes attributes end - def self.set_attribute_map map - @attribute_map = map - end - def update_attributes attributes map = self.class.attribute_map raise "map not set over-ride needed" unless map @@ -32,65 +125,7 @@ def update_attributes attributes end end end - - def self.response_child response, *children - for child in children - response = response[child] if response - end - - response || {} - end - - def self.response_child_array response, *children - child = response_child response, *children - child.kind_of?(Array) ? child : [child] - end - - def self.request(api_method, params = {}) - url = construct_url api_method, params - - json = get_json_data(url) - - if json['error'] and json['error']['errorMessage'] == 'Authorization failed' - raise RequestFailed.new(json['error']['errorMessage']) - end - - json - end - - # Constructs a VoteSmart API-friendly URL - def self.construct_url(api_method, params = {}) - "#{API_URL}#{api_method}?key=#{VoteSmart.api_key}&o=#{API_FORMAT}#{hash2get(params)}" - end - - # Converts a hash to a GET string - def self.hash2get(h) - - get_string = "" - - h.each_pair do |key, value| - get_string += "&#{key.to_s}=#{CGI::escape(value.to_s)}" unless value.nil? - end - - get_string - - end # def hash2get - - - # Use the Net::HTTP and JSON libraries to make the API call - # - # Usage: - # District.get_json_data("http://someurl.com") # returns Hash of data or nil - def self.get_json_data(url) - response = Net::HTTP.get_response(URI.parse(url)) - if response.class == Net::HTTPOK - result = JSON.parse(response.body) - else - raise RequestFailed.new("Request was not OK: #{response.class}: #{url} #{response.body}") - end - - end # self.get_json_data - + end end diff --git a/lib/vote_smart/district.rb b/lib/vote_smart/district.rb index 25f692c..625354e 100644 --- a/lib/vote_smart/district.rb +++ b/lib/vote_smart/district.rb @@ -33,6 +33,10 @@ def self.find_all_by_office_id_and_state_id office_id, state_id def self.get_by_office_state office_id, state_id = 'NA', district_name = '' request("District.getByOfficeState", "officeId" => office_id, "stateId" => state_id, "districtName" => district_name) end - + + # Returns districts in the provided zip code, with optional zip+4 + def self.get_by_zip zip5, zip4=nil + request("District.getByZip", "zip5" => zip5, "zip4" => zip4) + end end end diff --git a/lib/vote_smart/election.rb b/lib/vote_smart/election.rb index 66d05f6..5d2004f 100644 --- a/lib/vote_smart/election.rb +++ b/lib/vote_smart/election.rb @@ -17,5 +17,9 @@ def self.get_stage_candidates election_id, stage_id, party = "" request("Election.getStageCandidates", "electionId" => election_id, "stageId" => stage_id, "party" => party) end + # Returns elections in the provided zip code, with optional zip+4 and election_year + def self.get_by_zip zip5, zip4=nil, election_year=nil + request("Election.getElectionByZip", "zip5" => zip5, "zip4" => zip4, "year" => election_year) + end end end diff --git a/lib/vote_smart/office.rb b/lib/vote_smart/office.rb index dd6955d..3f0cfe8 100644 --- a/lib/vote_smart/office.rb +++ b/lib/vote_smart/office.rb @@ -67,7 +67,7 @@ def self.get_levels # Returns a list of offices by office type def self.get_offices_by_type type_id - request("Office.getOfficesByType", "typeId" => type_id) + request("Office.getOfficesByType", "officeTypeId" => type_id) end # Returns a list of offices by level of government @@ -77,7 +77,7 @@ def self.get_offices_by_level level_id # Returns a list of offices by office type and level of government def self.get_offices_by_type_level type_id, level_id - request("Office.getOfficesByType", "levelId" => level_id, "typeId" => type_id) + request("Office.getOfficesByTypeLevel", "levelId" => level_id, "officeTypeId" => type_id) end # Returns a list of offices by branch and level of government diff --git a/lib/vote_smart/official.rb b/lib/vote_smart/official.rb index 3081f56..b662f78 100644 --- a/lib/vote_smart/official.rb +++ b/lib/vote_smart/official.rb @@ -5,15 +5,18 @@ class Official < Common attr_accessor :id, :first_name, :nick_name, :middle_name, :last_name, :suffix, :title, :election_parties, :office_parties, :district_id, :district_name, :state_id - attr_accessor :district, :office, :office_id + attr_accessor :district, :office, :office_id, :party set_attribute_map "candidateId" => :id, "firstName" => :first_name, "nickName" => :nick_name, "middleName" => :middle_name, "lastName" => :last_name, "suffix" => :suffix, "title" => :title, "electionParties" => :election_parties, "officeDistrictId" => :district_id, - "officeDistrictName" => :district_name, "officeStateId" => :state_id + "officeDistrictName" => :district_name, "officeParties" => :party, "officeStateId" => :state_id def offices - Official.response_child_array(Address.get_office(self.id), "address", "office").collect {|office| CandidateOffice.new(office) } + offices = Official.response_child(Address.get_office(self.id), "address", "office") + (offices.is_a?(Array) ? offices : [offices]).collect {|office| + CandidateOffice.new(office) + } end def inspect @@ -44,10 +47,10 @@ def self.find_by_office_id_and_state_id office_id, state_id official end - - def self.find_all_by_address address, city, state, zip - placemark = Geocoding.get("#{address} #{city}, #{state} #{zip}").first + require 'ym4r/google_maps/geocoding' + + placemark = Ym4r::GoogleMaps::Geocoding.get("#{address} #{city}, #{state} #{zip}").first return [] unless placemark @@ -57,6 +60,8 @@ def self.find_all_by_address address, city, state, zip end def self.find_all_by_state_and_latitude_and_longitude state, latitude, longitude + require "mcll4r" + response = Mcll4r.new.district_lookup(latitude, longitude) response = response["response"] if response @@ -98,6 +103,9 @@ def self.find_state_legislators state_id, senate_district, house_district, assem officials end + def self.get_statewide(state_id = 'NA') + request("Officials.getStatewide", "stateId" => state_id) + end # Returns a list of incumbents that fit the criteria def self.get_by_office_state office_id, state_id = 'NA' @@ -123,6 +131,10 @@ def self.get_by_election election_id def self.get_by_district district_id request("Officials.getByDistrict", "districtId" => district_id) end - + + # Returns incumbents in the provided zip code, with optional zip+4 and stage + def self.get_by_zip zip5, election_year=nil, zip4=nil, stage_id=nil + request("Officials.getByZip", "zip5" => zip5, "electionYear" => election_year, "zip4" => zip4, "stageId" => stage_id) + end end end diff --git a/lib/vote_smart/rating.rb b/lib/vote_smart/rating.rb index 4763093..f563310 100644 --- a/lib/vote_smart/rating.rb +++ b/lib/vote_smart/rating.rb @@ -9,17 +9,34 @@ def self.get_categories state_id = nil # Returns a list of SIGs with ratings in category and state def self.get_sig_list category_id, state_id = 'NA' - request("Rating.getSigList", "category_id" => category_id, "stateId" => state_id) + request("Rating.getSigList", "categoryId" => category_id, "stateId" => state_id) end # Returns detailed SIG information def self.get_sig sig_id - request("Rating.getSig", "sigId" => sig_id) + result = request("Rating.getSig", "sigId" => sig_id) + # PVS return utf control codes for punction in descriptions + if result['sig']['description'].first == '"' + result['sig']['description'] = result['sig']['description'][1..-2] + end + result['sig']['description'] = result['sig']['description'].unpack("U*").map do |char| + case char + when 146 # 0092 + 39 + when 148, 147 # 0093,4 + 34 + when 150, 151 # 0096,7 + 45 + else + char + end + end.pack("U*") + result end # Returns an SIG's rating on a specific candidate - def self.get_candidate_rating candidate_id, sig_id - request("Rating.getCandidateRating", "candidateId" => candidate_id, "sigId" => sig_id) + def self.get_candidate_rating candidate_id, sig_id, &block + request("Rating.getCandidateRating", {"candidateId" => candidate_id, "sigId" => sig_id}, &block) end end diff --git a/lib/vote_smart/vote.rb b/lib/vote_smart/vote.rb index da9c53d..96605a7 100644 --- a/lib/vote_smart/vote.rb +++ b/lib/vote_smart/vote.rb @@ -37,9 +37,10 @@ def self.get_bills_by_year_state year, state_id = 'NA' request("Votes.getBillsByYearState", "year" => year, "stateId" => state_id) end + # Gets a list of bills that fit the criteria - def self.get_bills_by_official_year can_id, year - request("Votes.getBillsByOfficialYear", "candidateId" => can_id, "year" => year) + def self.get_bills_by_official_year_office can_id, year + request("Votes.getBillsByOfficialYearOffice", "candidateId" => can_id, "year" => year) end # Gets a list of bills that fit the criteria diff --git a/lib/votesmart.rb b/lib/votesmart.rb new file mode 100644 index 0000000..2b897f3 --- /dev/null +++ b/lib/votesmart.rb @@ -0,0 +1 @@ +require 'vote_smart' \ No newline at end of file diff --git a/ruby-votesmart.gemspec b/ruby-votesmart.gemspec deleted file mode 100644 index 8968fde..0000000 --- a/ruby-votesmart.gemspec +++ /dev/null @@ -1,29 +0,0 @@ -# -*- encoding: utf-8 -*- - -Gem::Specification.new do |s| - s.name = %q{ruby-votesmart} - s.version = "0.2.4" - - s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= - s.authors = ["Dan Cunning"] - s.date = %q{2009-02-17} - s.description = %q{A wrapper for the Project Vote Smart API} - s.email = %q{dancunning@gmail.com} - s.files = ["History.txt", "README.rdoc", "VERSION.yml", "lib/mcll4r", "lib/mcll4r/mcll4r.rb", "lib/mcll4r/mcll4r_test.rb", "lib/mcll4r/MIT-LICENSE", "lib/mcll4r/README", "lib/ruby-votesmart.rb", "lib/vote_smart", "lib/vote_smart/address.rb", "lib/vote_smart/candidate.rb", "lib/vote_smart/candidate_bio.rb", "lib/vote_smart/candidate_office.rb", "lib/vote_smart/committee.rb", "lib/vote_smart/common.rb", "lib/vote_smart/district.rb", "lib/vote_smart/election.rb", "lib/vote_smart/leadership.rb", "lib/vote_smart/local.rb", "lib/vote_smart/measure.rb", "lib/vote_smart/notes.rb", "lib/vote_smart/npat.rb", "lib/vote_smart/office.rb", "lib/vote_smart/official.rb", "lib/vote_smart/phone.rb", "lib/vote_smart/rating.rb", "lib/vote_smart/state.rb", "lib/vote_smart/vote.rb", "spec/responses", "spec/responses/Address.get_office.106446.js", "spec/responses/Address.get_office.1721.js", "spec/responses/authorization_failed.js", "spec/responses/District.get_by_office_state.7.GA.js", "spec/responses/District.get_by_office_state.8.GA.js", "spec/responses/District.get_by_office_state.9.GA.js", "spec/responses/Office.get_offices_by_type.C.js", "spec/responses/Office.get_offices_by_type.L.js", "spec/responses/Office.get_offices_by_type.P.js", "spec/responses/Office.get_offices_by_type.S.js", "spec/responses/Office.get_types.js", "spec/responses/Official.get_by_district.20451.js", "spec/responses/Official.get_by_district.20689.js", "spec/responses/Official.get_by_district.21397.js", "spec/responses/Official.get_by_district.21946.js", "spec/responses/Official.get_by_office_state.12.CO.js", "spec/responses/Official.get_by_office_state.12.GA.js", "spec/responses/Official.get_by_office_state.13.GA.js", "spec/responses/Official.get_by_office_state.33.GA.js", "spec/responses/Official.get_by_office_state.42.GA.js", "spec/responses/Official.get_by_office_state.44.GA.js", "spec/responses/Official.get_by_office_state.45.GA.js", "spec/responses/Official.get_by_office_state.53.GA.js", "spec/responses/State.get_state.GA.js", "spec/responses/State.get_state_ids.js", "spec/spec_helper.rb", "spec/vote_smart", "spec/vote_smart/district_spec.rb", "spec/vote_smart/office_spec.rb", "spec/vote_smart/official_spec.rb", "spec/vote_smart/state_spec.rb"] - s.has_rdoc = true - s.homepage = %q{http://github.com/netroots/ruby-votesmart} - s.rdoc_options = ["--inline-source", "--charset=UTF-8"] - s.require_paths = ["lib"] - s.rubygems_version = %q{1.3.1} - s.summary = %q{A wrapper for the Project Vote Smart API} - - if s.respond_to? :specification_version then - current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION - s.specification_version = 2 - - if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then - else - end - else - end -end diff --git a/spec/responses/Official.get_by_office_state.12.CO.js b/spec/responses/Official.get_by_office_state.12.CO.js index 055fe27..06e729e 100644 --- a/spec/responses/Official.get_by_office_state.12.CO.js +++ b/spec/responses/Official.get_by_office_state.12.CO.js @@ -1 +1 @@ -{"candidateList":{"generalInfo":{"title":"Project Vote Smart - Search Candidates","linkBack":"http:\/\/votesmart.org\/"},"candidate":{"electionDistrictName":"","lastName":"Suthers","candidateId":"29799","title":"Attorney General","officeDistrictName":"","nickName":"","electionYear":"","electionStatus":"","electionParties":"","middleName":"W.","firstName":"John","electionDistrictId":"","officeParties":"Republican","electionStateId":"","officeStateId":"CO","officeDistrictId":"","suffix":""}}} \ No newline at end of file +{"candidateList":{"generalInfo":{"title":"Project Vote Smart - Search Candidates","linkBack":"http://votesmart.org/"},"candidate":{"electionOfficeTypeId":"","electionOffice":"","lastName":"Suthers","candidateId":"29799","electionParties":"","title":"Attorney General","officeDistrictName":"","nickName":"","electionYear":"","electionStatus":"","middleName":"W.","officeId":"12","electionDistrictId":"","officeDistrictId":"","electionOfficeId":"","firstName":"John","officeTypeId":"S","suffix":"","officeName":"Attorney General","officeStateId":"CO","electionDistrictName":"","officeParties":"Republican","electionStateId":""}}} \ No newline at end of file diff --git a/spec/responses/Rating.get_sig.1863.js b/spec/responses/Rating.get_sig.1863.js new file mode 100644 index 0000000..5930ca9 --- /dev/null +++ b/spec/responses/Rating.get_sig.1863.js @@ -0,0 +1 @@ +{"sig":{"contactName":"","city":"Washington","address":"601 E Street Northwest","name":"AARP","generalInfo":{"title":"Project Vote Smart - Interest Group Ratings","linkBack":"http://votesmart.org/issue_rating.php"},"zip":"20049","stateId":"NA","url":"http://www.aarp.org","phone1":"888-687-2277","sigId":"1863","phone2":"202-434-3525","fax":"","parentId":"-1","description":"Founded in 1958, AARP is a nonprofit, nonpartisan membership organization that helps people 50 and over improve the quality of their lives. AARP works tirelessly to fulfill its vision: a society in which everyone ages with dignity and purpose, and in which AARP helps people fulfill their goals and dreams. AARP speaks with one voice - united by a common motto: \"To serve, not be served.\" ","email":"","state":"DC"}} \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4c3fa16..2149191 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,7 +1,6 @@ -require 'rubygems' require 'stringio' require 'spec' -require "#{File.dirname(__FILE__)}/../lib/ruby-votesmart.rb" +require "votesmart" class Spec::Example::ExampleGroup @@ -33,7 +32,7 @@ def stub_request_method klazz, request_method, args VoteSmart::State, VoteSmart::Vote].each do |klazz| - request_methods = klazz.methods.collect { |method| method if method.starts_with?("get_") }.compact + request_methods = klazz.methods.collect { |method| method if method.to_s.start_with?("get_") }.compact request_methods.each do |request_method| diff --git a/spec/vote_smart/district_spec.rb b/spec/vote_smart/district_spec.rb index abecc35..9326828 100644 --- a/spec/vote_smart/district_spec.rb +++ b/spec/vote_smart/district_spec.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/../spec_helper' +require 'spec_helper' module VoteSmart describe District do diff --git a/spec/vote_smart/office/type_spec.rb b/spec/vote_smart/office/type_spec.rb new file mode 100644 index 0000000..bcd4de8 --- /dev/null +++ b/spec/vote_smart/office/type_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +module VoteSmart + describe Office::Type do + + describe "all" do + + def do_find + Office::Type.all + end + + it_should_find :count => 10, :first => {:name => "Presidential and Cabinet", :id => "P"}, + :last => {:name => "Local Executive", :id => "M"} + + end + + describe "find_by_name" do + + def do_find + Office::Type.find_by_name("State Legislature") + end + + it_should_find :item => {:name => "State Legislature", :id => "L"} + + end + + describe "offices" do + + def do_find + Office::Type.find_by_name("Presidential and Cabinet").offices + end + + it_should_find :count => 17, :first => {:name => "President", :id => "1"}, + :last => {:name => "Vice President", :id => "2"} + + end + + describe "offices_by_name" do + + def do_find + Office::Type.find_by_name("Presidential and Cabinet").offices_by_name(["President", "Vice President"]) + end + + it_should_find :count => 2, :first => {:name => "President", :id => "1"}, + :last => {:name => "Vice President", :id => "2"} + + end + end +end diff --git a/spec/vote_smart/office_spec.rb b/spec/vote_smart/office_spec.rb index 6b76588..ee87b2c 100644 --- a/spec/vote_smart/office_spec.rb +++ b/spec/vote_smart/office_spec.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/../spec_helper' +require 'spec_helper' module VoteSmart describe Office do @@ -47,50 +47,4 @@ def do_find end end - - describe Office::Type do - - describe "all" do - - def do_find - Office::Type.all - end - - it_should_find :count => 10, :first => {:name => "Presidential and Cabinet", :id => "P"}, - :last => {:name => "Local Executive", :id => "M"} - - end - - describe "find_by_name" do - - def do_find - Office::Type.find_by_name("State Legislature") - end - - it_should_find :item => {:name => "State Legislature", :id => "L"} - - end - - describe "offices" do - - def do_find - Office::Type.find_by_name("Presidential and Cabinet").offices - end - - it_should_find :count => 17, :first => {:name => "President", :id => "1"}, - :last => {:name => "Vice President", :id => "2"} - - end - - describe "offices_by_name" do - - def do_find - Office::Type.find_by_name("Presidential and Cabinet").offices_by_name(["President", "Vice President"]) - end - - it_should_find :count => 2, :first => {:name => "President", :id => "1"}, - :last => {:name => "Vice President", :id => "2"} - - end - end end \ No newline at end of file diff --git a/spec/vote_smart/official_spec.rb b/spec/vote_smart/official_spec.rb index b400365..cb667fa 100644 --- a/spec/vote_smart/official_spec.rb +++ b/spec/vote_smart/official_spec.rb @@ -1,19 +1,17 @@ -require File.dirname(__FILE__) + '/../spec_helper' +require 'spec_helper' module VoteSmart describe Official do describe "find_by_district_id" do - def do_find Official.find_by_district_id("20451") end - it_should_find :item => {:last_name => "Isakson", :id => "1721", :district_id => "20451"} + it_should_find :item => {:last_name => "Isakson", :id => "1721", :district_id => "20451", :party => 'Republican'} end describe "multiple offices" do - def do_find @official = Official.find_by_district_id("20451") @official.offices @@ -24,7 +22,6 @@ def do_find end describe "one office" do - def do_find @official = Official.find_by_district_id("21397") @official.offices @@ -34,31 +31,29 @@ def do_find end describe "find_by_office_id_and_state_id" do - def do_find Official.find_by_office_id_and_state_id("12", "CO") end it_should_find :item => {:last_name => "Suthers", :id => "29799", :office_id => "12", :state_id => "CO"} - end describe "find by address" do - before :each do + require 'mcll4r' + require 'ym4r/google_maps/geocoding' + mcll4r = {"response" => {"state_lower" => {"district" => "1"}, "state_upper" => {"district" => "2"}}} - Geocoding.should_receive(:get).once.and_return([mock("placemark", :latitude => 2, :longitude => 10)]) + Ym4r::GoogleMaps::Geocoding.should_receive(:get).once.and_return([mock("placemark", :latitude => 2, :longitude => 10)]) Mcll4r.should_receive(:new).once.and_return(mock("mcll4r", :district_lookup => mcll4r)) end def do_find - Official.find_all_by_address "123 fake st", "atlanta", "ga", "30303" + Official.find_all_by_address "123 fake st", "atlanta", "GA", "30303" end it_should_find :count => 5, :first => {:last_name => "Handel", :title => "Secretary"}, :last => {:last_name => "Jackson", :title => "Senator"} - end end - -end \ No newline at end of file +end diff --git a/spec/vote_smart/rating_spec.rb b/spec/vote_smart/rating_spec.rb new file mode 100644 index 0000000..70269cc --- /dev/null +++ b/spec/vote_smart/rating_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe VoteSmart::Rating do + describe ".get_sig" do + it "should properly handle UTF characters" do + VoteSmart::Rating.get_sig('1863')['sig']['description'].unpack("U*").should_not include(148) + end + + it "shouldn't have a quoted description" do + description = VoteSmart::Rating.get_sig('1863')['sig']['description'] + description[0].should_not == '"' + description[-1].should_not == '"' + end + end +end diff --git a/spec/vote_smart/state_spec.rb b/spec/vote_smart/state_spec.rb index bea9f90..5222f81 100644 --- a/spec/vote_smart/state_spec.rb +++ b/spec/vote_smart/state_spec.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/../spec_helper' +require 'spec_helper' module VoteSmart describe State do diff --git a/tasks/spec_json.rake b/tasks/spec_json.rake index a342263..08cf9d7 100644 --- a/tasks/spec_json.rake +++ b/tasks/spec_json.rake @@ -17,6 +17,7 @@ namespace :spec do end task :json do + require "#{File.dirname(__FILE__)}/../lib/vote_smart" # response = VoteSmart::State.get_state("GA") # save_json_response "authorization_failed", response @@ -57,7 +58,7 @@ namespace :spec do # save_json VoteSmart::Address, :get_office, "106446" - save_json VoteSmart::Official, :get_by_office_state, "12", "CO" + # save_json VoteSmart::Official, :get_by_office_state, "12", "CO" + save_json VoteSmart::Rating, :get_sig, "1863" end - end \ No newline at end of file diff --git a/votesmart.gemspec b/votesmart.gemspec new file mode 100644 index 0000000..374a593 --- /dev/null +++ b/votesmart.gemspec @@ -0,0 +1,121 @@ +# Generated by jeweler +# DO NOT EDIT THIS FILE DIRECTLY +# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = "votesmart" + s.version = "0.4.1" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["Dan Cunning", "Ben Woosley"] + s.date = "2012-03-26" + s.description = "A wrapper for the Project Vote Smart API" + s.email = "ben.woosley@gmail.com" + s.extra_rdoc_files = [ + "README.rdoc" + ] + s.files = [ + "Gemfile", + "History.txt", + "README.rdoc", + "Rakefile", + "VERSION.yml", + "lib/vote_smart.rb", + "lib/vote_smart/address.rb", + "lib/vote_smart/candidate.rb", + "lib/vote_smart/candidate_bio.rb", + "lib/vote_smart/candidate_office.rb", + "lib/vote_smart/committee.rb", + "lib/vote_smart/common.rb", + "lib/vote_smart/district.rb", + "lib/vote_smart/election.rb", + "lib/vote_smart/leadership.rb", + "lib/vote_smart/local.rb", + "lib/vote_smart/measure.rb", + "lib/vote_smart/notes.rb", + "lib/vote_smart/npat.rb", + "lib/vote_smart/office.rb", + "lib/vote_smart/official.rb", + "lib/vote_smart/phone.rb", + "lib/vote_smart/rating.rb", + "lib/vote_smart/state.rb", + "lib/vote_smart/vote.rb", + "lib/votesmart.rb", + "script/autospec", + "script/console", + "script/destroy", + "script/generate", + "script/is_gem_built", + "spec/responses/Address.get_office.106446.js", + "spec/responses/Address.get_office.1721.js", + "spec/responses/District.get_by_office_state.7.GA.js", + "spec/responses/District.get_by_office_state.8.GA.js", + "spec/responses/District.get_by_office_state.9.GA.js", + "spec/responses/Office.get_offices_by_type.C.js", + "spec/responses/Office.get_offices_by_type.L.js", + "spec/responses/Office.get_offices_by_type.P.js", + "spec/responses/Office.get_offices_by_type.S.js", + "spec/responses/Office.get_types.js", + "spec/responses/Official.get_by_district.20451.js", + "spec/responses/Official.get_by_district.20689.js", + "spec/responses/Official.get_by_district.21397.js", + "spec/responses/Official.get_by_district.21946.js", + "spec/responses/Official.get_by_office_state.12.CO.js", + "spec/responses/Official.get_by_office_state.12.GA.js", + "spec/responses/Official.get_by_office_state.13.GA.js", + "spec/responses/Official.get_by_office_state.33.GA.js", + "spec/responses/Official.get_by_office_state.42.GA.js", + "spec/responses/Official.get_by_office_state.44.GA.js", + "spec/responses/Official.get_by_office_state.45.GA.js", + "spec/responses/Official.get_by_office_state.53.GA.js", + "spec/responses/Rating.get_sig.1863.js", + "spec/responses/State.get_state.GA.js", + "spec/responses/State.get_state_ids.js", + "spec/responses/authorization_failed.js", + "spec/spec_helper.rb", + "spec/vote_smart/district_spec.rb", + "spec/vote_smart/office/type_spec.rb", + "spec/vote_smart/office_spec.rb", + "spec/vote_smart/official_spec.rb", + "spec/vote_smart/rating_spec.rb", + "spec/vote_smart/state_spec.rb", + "tasks/spec_json.rake", + "votesmart.gemspec" + ] + s.homepage = "http://github.com/Empact/votesmart" + s.require_paths = ["lib"] + s.rubygems_version = "1.8.11" + s.summary = "A wrapper for the Project Vote Smart API" + + if s.respond_to? :specification_version then + s.specification_version = 3 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + s.add_runtime_dependency(%q, [">= 0.4.6"]) + s.add_development_dependency(%q, ["< 2"]) + s.add_development_dependency(%q, ["~> 3.12"]) + s.add_development_dependency(%q, ["~> 1.0.0"]) + s.add_development_dependency(%q, ["~> 1.8.3"]) + s.add_development_dependency(%q, [">= 0"]) + s.add_development_dependency(%q, [">= 0"]) + else + s.add_dependency(%q, [">= 0.4.6"]) + s.add_dependency(%q, ["< 2"]) + s.add_dependency(%q, ["~> 3.12"]) + s.add_dependency(%q, ["~> 1.0.0"]) + s.add_dependency(%q, ["~> 1.8.3"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + end + else + s.add_dependency(%q, [">= 0.4.6"]) + s.add_dependency(%q, ["< 2"]) + s.add_dependency(%q, ["~> 3.12"]) + s.add_dependency(%q, ["~> 1.0.0"]) + s.add_dependency(%q, ["~> 1.8.3"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + end +end +