govkit-h 0.7.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/.document +5 -0
  2. data/.rspec +3 -0
  3. data/Gemfile +13 -0
  4. data/LICENSE +20 -0
  5. data/README.md +70 -0
  6. data/Rakefile +82 -0
  7. data/USAGE +1 -0
  8. data/VERSION +1 -0
  9. data/generators/govkit/govkit_generator.rb +24 -0
  10. data/generators/govkit/templates/govkit.rb +24 -0
  11. data/govkit.gemspec +130 -0
  12. data/init.rb +4 -0
  13. data/lib/generators/govkit/govkit_generator.rb +21 -0
  14. data/lib/generators/govkit/templates/create_mentions.rb +21 -0
  15. data/lib/generators/govkit/templates/govkit.rb +24 -0
  16. data/lib/generators/govkit/templates/mention.rb +15 -0
  17. data/lib/gov_kit.rb +45 -0
  18. data/lib/gov_kit/acts_as_noteworthy.rb +63 -0
  19. data/lib/gov_kit/configuration.rb +58 -0
  20. data/lib/gov_kit/follow_the_money.rb +176 -0
  21. data/lib/gov_kit/open_congress.rb +125 -0
  22. data/lib/gov_kit/open_congress/bill.rb +171 -0
  23. data/lib/gov_kit/open_congress/blog_post.rb +15 -0
  24. data/lib/gov_kit/open_congress/news_post.rb +15 -0
  25. data/lib/gov_kit/open_congress/person.rb +141 -0
  26. data/lib/gov_kit/open_congress/person_stat.rb +13 -0
  27. data/lib/gov_kit/open_congress/roll_call.rb +14 -0
  28. data/lib/gov_kit/open_congress/roll_call_comparison.rb +28 -0
  29. data/lib/gov_kit/open_congress/voting_comparison.rb +44 -0
  30. data/lib/gov_kit/open_states.rb +132 -0
  31. data/lib/gov_kit/railtie.rb +24 -0
  32. data/lib/gov_kit/resource.rb +190 -0
  33. data/lib/gov_kit/search_engines.rb +7 -0
  34. data/lib/gov_kit/search_engines/bing.rb +38 -0
  35. data/lib/gov_kit/search_engines/google_blog.rb +32 -0
  36. data/lib/gov_kit/search_engines/google_news.rb +47 -0
  37. data/lib/gov_kit/search_engines/technorati.rb +35 -0
  38. data/lib/gov_kit/search_engines/wikipedia.rb +27 -0
  39. data/lib/gov_kit/transparency_data.rb +144 -0
  40. data/lib/gov_kit/vote_smart.rb +126 -0
  41. data/lib/govkit.rb +1 -0
  42. data/spec/fixtures/bing/news_search.response +1 -0
  43. data/spec/fixtures/bing/no_results.response +1 -0
  44. data/spec/fixtures/follow_the_money/business-page0.response +28 -0
  45. data/spec/fixtures/follow_the_money/business-page1.response +12 -0
  46. data/spec/fixtures/follow_the_money/contribution.response +12 -0
  47. data/spec/fixtures/follow_the_money/unauthorized.response +8 -0
  48. data/spec/fixtures/open_congress/person.response +8 -0
  49. data/spec/fixtures/open_states/401.response +10 -0
  50. data/spec/fixtures/open_states/404.response +9 -0
  51. data/spec/fixtures/open_states/410.response +6 -0
  52. data/spec/fixtures/open_states/bill.response +240 -0
  53. data/spec/fixtures/open_states/bill_query.response +1990 -0
  54. data/spec/fixtures/open_states/committee_find.response +53 -0
  55. data/spec/fixtures/open_states/committee_query.response +190 -0
  56. data/spec/fixtures/open_states/legislator.response +34 -0
  57. data/spec/fixtures/open_states/legislator_query.response +144 -0
  58. data/spec/fixtures/open_states/state.response +60 -0
  59. data/spec/fixtures/search_engines/google_news.response +8 -0
  60. data/spec/fixtures/transparency_data/contributions.response +18 -0
  61. data/spec/fixtures/transparency_data/entities_search.response +7 -0
  62. data/spec/fixtures/transparency_data/entities_search_limit_0.response +7 -0
  63. data/spec/fixtures/transparency_data/entities_search_limit_1.response +7 -0
  64. data/spec/fixtures/transparency_data/grants_find_all.response +7 -0
  65. data/spec/fixtures/transparency_data/lobbyists_find_all.response +7 -0
  66. data/spec/follow_the_money_spec.rb +61 -0
  67. data/spec/open_congress_spec.rb +108 -0
  68. data/spec/open_states_spec.rb +213 -0
  69. data/spec/search_engines_spec.rb +44 -0
  70. data/spec/spec_helper.rb +36 -0
  71. data/spec/transparency_data_spec.rb +106 -0
  72. metadata +258 -0
