API wrapper for Propublica's Congress API
# Abstracts queries to The Propublica Congress API into methods
class Congress
attr_reader :query
def initialize(args)
@query = args.fetch(:query, nil)
end
def members_of_senate(senate_number = 115)
get("#{senate_number}/senate/members.json")
end
private
def get(path)
query.get(path)
end
end
# Interface to build API requests
class Query
attr_reader :request, :base_url, :api_key
def initialize(args)
@request = args.fetch(:request, nil)
@base_url = args.fetch(:base_url, nil)
@api_key = args.fetch(:api_key, nil)
end
def get(path)
request.get(full_path(path), headers)
end
private
def full_path(path)
base_url.to_s + path.to_s
end
def headers
{ headers: { 'X-API-Key' => api_key } }
end
end
require 'HTTParty'
base_url = 'http://api.propublica.org/congress/v1/'
puts Congress.new(
query: Query.new(request: HTTParty,
base_url: base_url,
api_key: API_KEY)
).members_of_senate
# Query, what's your base_url? # => YES!
# Query, what's your api_key? # => YES!
# Query, what's your request? # => YES!
# Congress, what's your members_of_senate? # => YES!
# Congress, what's your query? # => YES!
# Congress has at least four dependencies on Request
# - The name of another class
# -- Congress expects a class named Request to exist
# => SOLUTION
# Dependency Injection. Congress now depends only on a 'duck' that responds to
# .get. Though Congress needs to send .get, somewhere, it doesn't need to know
# about Request. We can now pass in any object that has a .get method.
# - The name of the message it intends to send to somehwere other than self
# -- Congress expects Request to response to .get
# => SOLUTION
# Isolate vulnerable external messages. You can decrease a method's relience on
# an external method by wrapping it's external message in a method of it's own.
# Now, if Query#get changes, Congress will only have to change it's external
# call in one place.
# - The arguement that a message requires
# -- Congress knows that Request#new requires a base_url and api_key, etc.
# => SOLUTION
# When you send a message that requires arguemnts, having knowledge of those
# arguments are inevitable. But a bigger problem is the arguement order.
# See next.
# - The order of those arguments
# -- Congress knows the first argument of Request#new should be base_url, etc.
# => SOLUTION
# Remove argument order dependencies with an intialize hash and explicitly
# defining defaults by using "||", ".fetch", or isolating them inside of a
# seperate "defaults" method and ".merge" them into the initialize args hash.