Thomascountz
6/8/2017 - 1:14 AM

API wrapper for Propublica's Congress API

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.