govkit 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -1,26 +1,29 @@
1
- Govkit
2
- ======
1
+ # Govkit
3
2
 
4
3
  Govkit is a Ruby gem that provides simple access to open government APIs around the web.
5
4
 
6
- Setup
7
- =====
5
+ # Installation
6
+
7
+ From gemcutter:
8
+
9
+ gem install govkit
10
+
11
+ # Setup
8
12
 
9
13
  Add govkit to your environment.rb or Gemfile
10
14
 
11
15
  Run <code>./script/generate govkit</code> to copy a config file into <code>config/initializers/govkit.rb</code>. You will need to add your API keys to this config file.
12
16
 
13
- Example
14
- =======
17
+ # Example
15
18
 
16
19
  [http://fiftystates-dev.sunlightlabs.com/](The Fifty States project) has a RESTful API for accessing data about state legislators, bills, votes, etc.
17
20
 
18
- >> Govkit::FiftyStates::State.find_by_abbrev('CA')
21
+ >> Govkit::FiftyStates::State.find_by_abbreviation('CA')
22
+ >> Govkit::VoteSmart::Address.find(legislator_id)
19
23
 
20
24
  (TODO: add usage examples...)
21
25
 
22
- Bugs? Questions?
23
- ================
26
+ # Bugs? Questions?
24
27
 
25
28
  Please join the [Govkit Google Group](http://groups.google.com/group/govkit), especially if you'd like to talk about a new feature and get announcements.
26
29
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.0.2
@@ -1,9 +1,9 @@
1
- class GovkitGenerator < Rails::Generator::Base
2
-
1
+ class GovKitGenerator < Rails::Generator::Base
2
+
3
3
  def initialize(*runtime_args)
4
4
  super
5
5
  end
6
-
6
+
7
7
  def manifest
8
8
  record do |m|
9
9
  m.directory File.join('config', 'initializers')
@@ -12,9 +12,9 @@ class GovkitGenerator < Rails::Generator::Base
12
12
  end
13
13
 
14
14
  protected
15
-
15
+
16
16
  def banner
17
17
  %{Usage: #{$0} #{spec.name}\nCopies a config initializer to config/initializers/govkit.rb}
18
18
  end
19
-
20
- end
19
+
20
+ end
@@ -1,6 +1,6 @@
1
- if defined? Govkit
2
-
3
- Govkit.configure do |config|
1
+ if defined? GovKit
2
+
3
+ GovKit.configure do |config|
4
4
  # Get an API key for Sunlight's Fifty States project here:
5
5
  # http://services.sunlightlabs.com/accounts/register/
6
6
  config.fiftystates_apikey = 'YOUR_FIFTYSTATES_API_KEY'
data/govkit.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{govkit}
8
- s.version = "0.0.1"
8
+ s.version = "0.0.2"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Participatory Politics Foundation", "Srinivas Aki", "Carl Tashian"]
12
- s.date = %q{2010-05-10}
12
+ s.date = %q{2010-05-14}
13
13
  s.description = %q{Govkit lets you quickly get encapsulated Ruby objects for common open government APIs. We're starting with Sunlight's Fifty States API and the Project Vote Smart API.}
14
14
  s.email = %q{carl@ppolitics.org}
15
15
  s.extra_rdoc_files = [
@@ -27,9 +27,15 @@ Gem::Specification.new do |s|
27
27
  "generators/govkit/govkit_generator.rb",
28
28
  "generators/govkit/templates/govkit.rb",
29
29
  "govkit.gemspec",
30
+ "lib/gov_kit.rb",
31
+ "lib/gov_kit/configuration.rb",
32
+ "lib/gov_kit/fifty_states.rb",
33
+ "lib/gov_kit/resource.rb",
34
+ "lib/gov_kit/vote_smart.rb",
30
35
  "lib/govkit.rb",
31
36
  "lib/govkit/configuration.rb",
32
37
  "lib/govkit/fifty_states.rb",
38
+ "lib/govkit/vote_smart.rb",
33
39
  "rails/init.rb",
34
40
  "spec/fifty_states_spec.rb",
35
41
  "spec/spec.opts",
@@ -0,0 +1,29 @@
1
+ module GovKit
2
+ class Configuration
3
+ attr_accessor :fiftystates_apikey, :fiftystates_base_url
4
+ attr_accessor :votesmart_apikey, :votesmart_base_url
5
+
6
+ def initialize
7
+ @fiftystates_apikey = ''
8
+ @fiftystates_base_url = 'fiftystates-dev.sunlightlabs.com/api'
9
+
10
+ @votesmart_apikey = ''
11
+ @votesmart_base_url = 'api.votesmart.org/'
12
+ end
13
+ end
14
+
15
+ class << self
16
+ attr_accessor :configuration
17
+ end
18
+
19
+ # Configure GovKit in config/initializers/govkit.rb
20
+ #
21
+ # @example
22
+ # GovKit.configure do |config|
23
+ # config.fiftystates_apikey = ''
24
+ # end
25
+ def self.configure
26
+ self.configuration ||= Configuration.new
27
+ yield(configuration)
28
+ end
29
+ end
@@ -0,0 +1,57 @@
1
+ module GovKit
2
+ class FiftyStatesResource < Resource
3
+ default_params :output => 'json', :apikey => GovKit::configuration.fiftystates_apikey
4
+ base_uri GovKit::configuration.fiftystates_base_url
5
+ end
6
+
7
+ module FiftyStates
8
+ ROLE_MEMBER = "member"
9
+ ROLE_COMMITTEE_MEMBER = "committee member"
10
+ CHAMBER_UPPER = "upper"
11
+ CHAMBER_LOWER = "lower"
12
+
13
+ class State < FiftyStatesResource
14
+ def self.find_by_abbreviation(abbreviation)
15
+ response = get("/#{abbreviation}")
16
+ instantiate_record(response)
17
+ end
18
+ end
19
+
20
+ class Bill < FiftyStatesResource
21
+ # http://fiftystates-dev.sunlightlabs.com/api/ca/20092010/lower/bills/AB667/
22
+ def self.find(state_abbrev, session, chamber, bill_id)
23
+ response = get("/#{state_abbrev}/#{session}/#{chamber}/bills/#{bill_id}/")
24
+ instantiate_record(response)
25
+ end
26
+
27
+ def self.search(query, options = {})
28
+ response = get('/bills/search', :query => {:q => query}.merge(options))
29
+ instantiate_collection(response)
30
+ end
31
+
32
+ def self.latest(updated_since, state_abbrev)
33
+ response = get('/bills/latest/', :query => {:updated_since => updated_since, :state => state_abbrev})
34
+ instantiate_collection(response)
35
+ end
36
+ end
37
+
38
+ class Legislator < FiftyStatesResource
39
+ def self.find(legislator_id)
40
+ response = get("/legislators/#{legislator_id}")
41
+ instantiate_record(response)
42
+ end
43
+
44
+ def self.search(options = {})
45
+ response = get('/legislators/search', :query => options)
46
+ instantiate_collection(response)
47
+ end
48
+ end
49
+
50
+ class Vote < FiftyStatesResource
51
+ def self.find(vote_id)
52
+ response = get("/votes/#{vote_id}")
53
+ instantiate_record(response)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,125 @@
1
+ module GovKit
2
+ class GovKitError < StandardError
3
+ attr_reader :response
4
+
5
+ def initialize(response, message = nil)
6
+ @response = response
7
+ @message = message
8
+ end
9
+
10
+ def to_s
11
+ "Failed with #{response.code} #{response.message if response.respond_to?(:message)}"
12
+ end
13
+ end
14
+
15
+ class NotauthorizedError < GovKitError;
16
+ end
17
+
18
+ class InvalidRequestError < GovKitError;
19
+ end
20
+
21
+ class NotFoundError < GovKitError;
22
+ end
23
+
24
+ class NameError < GovKitError;
25
+ end
26
+
27
+ class Resource
28
+ include HTTParty
29
+ format :json
30
+
31
+ attr_accessor :attributes
32
+
33
+ def initialize(attributes = {})
34
+ @attributes = {}
35
+ unload(attributes)
36
+ end
37
+
38
+ class << self
39
+ def instantiate_record(record)
40
+ new(record)
41
+ end
42
+
43
+ def instantiate_collection(collection)
44
+ collection.collect! { |record| instantiate_record(record) }
45
+ end
46
+
47
+ def parse(json)
48
+ instantiate_record(json)
49
+ end
50
+ end
51
+
52
+ def unload(attributes)
53
+ raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
54
+ attributes.each do |key, value|
55
+ @attributes[key.to_s] =
56
+ case value
57
+ when Array
58
+ resource = resource_for_collection(key)
59
+ value.map do |attrs|
60
+ if attrs.is_a?(String) || attrs.is_a?(Numeric)
61
+ attrs.duplicable? ? attrs.dup : attrs
62
+ else
63
+ resource.new(attrs)
64
+ end
65
+ end
66
+ when Hash
67
+ resource = find_or_create_resource_for(key)
68
+ resource.new(value)
69
+ else
70
+ value.dup rescue value
71
+ end
72
+ end
73
+ self
74
+ end
75
+
76
+ private
77
+ def resource_for_collection(name)
78
+ find_or_create_resource_for(name.to_s.singularize)
79
+ end
80
+
81
+ def find_resource_in_modules(resource_name, module_names)
82
+ receiver = Object
83
+ namespaces = module_names[0, module_names.size-1].map do |module_name|
84
+ receiver = receiver.const_get(module_name)
85
+ end
86
+ if namespace = namespaces.reverse.detect { |ns| ns.const_defined?(resource_name) }
87
+ return namespace.const_get(resource_name)
88
+ else
89
+ raise NameError, "Namespace for #{namespace} not found"
90
+ end
91
+ end
92
+
93
+ def find_or_create_resource_for(name)
94
+ resource_name = name.to_s.camelize
95
+ ancestors = self.class.name.split("::")
96
+ if ancestors.size > 1
97
+ find_resource_in_modules(resource_name, ancestors)
98
+ else
99
+ self.class.const_get(resource_name)
100
+ end
101
+ rescue NameError
102
+ if self.class.const_defined?(resource_name)
103
+ resource = self.class.const_get(resource_name)
104
+ else
105
+ resource = self.class.const_set(resource_name, Class.new(GovKit::Resource))
106
+ end
107
+ resource
108
+ end
109
+
110
+ def method_missing(method_symbol, * arguments) #:nodoc:
111
+ method_name = method_symbol.to_s
112
+
113
+ case method_name.last
114
+ when "="
115
+ attributes[method_name.first(-1)] = arguments.first
116
+ when "?"
117
+ attributes[method_name.first(-1)]
118
+ when "]"
119
+ attributes[arguments.first.to_s]
120
+ else
121
+ attributes.has_key?(method_name) ? attributes[method_name] : super
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,22 @@
1
+ module GovKit
2
+ class VoteSmartResource < Resource
3
+ default_params :o => 'JSON', :key => GovKit::configuration.votesmart_apikey
4
+ base_uri GovKit::configuration.votesmart_base_url
5
+ end
6
+
7
+ module VoteSmart
8
+ class Address < VoteSmartResource
9
+ def self.find(candidate_id)
10
+ response = get("/Address.getOffice", :query => {"candidateId" => candidate_id})
11
+ instantiate_record(response['address'])
12
+ end
13
+ end
14
+
15
+ class WebAddress < VoteSmartResource
16
+ def self.find(candidate_id)
17
+ response = get("/Address.getOfficeWebAddress", :query => {"candidateId" => candidate_id})
18
+ instantiate_record(response['webaddress'])
19
+ end
20
+ end
21
+ end
22
+ end
data/lib/gov_kit.rb ADDED
@@ -0,0 +1,20 @@
1
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__))) unless $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__)))
2
+
3
+ require 'active_support'
4
+ require 'gov_kit/configuration'
5
+
6
+ module GovKit
7
+ autoload :FiftyStates, 'gov_kit/fifty_states'
8
+ autoload :VoteSmart, 'gov_kit/vote_smart'
9
+
10
+ class Bill < Resource; end
11
+ class Vote < Resource; end
12
+ class Session < Resource; end
13
+ class Role < Resource; end
14
+ class Legislator < Resource; end
15
+ class Vote < Resource; end
16
+ class Sponsor < Resource; end
17
+ class Version < Resource; end
18
+ class Source < Resource; end
19
+ class Address < Resource; end
20
+ end
@@ -1,10 +1,14 @@
1
1
  module Govkit
