Skip to content

Commit

Permalink
Refactor URI in prepration for addressable upgrade
Browse files Browse the repository at this point in the history
To prepare for loosening the addressable gem version, refactor our URI
extensions to use composition rather than monkey-patching.
  • Loading branch information
jstotz committed Feb 21, 2018
1 parent b60de3c commit ebfe17e
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 64 deletions.
9 changes: 2 additions & 7 deletions lib/springboard/client.rb
@@ -1,6 +1,5 @@
require 'rubygems'
require 'patron'
require 'addressable/uri'
require 'json'

require 'springboard/client/errors'
Expand All @@ -18,10 +17,6 @@ module Springboard
# Provides direct access to the URI-oriented interface via the HTTP methods.
# Provides access to the URI-oriented interface via the {#[]} method.
class Client
##
# Alias for {Addressable::URI}
URI = Addressable::URI

##
# Default number of records per page when iterating over collection resources
DEFAULT_PER_PAGE = 20
Expand Down Expand Up @@ -85,7 +80,7 @@ def auth(opts={})
unless opts[:username] && opts[:password]
raise "Must specify :username and :password"
end
body = URI.form_encode \
body = ::Addressable::URI.form_encode \
:auth_key => opts[:username],
:password => opts[:password]
response = post '/auth/identity/callback', body,
Expand Down Expand Up @@ -265,4 +260,4 @@ def configure_session(base_url, opts)
require 'springboard/client/resource'
require 'springboard/client/response'
require 'springboard/client/body'
require 'springboard/client/uri_ext'
require 'springboard/client/uri'
96 changes: 96 additions & 0 deletions lib/springboard/client/uri.rb
@@ -0,0 +1,96 @@
require 'addressable/uri'

module Springboard
class Client
##
# A wrapper around Addressable::URI
class URI
##
# Returns a URI object based on the parsed string.
#
# @return [URI]
def self.parse(value)
return value if value.is_a?(self)
new(::Addressable::URI.parse(value))
end

##
# Joins several URIs together.
#
# @return [URI]
def self.join(*args)
new(::Addressable::URI.join(*args))
end

##
# Creates a new URI object from an Addressable::URI
#
# @return [URI]
def initialize(uri)
@uri = uri
end

##
# Returns a new URI with the given subpath appended to it. Ensures a single
# forward slash between the URI's path and the given subpath.
#
# @return [URI]
def subpath(subpath)
uri = dup
uri.path = "#{path}/" unless path.end_with?('/')
uri.join subpath.to_s.gsub(/^\//, '')
end

##
# Merges the given hash of query string parameters and values with the URI's
# existing query string parameters (if any).
def merge_query_values!(values)
self.springboard_query_values = (self.query_values || {}).merge(normalize_query_hash(values))
end

def ==(other_uri)
return false unless other_uri.is_a?(self.class)
uri == other_uri.__send__(:uri)
end

private

attr_reader :uri

def springboard_query_values=(values)
retval = self.query_values = normalize_query_hash(values)
# Hack to strip digits from Addressable::URI's subscript notation
self.query = self.query.gsub(/\[\d+\]=/, '[]=')
retval
end

def self.delegate_and_wrap(*methods)
methods.each do |method|
define_method(method) do |*args, &block|
result = @uri.__send__(method, *args, &block)
if result.is_a?(Addressable::URI)
self.class.new(result)
else
result
end
end
end
end

delegate_and_wrap(
:join, :path, :path=, :form_encode, :to_s,
:query_values, :query_values=, :query, :query=
)

def normalize_query_hash(hash)
hash.inject({}) do |copy, (k, v)|
copy[k.to_s] = case v
when Hash then normalize_query_hash(v)
when true, false then v.to_s
else v end
copy
end
end
end
end
end
51 changes: 0 additions & 51 deletions lib/springboard/client/uri_ext.rb

This file was deleted.

2 changes: 1 addition & 1 deletion spec/spec_helper.rb
Expand Up @@ -12,6 +12,6 @@

class String
def to_uri
Addressable::URI.parse(self)
Springboard::Client::URI.parse(self)
end
end
@@ -1,13 +1,42 @@
require 'spec_helper'

describe Addressable::URI do
let(:uri) { Addressable::URI.parse('/relative/path') }
describe Springboard::Client::URI do
let(:uri) { described_class.parse('/relative/path') }

describe "subpath" do
it "should return a new URI with the path relative to the receiver" do
expect(uri.subpath('other')).to eq(Addressable::URI.parse('/relative/path/other'))
expect(uri.subpath('/other')).to eq(Addressable::URI.parse('/relative/path/other'))
uri.subpath(Addressable::URI.parse('/other')) == Addressable::URI.parse('/relative/path/other')
expect(uri.subpath('other')).to eq(described_class.parse('/relative/path/other'))
expect(uri.subpath('/other')).to eq(described_class.parse('/relative/path/other'))
uri.subpath(described_class.parse('/other')) == described_class.parse('/relative/path/other')
end
end

describe "parse" do
it "should return a URI based on the given string" do
uri = described_class.parse('/some_path')
expect(uri).to be_a(described_class)
expect(uri.to_s).to eq('/some_path')
end
end

describe "==" do
it "should consider two URIs parsed from the same string equal" do
expect(
described_class.parse('http://example.com/some_path?a=1&b=2') ==
described_class.parse('http://example.com/some_path?a=1&b=2')
).to be(true)
end

it "should consider two URIs parsed from different strings not equal" do
expect(
described_class.parse('http://example.com/some_path?a=1&b=2') ==
described_class.parse('http://example.com/some_path?a=1&c=3')
).to be(false)

expect(
described_class.parse('http://foo.example.com') ==
described_class.parse('http://bar.example.com')
).to be(false)
end
end

Expand Down

0 comments on commit ebfe17e

Please sign in to comment.