lib/daywalker/legislator.rb in technicalpickles-daywalker-0.1.2 vs lib/daywalker/legislator.rb in technicalpickles-daywalker-0.2.0

- old
+ new

@@ -1,39 +1,93 @@ module Daywalker # Represents a legislator, either a Senator or Representative. # # They have the following attributes: - # * district_number - # * title (ether :senator or :representative) - # * eventful_id (on http://eventful.com) - # * in_office (true or false) - # * state (two-letter abbreviation) - # * votesmart_id (on http://www.votesmart.org) - # * party (:democrat, :republican, or :independent) - # * crp_id (on http://opensecrets.org) - # * website_url - # * fax_number - # * govtrack_id (on http://www.govtrack.us) - # * first_name - # * middle_name - # * last_name - # * congress_office (address in Washington, DC) - # * bioguide_id (on http://bioguide.congress.gov) - # * webform_url - # * youtube_url - # * nickname - # * phone - # * fec_id (on http://fec.gov) - # * gender (:male or :female) - # * name_suffix - # * twitter_id (on http://twitter.com) - # * congresspedia_url + # + # +title+:: + # The title held by a Legislator, as a Symbol, ether <tt>:senator</tt> or <tt>:representative</tt> + # + # +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+, <tt>:democrat</tt>, <tt>:republican</tt>, or <tt>:independent</tt>. + # + # +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 <tt>:junior_seat</tt> or <tt>:senior_seat</tt>. + # + # +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_number', Integer, :tag => 'district' + 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 @@ -58,69 +112,111 @@ 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_number, :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] + 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 in a particular zip code - def self.find_all_by_zip(zip) + # 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 one or many legislators, based on a set of conditions. See - # VALID_ATTRIBUTES for possible attributes you can search for. - # - # If you want one legislators, and you expect there is exactly one - # legislator, use :one. An error will be raised if there are more than - # one result. An ArgumentErrror will be raised if multiple results come back. + # Find a unique legislator matching a Hash of attribute name/values. See VALID_ATTRIBUTES for possible attributes. # - # Daywalker::Legislator.find(:one, :state => 'NY', :district => 4) + # Daywalker::Legislator.unique(:state => 'NY', :district => 4) # - # Otherwise, use :all. - # - # Daywalker::Legislator.find(:all, :state => 'NY', :title => :senator) + # Dynamic finders based on the attribute names are also possible. This query can be rewritten as: # - # Additionally, dynamic finders based on these attributes are available: + # Daywalker::Legislator.unique_by_state_and_district('NY', 4) # - # Daywalker::Legislator.find_by_state_and_district('NY', 4) - # Daywalker::Legislator.find_all_by_state_and_senator('NY', :senator) - def self.find(sym, conditions) - url = case sym - when :one then '/legislators.get.xml' - when :all then '/legislators.getList.xml' - else raise ArgumentError, "invalid argument #{sym.inspect}, only :one and :all are allowed" - end + # 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 <tt>:state => ['NH', 'MA']</tt> is not supported at this time + def self.all(conditions) conditions = TypeConverter.normalize_conditions(conditions) query = conditions.merge(:apikey => Daywalker.api_key) - response = get(url, :query => query) - case sym - when :one then handle_response(response).first - when :all then handle_response(response) - end + 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) # :nodoc: + def self.respond_to?(method_id, include_private = false) # :nodoc: match = DynamicFinderMatch.new(method_id) if match.match? true else super @@ -130,22 +226,23 @@ 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.find_all_by_district_number_and_state(*args) + 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_number, :state].each_with_index do |key, index| + [:#{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 - find(#{finder.inspect}, conditions) # find(:all, conditions) + #{finder}(conditions) # all(conditions) end # end } end end end