2
2
  class Configuration
3
3
  attr_accessor :fiftystates_apikey, :fiftystates_base_url
4
+ attr_accessor :votesmart_apikey, :votesmart_base_url
4
5
 
5
6
  def initialize
6
7
  @fiftystates_apikey = ''
7
8
  @fiftystates_base_url = 'fiftystates-dev.sunlightlabs.com/api'
9
+
10
+ @votesmart_apikey = ''
11
+ @votesmart_base_url = 'api.votesmart.org/'
8
12
  end
9
13
  end
10
14
 
@@ -0,0 +1,146 @@
1
+ require 'httparty'
2
+
3
+ module Govkit::VoteSmart
4
+ class VoteSmartError < StandardError
5
+ attr_reader :response
6
+
7
+ def initialize(response, message = nil)
8
+ @response = response
9
+ @message = message
10
+ end
11
+
12
+ def to_s
13
+ "Failed with #{response.code} #{response.message if response.respond_to?(:message)}"
14
+ end
15
+ end
16
+
17
+ class NotauthorizedError < VoteSmartError;
18
+ end
19
+
20
+ class InvalidRequestError < StandardError;
21
+ end
22
+
23
+ class NotFoundError < VoteSmartError;
24
+ end
25
+
26
+ class NameError < VoteSmartError;
27
+ end
28
+
29
+ class Base
30
+ include HTTParty
31
+ default_params :o => 'JSON', :key => Govkit::configuration.votesmart_apikey
32
+ base_uri Govkit::configuration.votesmart_base_url
33
+
34
+ attr_accessor :attributes
35
+
36
+ def initialize(attributes = {})
37
+ @attributes = {}
38
+ unload(attributes)
39
+ end
40
+
41
+ class << self
42
+ def instantiate_record(record)
43
+ new(record)
44
+ end
45
+
46
+ def instantiate_collection(collection)
47
+ collection.collect! { |record| instantiate_record(record) }
48
+ end
49
+
50
+ def parse(json)
51
+ instantiate_record(json)
52
+ end
53
+ end
54
+
55
+ def unload(attributes)
56
+ raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
57
+ attributes.each do |key, value|
58
+ @attributes[key.to_s] =
59
+ case value
60
+ when Array
61
+ resource = resource_for_collection(key)
62
+ value.map do |attrs|
63
+ if attrs.is_a?(String) || attrs.is_a?(Numeric)
64
+ attrs.duplicable? ? attrs.dup : attrs
65
+ else
66
+ resource.new(attrs)
67
+ end
68
+ end
69
+ when Hash
70
+ resource = find_or_create_resource_for(key)
71
+ resource.new(value)
72
+ else
73
+ value.dup rescue value
74
+ end
75
+ end
76
+ self
77
+ end
78
+
79
+ private
80
+ def resource_for_collection(name)
81
+ find_or_create_resource_for(name.to_s.singularize)
82
+ end
83
+
84
+ def find_resource_in_modules(resource_name, module_names)
85
+ receiver = Object
86
+ namespaces = module_names[0, module_names.size-1].map do |module_name|
87
+ receiver = receiver.const_get(module_name)
88
+ end
89
+ if namespace = namespaces.reverse.detect { |ns| ns.const_defined?(resource_name) }
90
+ return namespace.const_get(resource_name)
91
+ else
92
+ raise NameError, "Namespace for #{namespace} not found"
93
+ end
94
+ end
95
+
96
+ def find_or_create_resource_for(name)
97
+ resource_name = name.to_s.camelize
98
+ ancestors = self.class.name.split("::")
99
+ if ancestors.size > 1
100
+ find_resource_in_modules(resource_name, ancestors)
101
+ else
102
+ self.class.const_get(resource_name)
103
+ end
104
+ rescue NameError
105
+ if self.class.const_defined?(resource_name)
106
+ resource = self.class.const_get(resource_name)
107
+ else
108
+ resource = self.class.const_set(resource_name, Class.new(Base))
109
+ end
110
+ resource
111
+ end
112
+
113
+ def method_missing(method_symbol, * arguments) #:nodoc:
114
+ method_name = method_symbol.to_s
115
+
116
+ case method_name.last
117
+ when "="
118
+ attributes[method_name.first(-1)] = arguments.first
119
+ when "?"
120
+ attributes[method_name.first(-1)]
121
+ when "]"
122
+ attributes[arguments.first.to_s]
123
+ else
124
+ attributes.has_key?(method_name) ? attributes[method_name] : super
125
+ end
126
+ end
127
+ end
128
+
129
+ class Address < Base
130
+ class << self
131
+ def find(candidate_id)
132
+ response = get("/Address.getOffice", :query => {"candidateId" => candidate_id})
133
+ instantiate_record(response['address'])
134
+ end
135
+ end
136
+ end
137
+
138
+ class WebAddress < Base
139
+ class << self
140
+ def find(candidate_id)
141
+ response = get("/Address.getOfficeWebAddress", :query => {"candidateId" => candidate_id})
142
+ instantiate_record(response['webaddress'])
143
+ end
144
+ end
145
+ end
146
+ end
data/lib/govkit.rb CHANGED
@@ -1,8 +1 @@
1
- $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__))) unless $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__)))
2
-
3
- require 'active_support'
4
- require 'govkit/configuration'
5
-
6
- module Govkit
7
- autoload :FiftyStates, 'govkit/fifty_states'
8
- end
1
+ require 'gov_kit'
@@ -2,8 +2,8 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
3
  #TODO: Mock http requests, rather than making an actual call to the api
