module Daywalker
# Represents a legislator, either a Senator or Representative.
#
# They have the following attributes:
#
# +title+::
# The title held by a Legislator, as a Symbol, ether :senator or :representative
#
# +first_name+::
# Legislator's first name
#
# +middle_name+::
# Legislator's middle name
#
# +last_name+::
# Legislator's last name
#
# +name_suffix+::
# Legislator's suffix (Jr., III, etc)
#
# +nickname+::
# Preferred nickname of Legislator
#
# +party+::
# Legislator's party as a +Sybmol+, :democrat, :republican, or :independent.
#
# +state+::
# two-letter +String+ abbreviation of the Legislator's state.
#
# +district+::
# The district a Legislator represents. For Representatives, this is a +Fixnum+. For Senators, this is a +Symbol+, either :junior_seat or :senior_seat.
#
# +in_office+::
# +true+ if the Legislator is currently server, or false if the Legislator is no longer due to defeat/resignation/death/etc.
#
# +gender+::
# Legislator's gender as a +Symbol+, :male or :female
#
# +phone+::
# Legislator's Congressional office phone number
# # FIXME normalize this to phone_number
#
# +fax_number+::
# Legislator's Congressional office fax number
#
# +website_url+::
# URL of the Legislator's Congressional wesbite as a +String+
#
# +webform_url+::
# URL of the Legislator's web contact form as a +String+
#
# +email+::
# Legislator's email address
#
# +congress_office+::
# Legislator's Washington, DC Office Address
#
# +bioguide_id+::
# Legislator's ID assigned by the COngressional Biographical DIrectory (http://bioguide.congress.gov) and also used by the Washington Post and NY Times.
#
# +votesmart_id+::
# Legislator ID assigned by Project Vote Smart (http://www.votesmart.org).
#
# +fec_id+::
# Legislator's ID provided by the Federal Election Commission (http://fec.gov)
#
# +govtrack_id+::
# Legislator's ID provided by Govtrack.us (http://www.govtrack.us)
#
# +crp_id+::
# Legislator's ID provided by Center for Responsive Politics (http://opensecrets.org)
#
# +eventful_id+::
# Legislator's 'Performer ID' on http://eventful.com
#
# +congresspedia_url+::
# URL of the Legislator's Congresspedia entry (http://congresspedia.org)
#
# +twitter_id+::
# Legislator's ID on Twitter (http://twitter.com)
#
# +youtube_url+::
# URL of the Legislator's YouTube account (http://youtube.com) as a +String+
class Legislator < Base
include HappyMapper
tag 'legislator'
element 'district', TypeConverter, :tag => 'district', :parser => :district_to_sym_or_i
element 'title', TypeConverter, :parser => :title_abbr_to_sym
element 'eventful_id', String
element 'in_office', Boolean
element 'state', String
element 'votesmart_id', Integer
element 'official_rss_url', TypeConverter, :tag => 'official_rss', :parser => :blank_to_nil
element 'party', TypeConverter, :parser => :party_letter_to_sym
element 'email', TypeConverter, :parser => :blank_to_nil
element 'crp_id', String
element 'website_url', String, :tag => 'website'
element 'fax_number', String, :tag => 'fax'
element 'govtrack_id', Integer
element 'first_name', String, :tag => 'firstname'
element 'middle_name', String, :tag => 'middlename'
element 'last_name', String, :tag => 'lastname'
element 'congress_office', String
element 'bioguide_id', String
element 'webform_url', String, :tag => 'webform'
element 'youtube_url', String
element 'nickname', TypeConverter, :parser => :blank_to_nil
element 'phone', String
element 'fec_id', String
element 'gender', TypeConverter, :parser => :gender_letter_to_sym
element 'name_suffix', TypeConverter, :parser => :blank_to_nil
element 'twitter_id', TypeConverter, :parser => :blank_to_nil
element 'sunlight_old_id', String
element 'congresspedia_url', String
VALID_ATTRIBUTES = [:district, :title, :eventful_id, :in_office, :state, :votesmart_id, :official_rss_url, :party, :email, :crp_id, :website_url, :fax_number, :govtrack_id, :first_name, :middle_name, :last_name, :congress_office, :bioguide_id, :webform_url, :youtube_url, :nickname, :phone, :fec_id, :gender, :name_suffix, :twitter_id, :sunlight_old_id, :congresspedia_url]
# Find all Legislators who serve a particular zip code. This would include the Junior and Senior Senators, as well as the Representatives of any Districts in the zip code.
def self.all_by_zip(zip)
raise ArgumentError, 'missing required parameter zip' if zip.nil?
query = {
:zip => zip,
:apikey => Daywalker.api_key
}
response = get('/legislators.allForZip.xml', :query => query)
handle_response(response)
end
# Find a unique legislator matching a Hash of attribute name/values. See VALID_ATTRIBUTES for possible attributes.
#
# Daywalker::Legislator.unique(:state => 'NY', :district => 4)
#
# Dynamic finders based on the attribute names are also possible. This query can be rewritten as:
#
# Daywalker::Legislator.unique_by_state_and_district('NY', 4)
#
# Returns a Legislator. ArgumentError is raised if more than one result is found.
#
# Gotchas:
# * Results are case insensative (Richard and richard are equivilant)
# * Results are exact (Richard vs Rich are not the same)
def self.unique(conditions)
conditions = TypeConverter.normalize_conditions(conditions)
query = conditions.merge(:apikey => Daywalker.api_key)
response = get('/legislators.get.xml', :query => query)
handle_response(response).first
end
# Find all legislators matching a Hash of attribute name/values. See VALID_ATTRIBUTES for possible attributes.
#
# Daywalker::Legislator.all(:state => 'NY', :title => :senator)
#
# Dynamic finders based on the attribute names are also possible. This query can be rewritten as:
#
# Daywalker::Legislator.all_by_state_and_title('NY', :senator)
#
# Returns an Array of Legislators.
#
# Gotchas:
# * Results are case insensative (Richard and richard are equivilant)
# * Results are exact (Richard vs Rich)
# * nil attributes will match anything, not legislators without a value for the attribute
# * Passing an Array of values to match, ie :state => ['NH', 'MA'] is not supported at this time
def self.all(conditions)
conditions = TypeConverter.normalize_conditions(conditions)
query = conditions.merge(:apikey => Daywalker.api_key)
response = get('/legislators.getList.xml', :query => query)
handle_response(response)
end
# Find all the legislators serving a specific latitude and longitude. This will include the district's Represenative, the Senior Senator, and the Junior Senator.
#
# Returns a Hash containing keys :representative, :junior_senator, and :senior_senator, with values corresponding to the appropriate Legislator.
#
def self.all_by_latitude_and_longitude(latitude, longitude)
district = District.unique_by_latitude_and_longitude(latitude, longitude)
representative = unique_by_state_and_district(district.state, district.number)
junior_senator = unique_by_state_and_district(district.state, :junior_seat)
senior_senator = unique_by_state_and_district(district.state, :senior_seat)
{
:representative => representative,
:junior_senator => junior_senator,
:senior_senator => senior_senator
}
end
# Find all the legislators serving a specific address. This will include the district's Represenative, the Senior Senator, and the Junior Senator.
#
# Returns a Hash containing keys :representative, :junior_senator, and :senior_senator, with values corresponding to the appropriate Legislator.
#
# Raises Daywalker::AddressError if the address can't be geocoded.
def self.all_by_address(address)
location = Daywalker.geocoder.locate(address)
all_by_latitude_and_longitude(location[:latitude], location[:longitude])
end
def self.method_missing(method_id, *args, &block) # :nodoc:
match = DynamicFinderMatch.new(method_id)
if match.match?
create_finder_method(method_id, match.finder, match.attribute_names)
send(method_id, *args, &block)
else
super
end
end
def self.respond_to?(method_id, include_private = false) # :nodoc:
match = DynamicFinderMatch.new(method_id)
if match.match?
true
else
super
end
end
protected
def self.handle_bad_request(body) # :nodoc:
case body
when "Multiple Legislators Returned" then raise(ArgumentError, "The conditions provided returned multiple results, by only one is expected")
# FIXME need to catcfh when legislator (or any object, which would mean it goes in super) is not found
else super
end
end
def self.create_finder_method(method, finder, attribute_names) # :nodoc:
class_eval %{
def self.#{method}(*args) # def self.all_by_district_and_state(*args)
conditions = args.last.kind_of?(Hash) ? args.pop : {} # conditions = args.last.kind_of?(Hash) ? args.pop : {}
[:#{attribute_names.join(', :')}].each_with_index do |key, index| # [:district, :state].each_with_index do |key, index|
conditions[key] = args[index] # conditions[key] = args[index]
end # end
#{finder}(conditions) # all(conditions)
end # end
}
end
end
end