@@ -0,0 +1,132 @@
1
+ module GovKit
2
+
3
+ # Parent class for OpenStates resources
4
+ # See http://openstates.sunlightlabs.com/api/
5
+ class OpenStatesResource < Resource
6
+
7
+ # Uses default_params from the HTTParty gem.
8
+ # See HTTParty::ClassMethods:
9
+ # http://rubydoc.info/gems/httparty/0.7.4/HTTParty/ClassMethods#default_params-instance_method
10
+ default_params :output => 'json', :apikey => GovKit::configuration.sunlight_apikey
11
+ base_uri GovKit::configuration.openstates_base_url
12
+
13
+ # Do a GET query, with optional parameters.
14
+ #
15
+ # OpenStates returns a 404 error when a query
16
+ # returns nothing.
17
+ #
18
+ # So, if a query result is a resource not found error,
19
+ # we return an empty set.
20
+ def self.get_uri(uri, options={})
21
+ begin
22
+ response = get(uri, options)
23
+ result = parse(response)
24
+ rescue ResourceNotFound
25
+ return []
26
+ end
27
+ result
28
+ end
29
+
30
+ end
31
+
32
+ # Ruby module for interacting with the Open States Project API
33
+ # See http://openstates.sunlightlabs.com/api/
34
+ # Most +find+ and +search+ methods:
35
+ # * call HTTParty::ClassMethods#get
36
+ # * which returns an HTTParty::Response object
37
+ # * which is passed to GovKit::Resource#parse
38
+ # * which uses the response to populate a Resource
39
+ #
40
+ module OpenStates
41
+ ROLE_MEMBER = "member"
42
+ ROLE_COMMITTEE_MEMBER = "committee member"
43
+ CHAMBER_UPPER = "upper"
44
+ CHAMBER_LOWER = "lower"
45
+
46
+ # The State class represents the state data returned from Open States.
47
+ #
48
+ # For details about fields returned, see the Open States documentation, at
49
+ # http://openstates.sunlightlabs.com/api/metadata/,
50
+ #
51
+ class State < OpenStatesResource
52
+ def self.find_by_abbreviation(abbreviation)
53
+ get_uri("/metadata/#{abbreviation}/")
54
+ end
55
+ end
56
+
57
+ # The Bill class represents the bill data returned from Open States.
58
+ #
59
+ # For details about fields returned, see the Open States documentation, at
60
+ # http://openstates.sunlightlabs.com/api/bills/,
61
+ #
62
+ class Bill < OpenStatesResource
63
+ # http://openstates.sunlightlabs.com/api/v1/bills/ca/20092010/AB 667/
64
+ def self.find(state_abbrev, session, bill_id, chamber = '')
65
+ escaped_bill_id = bill_id.gsub(/ /, '%20')
66
+ escaped_session = session.gsub(/ /, '%20')
67
+
68
+ get_uri("/bills/#{state_abbrev.downcase}/#{escaped_session}/#{chamber.blank? ? '' : chamber + '/'}#{escaped_bill_id}/")
69
+ end
70
+
71
+ def self.search(query, options = {})
72
+ result = get_uri('/bills/', :query => {:q => query}.merge(options))
73
+ return Array(result)
74
+ end
75
+
76
+ def self.latest(updated_since, ops = {})
77
+ response = get('/bills/', :query => {:updated_since => updated_since.to_s}.merge(ops))
78
+ parse(response)
79
+ end
80
+ end
81
+
82
+ # The Legislator class represents the legislator data returned from Open States.
83
+ #
84
+ # For details about fields returned, see the Open States documentation, at
85
+ # http://openstates.sunlightlabs.com/api/legislators/,
86
+ #
87
+ class Legislator < OpenStatesResource
88
+ def self.find(legislator_id)
89
+ get_uri("/legislators/#{legislator_id}/")
90
+ end
91
+
92
+ def self.search(options = {})
93
+ result = get_uri('/legislators/', :query => options)
94
+ return Array(result)
95
+ end
96
+ end
97
+
98
+ # The Committee class represents the committee data returned from Open States.
99
+ #
100
+ # For details about fields returned, see the Open States documentation, at
101
+ # http://openstates.sunlightlabs.com/api/committees/,
102
+ #
103
+ class Committee < OpenStatesResource
104
+ def self.find(committee_id)
105
+ get_uri("/committees/#{committee_id}/")
106
+ end
107
+
108
+ def self.search(options = {})
109
+ get_uri('/committees/', :query => options)
110
+ end
111
+ end
112
+
113
+ class Role < OpenStatesResource; end
114
+
115
+ class Sponsor < OpenStatesResource; end
116
+
117
+ class Version < OpenStatesResource; end
118
+
119
+ class Source < OpenStatesResource; end
120
+
121
+ class Address < OpenStatesResource; end
122
+
123
+ class Action < OpenStatesResource; end
124
+
125
+ class Vote < OpenStatesResource
126
+ def self.find(vote_id)
127
+ response = get("/votes/#{vote_id}/")
128
+ parse(response)
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,24 @@
1
+ require 'govkit'
2
+
3
+ module GovKit
4
+ if defined? Rails::Railtie
5
+ require 'rails'
6
+ class Railtie < Rails::Railtie
7
+ initializer 'govkit.insert_into_active_record' do
8
+ ActiveSupport.on_load :active_record do
9
+ GovKit::Railtie.insert
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ # This class exists in order to run its insert method while
16
+ # Rails is loading.
17
+ # This then adds GovKit::ActsAsNoteworthy to ActiveRecord::Base.
18
+ # See http://api.rubyonrails.org/classes/Rails/Railtie.html
19
+ class Railtie
20
+ def self.insert
21
+ ActiveRecord::Base.send(:include, GovKit::ActsAsNoteworthy)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,190 @@
1
+ module GovKit
2
+
3
+
4
+ # This is the parent class for the classes that wrap
5
+ # the data returned to govkit.
6
+ #
7
+ # Subclasses are responsible for fetching the data from
8
+ # different web services; Resource will then parse the returned data,
9
+ # converting returned fields to instance methods.
10
+ #
11
+ # Initialize a Resource with a hash of attributes, or an array of hashes.
12
+ # For each attribute, add a getter and setter to this instance.
13
+ # @example
14
+ # res = Resource.new { "aaa" => "111", "bbb" => "222", "ccc" => "333" }
15
+ # res.aaa == "111"
16
+ # res.bbb == "222"
17
+ # res.ccc == "333"
18
+ #
19
+ # Includes {http://rdoc.info/github/jnunemaker/httparty/master/HTTParty/ClassMethods HTTParty}, which provides convenience methods like get().
20
+ class Resource
21
+ include HTTParty
22
+ format :json
23
+
24
+ # The attributes data returned by the service.
25
+ attr_reader :attributes
26
+
27
+ # The response returned by the service.
28
+ attr_reader :raw_response
29
+
30
+ def initialize(attributes = {})
31
+ @attributes = {}
32
+ @raw_response = attributes
33
+
34
+ unload(attributes)
35
+ end
36
+
37
+ # @return [Hash] the response object, potentially useful for comparison on sync
38
+ #
39
+ def to_md5
40
+ Digest::MD5.hexdigest(@raw_response.body)
41
+ end
42
+
43
+ # Handles the basic responses we might get back from a web service.
44
+ #
45
+ # On failure, throws an error.
46
+ #
47
+ # If a service returns something other than a 404 when an object is not found,
48
+ # you'll need to handle that in the subclass.
49
+ #
50
+ # @param [Object] response the object.
51
+ # @return [Resource] a new Resource created from the response.
52
+ #
53
+ def self.parse(response)
54
+
55
+ if response.class == HTTParty::Response
56
+ case response.response
57
+ when Net::HTTPNotFound
58
+ raise ResourceNotFound, "404 Not Found"
59
+ when Net::HTTPGone
60
+ raise ResourceNotFound, "404 Not Found"
61
+ when Net::HTTPUnauthorized
62
+ raise NotAuthorized, "401 Not Authorized; have you set up your API key?"
63
+ when Net::HTTPServerError
64
+ raise ServerError, '5xx server error'
65
+ when Net::HTTPClientError
66
+ raise ClientError, '4xx client error'
67
+ end
68
+ end
69
+
70
+ return [] unless !response.blank?
71
+
72
+ instantiate(response)
73
+ end
74
+
75
+ # Instantiate new GovKit::Resources.
76
+ #
77
+ # @param [Hash] record a hash of values returned by a service, or an array of hashes.
78
+ # @return [Resource]
79
+ #
80
+ # If +record+ is a hash, return a single GovKit::Resource.
81
+ # If it is an array, return an array of GovKit::Resources.
82
+ #
83
+ def self.instantiate(record)
84
+ if record.is_a?(Array)
85
+ instantiate_collection(record)
86
+ else
87
+ new(record)
88
+ end
89
+ end
90
+
91
+ # Instantiate a set of records.
92
+ #
93
+ # @return [Array] Array of records
94
+ # @param [Array] collection An array of records
95
+ def self.instantiate_collection(collection)
96
+ collection.collect! { |record| new(record) }
97
+ end
98
+
99
+ # Given a hash of attributes, assign it to the @attributes member.
100
+ # Then for each attribute, create or set a pair of member accessors with the name
101
+ # of the attribute's key.
102
+ #
103
+ # If the value of the attribute is itself an array or a hash,
104
+ # then create a new class with the (singularized) key as a name, and with a parent class of Resource,
105
+ # and initialize it with the hash.
106
+ #
107
+ # @param [Hash] attributes the attributes returned by the web service.
108
+ #
109
+ def unload(attributes)
110
+ raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
111
+
112
+ attributes.each do |key, value|
113
+ @attributes[key.to_s] =
114
+ case value
115
+ when Array
116
+ resource = resource_for_collection(key)
117
+ value.map do |attrs|
118
+ if attrs.is_a?(String) || attrs.is_a?(Numeric)
119
+ attrs.duplicable? ? attrs.dup : attrs
120
+ else
121
+ resource.new(attrs)
122
+ end
123
+ end
124
+ when Hash
125
+ resource = find_or_create_resource_for(key)
126
+ resource.new(value)
127
+ else
128
+ value.dup rescue value
129
+ end
130
+ end
131
+ self
132
+ end
133
+
134
+ private
135
+
136
+ # Finds a member of the GovKit module with the given name.
137
+ # If the resource doesn't exist, creates it.
138
+ #
139
+ def resource_for_collection(name)
140
+ find_or_create_resource_for(name.to_s.singularize)
141
+ end
142
+
143
+ # Searches each module in +ancestors+ for members named +resource_name+
144
+ # Returns the named resource
145
+ # Throws a NameError if none of the resources in the list contains +resource_name+
146
+ #
147
+ def find_resource_in_modules(resource_name, ancestors)
148
+ if namespace = ancestors.detect { |a| a.constants.include?(resource_name.to_sym) }
149
+ return namespace.const_get(resource_name)
150
+ else
151
+ raise NameError, "Namespace for #{namespace} not found"
152
+ end
153
+ end
154
+
155
+ # Searches the GovKit module for a resource with the name +name+, cleaned and camelized
156
+ # Returns that resource.
157
+ # If the resource isn't found, it's created.
158
+ #
159
+ def find_or_create_resource_for(name)
160
+ resource_name = name.to_s.gsub(/^[_\-+]/,'').gsub(/^(\-?\d)/, "n#{$1}").gsub(/(\s|-)/, '').camelize
161
+ if self.class.parents.size > 1
162
+ find_resource_in_modules(resource_name, self.class.parents)
163
+ else
164
+ self.class.const_get(resource_name)
165
+ end
166
+ rescue NameError
167
+ if self.class.const_defined?(resource_name)
168
+ resource = self.class.const_get(resource_name)
169
+ else
170
+ resource = self.class.const_set(resource_name, Class.new(GovKit::Resource))
171
+ end
172
+ resource
173
+ end
174
+
175
+ def method_missing(method_symbol, * arguments) #:nodoc:
176
+ method_name = method_symbol.to_s
177
+
178
+ case method_name.last
179
+ when "="
180
+ attributes[method_name.first(-1)] = arguments.first
181
+ when "?"
182
+ !attributes[method_name.first(-1)].blank?
183
+ when "]"
184
+ attributes[arguments.first.to_s]
185
+ else
186
+ attributes.has_key?(method_name) ? attributes[method_name] : super
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,7 @@
1
+ module GovKit::SearchEngines
2
+ autoload :GoogleNews, 'gov_kit/search_engines/google_news'
3
+ autoload :GoogleBlog, 'gov_kit/search_engines/google_blog'
4
+ autoload :Technorati, 'gov_kit/search_engines/technorati'
5
+ autoload :Bing, 'gov_kit/search_engines/bing'
6
+ autoload :Wikipedia, 'gov_kit/search_engines/wikipedia'
7
+ end
@@ -0,0 +1,38 @@
1
+ module GovKit
2
+ module SearchEngines
3
+ class Bing
4
+ def self.search(query=[], options={})
5
+ host = GovKit::configuration.bing_base_url
6
+ query = [query, options[:geo]].compact.join('+')
7
+
8
+ options['Sources'] ||= 'news'
9
+
10
+ path = "/json.aspx?Query=#{URI::encode(query)}&AppId=#{GovKit::configuration.bing_appid}&Sources=#{options['Sources']}"
11
+
12
+ doc = JSON.parse(make_request(host, path))
13
+
14
+ mentions = []
15
+
16
+ if news_items = doc['SearchResponse']['News']
17
+ puts "#{news_items['Results'].size} from Bing"
18
+ news_items['Results'].each do |i|
19
+ mention = GovKit::Mention.new
20
+ mention.title = i['Title']
21
+ mention.search_source = 'Bing'
22
+ mention.date = DateTime.parse(i['Date'])
23
+ mention.excerpt = i['Snippet']
24
+ mention.source = i['Source']
25
+ mention.url = i['Url']
26
+
27
+ mentions << mention
28
+ end
29
+ end
30
+ mentions
31
+ end
32
+
33
+ def self.make_request(host, path)
34
+ Net::HTTP.get(host, path)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,32 @@
1
+ module GovKit
2
+ module SearchEngines
3
+ class GoogleBlog
4
+ def self.search(query=[], options = {})
5
+ query = [query, options[:geo]].compact.join('+')
6
+ host = GovKit::configuration.google_blog_base_url
7
+ path = "/blogsearch_feeds?q=#{URI::encode(query)}&hl=en&output=rss&num=50"
8
+
9
+ doc = Nokogiri::XML(make_request(host, path))
10
+
11
+ mentions = []
12
+
13
+ doc.xpath('//item').each do |i|
14
+ mention = GovKit::Mention.new
15
+ mention.title = i.xpath('title').inner_text
16
+ mention.search_source = 'Google Blogs'
17
+ mention.date = i.xpath('dc:date').inner_text
18
+ mention.excerpt = i.xpath('description').inner_text
19
+ mention.source = i.xpath('dc:publisher').inner_text
20
+ mention.url = i.xpath('link').inner_text
21
+
22
+ mentions << mention
23
+ end
24
+ mentions
25
+ end
26
+
27
+ def self.make_request(host, path)
28
+ response = Net::HTTP.get(host, path)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,47 @@
1
+ module GovKit
2
+ module SearchEngines
3
+
4
+ # Class to wrap access to Google News.
5
+ class GoogleNews
6
+
7
+ # Fetches stories about a topic from google news.
8
+ # Returns an array of GovKit::Mention objects.
9
+ #
10
+ # query: The query wanted For example:
11
+ # mentions = GoogleNews.search("Nancy Pelosi")
12
+ #
13
+ # options: Any additional parameters to the search. eg.:
14
+ # :geo => 'Texas' will add &geo=Texas to the URL.
15
+ # :num => 100 will show 100 results per page.
16
+ #
17
+ def self.search(query=[], options={})
18
+ query = Array(query).join('+')
19
+ host = GovKit::configuration.google_news_base_url
20
+ options[:num] ||= 50
21
+
22
+ path = "/news?q=#{URI::encode(query)}&output=rss" + '&' + options.map { |k, v| URI::encode(k.to_s) + '=' + URI::encode(v.to_s) }.join('&')
23
+
24
+ doc = Nokogiri::XML(make_request(host, path))
25
+
26
+ mentions = []
27
+
28
+ doc.xpath('//item').each do |i|
29
+ mention = GovKit::Mention.new
30
+ mention.title = i.xpath('title').inner_text.split(" - ").first
31
+ mention.date = i.xpath('pubDate').inner_text
32
+ mention.excerpt = i.xpath('description').inner_text
33
+ mention.source = i.xpath('title').inner_text.split(" - ").last
34
+ mention.url = i.xpath('link').inner_text
35
+
36
+ mentions << mention
37
+ end
38
+
39
+ mentions
40
+ end
41
+
42
+ def self.make_request(host, path)
43
+ response = Net::HTTP.get(host, path)
44
+ end
45
+ end
46
+ end
47
+ end