4
4
 
5
- module Govkit::FiftyStates
6
- describe Govkit::FiftyStates do
5
+ module GovKit::FiftyStates
6
+ describe GovKit::FiftyStates do
7
7
  it "should have the base uri set properly" do
8
8
  [State, Bill, Legislator].each do |klass|
9
9
  klass.base_uri.should == "http://fiftystates-dev.sunlightlabs.com/api"
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 1
9
- version: 0.0.1
8
+ - 2
9
+ version: 0.0.2
10
10
  platform: ruby
11
11
  authors:
12
12
  - Participatory Politics Foundation
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2010-05-10 00:00:00 -07:00
19
+ date: 2010-05-14 00:00:00 -07:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -67,9 +67,15 @@ files:
67
67
  - generators/govkit/govkit_generator.rb
68
68
  - generators/govkit/templates/govkit.rb
69
69
  - govkit.gemspec
70
+ - lib/gov_kit.rb
71
+ - lib/gov_kit/configuration.rb
72
+ - lib/gov_kit/fifty_states.rb
73
+ - lib/gov_kit/resource.rb
74
+ - lib/gov_kit/vote_smart.rb
70
75
  - lib/govkit.rb
71
76
  - lib/govkit/configuration.rb
72
77
  - lib/govkit/fifty_states.rb
78
+ - lib/govkit/vote_smart.rb
73
79
  - rails/init.rb
74
80
  - spec/fifty_states_spec.rb
75
81
  - spec/spec.opts