ajmorris-sunlight 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ h3. 0.1.0 / 2008-08-20
2
+
3
+ * Initial version
@@ -0,0 +1,131 @@
1
+ h1. Sunlight Labs API Gem
2
+
3
+ NOTE: This file is formatted in Textile and best viewed on "GitHub":http://github.com/luigi/sunlight.
4
+
5
+ h2. Description
6
+
7
+ A little gem that integrates with the Sunlight Labs API. From the "official site":http://services.sunlightlabs.com/api/:
8
+
9
+ bq. The Sunlight Labs API provides methods for obtaining basic information on Members of Congress, legislator IDs used by various websites, and lookups between places and the politicians that represent them. The primary purpose of the API is to facilitate mashups involving politicians and the various other APIs that are out there.
10
+
11
+ Full API documentation available "here":http://services.sunlightlabs.com/api/docs/.
12
+
13
+ Also, the Sunlight gem integrates with the Google Maps API for "street address geocoding":http://code.google.com/apis/maps/documentation/services.html#Geocoding. Because some zip codes overlap two or more congressional districts, passing in a latitude/longitude will give users the most accurate information. Since it's highly unlikely a user would know their exact lat/long, the Google Maps API is used to translate a street address string into a lat/long pair.
14
+
15
+
16
+ h2. Installation
17
+
18
+ The following gems are required:
19
+
20
+ * json
21
+ * ym4r
22
+
23
+ @$ sudo gem install json ym4r@
24
+
25
+ Then, install the stable Sunlight gem:
26
+
27
+ @$ sudo gem install sunlight@
28
+
29
+ Or, if you're adventurous, the latest dev version (which should be quite stable):
30
+
31
+ @$ sudo gem install luigi-sunlight --source=http://gems.github.com@
32
+
33
+
34
+
35
+ h2. Set Up
36
+
37
+ First, register for an API key "here":http://services.sunlightlabs.com/api/register/.
38
+
39
+ Then, you'll want to stick the following lines somewhere in your Ruby environment.
40
+
41
+ <pre><code>
42
+ require 'rubygems'
43
+ require 'sunlight'
44
+ include Sunlight
45
+ Sunlight.api_key = 'yourapikeyfromtheurlabove'
46
+ </code></pre>
47
+
48
+ If you're testing this out in IRB, you'll run them one at a time. If you're using Rails 2.1, stick them in a file called @sunlight.rb@ in @RAILS_ROOT/config/initializers@. They'll load on app startup.
49
+
50
+ h2. Usage
51
+
52
+ Now, it's time to get to the good stuff. The most useful method is @Legislator#all_for@:
53
+
54
+ <pre><code>
55
+ congresspeople = Legislator.all_for(:address => "123 Fifth Ave New York, NY 10003")
56
+ senior_senator = congresspeople[:senior_senator]
57
+ junior_senator = congresspeople[:junior_senator]
58
+ representative = congresspeople[:representative]
59
+
60
+ junior_senator.firstname # returns "Hillary"
61
+ junior_senator.lastname # returns "Clinton"
62
+ junior_senator.congress_office # returns "476 Russell Senate Office Building"
63
+ junior_senator.phone # returns "202-224-4451"
64
+ </code></pre>
65
+
66
+ Note that you should make the best attempt to get a full street address, as that is geocoded behind the scenes into a lat/long pair. A five-digit zip code alone won't cut it, as there may be multiple congressional districts in a single zip code (there are ways to deal with this below). If you pass in a zip+4, then you'll get much better geocoding results.
67
+
68
+ So @Legislator#all_for@ returns a hash of @Legislator@ objects, and the keys are @:senior_senator@, @:junior_senator@, and @:representative@. Make sure to review all the available fields from the "Sunlight Labs API":http://services.sunlightlabs.com/api/docs/legislators/. You can also pass in a lat/long pair:
69
+
70
+ <pre><code>
71
+ congresspeople = Legislator.all_for(:latitude => 33.876145, :longitude => -84.453789)
72
+ </code></pre>
73
+
74
+ This bypasses the geocoding necessary by the Google Maps API. For social networks and other applications with a User object, it makes sense to geocode the user's address up front and save the lat/long data in the local database. Then, use the lat/long pair instead of address, which cuts a substantial bit of time from the @Legislator#all_for@ request since the Google Maps API Geocoding function doesn't have to be called.
75
+
76
+ You can also use the @Legislator#all_where@ method for searching based on available fields. You'll get back an array of @Legislator@ objects:
77
+
78
+ <pre><code>
79
+ johns = Legislator.all_where(:firstname => "John")
80
+ floridians = Legislator.all_where(:state => "FL")
81
+ dudes = Legislator.all_where(:gender => "M")
82
+
83
+ johns.each do |john|
84
+ # do stuff
85
+ end
86
+ </code></pre>
87
+
88
+ There's also the @District@ object. @District#get@ takes in either lat/long or an address and does it's best to return the correct Congressional District:
89
+
90
+ <pre><code>
91
+ district = District.get(:latitude => 33.876145, :longitude => -84.453789)
92
+ district.state # returns "GA"
93
+ district.number # returns "6"
94
+
95
+ district = District.get(:address => "123 Fifth Ave New York, NY")
96
+ </code></pre>
97
+
98
+ Finally, two more methods, @District.all_from_zipcode@ and @District.zipcodes_in@, help you out when all you have is a five-digit zip code and want to make sure you account for all the districts in a zip code, or if you want to get back all zipcodes in a given district.
99
+
100
+ <pre><code>
101
+ districts = District.all_from_zipcode(90210) # returns array of District objects
102
+ zipcodes = District.zipcodes_in("NY", "10") # returns array of zip codes as strings ["11201", "11202", "11203",...]
103
+ </code></pre>
104
+
105
+
106
+ h2. License
107
+
108
+ See the terms of usage for the "Sunlight Labs API":http://services.sunlightlabs.com/api/ and the "Google Maps API":http://code.google.com/apis/maps/terms.html.
109
+
110
+ Copyright &copy; 2008 by Luigi Montanez under the MIT License.
111
+
112
+ Permission is hereby granted, free of charge, to any person
113
+ obtaining a copy of this software and associated documentation
114
+ files (the "Software"), to deal in the Software without
115
+ restriction, including without limitation the rights to use,
116
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
117
+ copies of the Software, and to permit persons to whom the
118
+ Software is furnished to do so, subject to the following
119
+ conditions:
120
+
121
+ The above copyright notice and this permission notice shall be
122
+ included in all copies or substantial portions of the Software.
123
+
124
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
125
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
126
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
127
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
128
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
129
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
130
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
131
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,59 @@
1
+ require 'rubygems'
2
+ require 'json'
3
+ require 'cgi'
4
+ require 'ym4r/google_maps/geocoding'
5
+ include Ym4r::GoogleMaps
6
+
7
+ module Sunlight
8
+
9
+ API_URL = "http://services.sunlightlabs.com/api/"
10
+ API_FORMAT = "json"
11
+ attr_accessor :api_key
12
+
13
+ # Houses general methods to work with the Sunlight and Google Maps APIs
14
+ class SunlightObject
15
+
16
+
17
+ # Constructs a Sunlight API-friendly URL
18
+ def self.construct_url(api_method, params)
19
+ "#{API_URL}#{api_method}.#{API_FORMAT}?apikey=#{Sunlight.api_key}#{hash2get(params)}"
20
+ end
21
+
22
+
23
+
24
+ # Converts a hash to a GET string
25
+ def self.hash2get(h)
26
+
27
+ get_string = ""
28
+
29
+ h.each_pair do |key, value|
30
+ get_string += "&#{key.to_s}=#{CGI::escape(value.to_s)}"
31
+ end
32
+
33
+ get_string
34
+
35
+ end # def hash2get
36
+
37
+
38
+ # Use the Net::HTTP and JSON libraries to make the API call
39
+ #
40
+ # Usage:
41
+ # District.get_json_data("http://someurl.com") # returns Hash of data or nil
42
+ def self.get_json_data(url)
43
+
44
+ response = Net::HTTP.get_response(URI.parse(url))
45
+ if response.class == Net::HTTPOK
46
+ result = JSON.parse(response.body)
47
+ else
48
+ nil
49
+ end
50
+
51
+ end # self.get_json_data
52
+
53
+
54
+
55
+ end # class SunlightObject
56
+
57
+ end # module Sunlight
58
+
59
+ Dir["#{File.dirname(__FILE__)}/sunlight/*.rb"].each { |source_file| require source_file }
@@ -0,0 +1,108 @@
1
+ module Sunlight
2
+
3
+ class District < SunlightObject
4
+
5
+ attr_accessor :state, :number
6
+
7
+ def initialize(state, number)
8
+ @state = state
9
+ @number = number
10
+ end
11
+
12
+
13
+ # Usage:
14
+ # District.get(:latitude => 33.876145, :longitude => -84.453789) # returns one District object or nil
15
+ # District.get(:address => "123 Fifth Ave New York, NY") # returns one District object or nil
16
+ #
17
+ def self.get(params)
18
+
19
+ if (params[:latitude] and params[:longitude])
20
+
21
+ get_from_lat_long(params[:latitude], params[:longitude])
22
+
23
+ elsif (params[:address])
24
+
25
+ # get the lat/long from Google
26
+ placemarks = Geocoding::get(params[:address])
27
+
28
+ unless placemarks.empty?
29
+ placemark = placemarks[0]
30
+ get_from_lat_long(placemark.latitude, placemark.longitude)
31
+ end
32
+
33
+ else
34
+ nil # appropriate params not found
35
+ end
36
+
37
+ end
38
+
39
+
40
+
41
+ # Usage:
42
+ # District.get_from_lat_long(-123, 123) # returns District object or nil
43
+ #
44
+ def self.get_from_lat_long(latitude, longitude)
45
+
46
+ url = construct_url("districts.getDistrictFromLatLong", {:latitude => latitude, :longitude => longitude})
47
+
48
+ if (result = get_json_data(url))
49
+
50
+ districts = []
51
+ result["response"]["districts"].each do |district|
52
+ districts << District.new(district["district"]["state"], district["district"]["number"])
53
+ end
54
+
55
+ districts.first
56
+
57
+ else
58
+ nil
59
+ end # if response.class
60
+
61
+ end
62
+
63
+
64
+
65
+ # Usage:
66
+ # District.all_from_zipcode(90210) # returns array of District objects
67
+ #
68
+ def self.all_from_zipcode(zipcode)
69
+
70
+ url = construct_url("districts.getDistrictsFromZip", {:zip => zipcode})
71
+
72
+ if (result = get_json_data(url))
73
+
74
+ districts = []
75
+ result["response"]["districts"].each do |district|
76
+ districts << District.new(district["district"]["state"], district["district"]["number"])
77
+ end
78
+
79
+ districts
80
+
81
+ else
82
+ nil
83
+ end # if response.class
84
+
85
+ end
86
+
87
+
88
+
89
+ # Usage:
90
+ # District.zipcodes_in("NY", 29) # returns ["14009", "14024", "14029", ...]
91
+ #
92
+ def self.zipcodes_in(state, number)
93
+
94
+ url = construct_url("districts.getZipsFromDistrict", {:state => state, :district => number})
95
+
96
+ if (result = get_json_data(url))
97
+ result["response"]["zips"]
98
+ else
99
+ nil
100
+ end # if response.class
101
+
102
+ end
103
+
104
+
105
+
106
+ end # class District
107
+
108
+ end # module Sunlight
@@ -0,0 +1,113 @@
1
+ module Sunlight
2
+
3
+ class Legislator < SunlightObject
4
+
5
+
6
+ attr_accessor :title, :firstname, :middlename, :lastname, :name_suffix, :nickname,
7
+ :party, :state, :district, :gender, :phone, :fax, :website, :webform,
8
+ :email, :congress_office, :bioguide_id, :votesmart_id, :fec_id,
9
+ :govtrack_id, :crp_id, :event_id, :congresspedia_url
10
+
11
+ # Takes in a hash where the keys are strings (the format passed in by the JSON parser)
12
+ #
13
+ def initialize(params)
14
+ params.each do |key, value|
15
+ instance_variable_set("@#{key}", value) if Legislator.instance_methods.include? key
16
+ end
17
+ end
18
+
19
+
20
+ def self.all
21
+ Legislator.find(:all)
22
+ end
23
+ #
24
+ # Useful for getting the exact Legislators for a given district.
25
+ #
26
+ # Returns:
27
+ #
28
+ # A Hash of the three Members of Congress for a given District: Two
29
+ # Senators and one Representative.
30
+ #
31
+ # You can pass in lat/long or address. The district will be
32
+ # determined for you:
33
+ #
34
+ # officials = Legislator.all_for(:latitude => 33.876145, :longitude => -84.453789)
35
+ # senior = officials[:senior_senator]
36
+ # junior = officials[:junior_senator]
37
+ # rep = officials[:representative]
38
+ #
39
+ # Legislator.all_for(:address => "123 Fifth Ave New York, NY 10003")
40
+ # Legislator.all_for(:address => "90210") # not recommended, but it'll work
41
+ #
42
+ def self.all_for(params)
43
+
44
+ if (params[:latitude] and params[:longitude])
45
+ Legislator.all_in_district(District.get(:latitude => params[:latitude], :longitude => params[:longitude]))
46
+ elsif (params[:address])
47
+ Legislator.all_in_district(District.get(:address => params[:address]))
48
+ else
49
+ nil # appropriate params not found
50
+ end
51
+
52
+ end
53
+
54
+
55
+ #
56
+ # A helper method for all_for. Use that instead, unless you
57
+ # already have the district object, then use this.
58
+ #
59
+ # Usage:
60
+ #
61
+ # officials = Legislator.all_in_district(District.new("NJ", "7"))
62
+ #
63
+ def self.all_in_district(district)
64
+
65
+ senior_senator = Legislator.all_where(:state => district.state, :district => "Senior Seat").first
66
+ junior_senator = Legislator.all_where(:state => district.state, :district => "Junior Seat").first
67
+ representative = Legislator.all_where(:state => district.state, :district => district.number).first
68
+
69
+ {:senior_senator => senior_senator, :junior_senator => junior_senator, :representative => representative}
70
+
71
+ end
72
+
73
+
74
+ #
75
+ # A more general, open-ended search on Legislators than #all_for.
76
+ # See the Sunlight API for list of conditions and values:
77
+ #
78
+ # http://services.sunlightlabs.com/api/docs/legislators/
79
+ #
80
+ # Returns:
81
+ #
82
+ # An array of Legislator objects that matches the conditions
83
+ #
84
+ # Usage:
85
+ #
86
+ # johns = Legislator.all_where(:firstname => "John")
87
+ # floridians = Legislator.all_where(:state => "FL")
88
+ # dudes = Legislator.all_where(:gender => "M")
89
+ #
90
+ def self.all_where(params)
91
+
92
+ url = construct_url("legislators.getList", params)
93
+
94
+ if (result = get_json_data(url))
95
+
96
+ legislators = []
97
+ result["response"]["legislators"].each do |legislator|
98
+ legislators << Legislator.new(legislator["legislator"])
99
+ end
100
+
101
+ legislators
102
+
103
+ else
104
+ nil
105
+ end # if response.class
106
+
107
+ end
108
+
109
+
110
+
111
+ end # class Legislator
112
+
113
+ end # module Sunlight
@@ -0,0 +1,14 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "sunlight"
3
+ s.version = "0.1.1"
4
+ s.date = "2008-08-28"
5
+ s.summary = "Library for accessing the Sunlight Labs API."
6
+ s.email = "luigi.montanez@gmail.com"
7
+ s.homepage = "http://github.com/luigi/sunlight"
8
+ s.authors = ["Luigi Montanez"]
9
+ s.files = ['sunlight.gemspec', 'lib/sunlight.rb', 'lib/sunlight/district.rb',
10
+ 'lib/sunlight/legislator.rb', 'README.textile', 'CHANGES.textile']
11
+ s.add_dependency("json", [">= 1.1.3"])
12
+ s.add_dependency("ym4r", [">= 0.6.1"])
13
+ s.has_rdoc = true
14
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ajmorris-sunlight
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Luigi Montanez
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-08-28 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: json
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.1.3
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: ym4r
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.6.1
34
+ version:
35
+ description:
36
+ email: luigi.montanez@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - sunlight.gemspec
45
+ - lib/sunlight.rb
46
+ - lib/sunlight/district.rb
47
+ - lib/sunlight/legislator.rb
48
+ - README.textile
49
+ - CHANGES.textile
50
+ has_rdoc: true
51
+ homepage: http://github.com/luigi/sunlight
52
+ post_install_message:
53
+ rdoc_options: []
54
+
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ requirements: []
70
+
71
+ rubyforge_project:
72
+ rubygems_version: 1.2.0
73
+ signing_key:
74
+ specification_version: 2
75
+ summary: Library for accessing the Sunlight Labs API.
76
+ test_files: []
77
+