Use a Struct for passing parameters around

This helps avoid bugs due to mispelled param names
This commit is contained in:
Mark Maglana
2013-08-25 23:44:08 -07:00
parent fa2ccf1fc4
commit 193cc5f3cc
7 changed files with 156 additions and 118 deletions

1
.gitignore vendored
View File

@@ -17,3 +17,4 @@ test/version_tmp
tmp
.DS_Store
test/environment.yml
vcr.log

View File

@@ -21,8 +21,14 @@ module Aviator
end
def initialize(params={})
def initialize
params = self.class.params_class.new if self.class.params_class
if params
yield(params) if block_given?
validate_params(params)
end
@params = params
end
@@ -38,7 +44,7 @@ module Aviator
def body?
self.respond_to? :body
self.class.body?
end
@@ -58,12 +64,12 @@ module Aviator
def path?
self.respond_to? :path
self.class.path?
end
def querystring?
respond_to? :querystring
self.class.querystring?
end
@@ -71,32 +77,32 @@ module Aviator
def validate_params(params)
validators = methods.select{ |name| name =~ /^param_validator_/ }
validators.each do |name|
send(name, params)
required_params = self.class.required_params
required_params.each do |name|
raise ArgumentError.new("Missing parameter #{ name }.") if params.send(name).nil?
end
end
# NOTE that, because we are defining the following as class methods, when they
# are called, all 'instance' variables are actually defined in the descendant class,
# not in the instance/object. This is by design since we want to keep these attributes
# within the class and because they don't change between instances anyway.
class << self
def anonymous
# @anonymous will be defined by the descendant class
# where this method (or macro) is called.
@anonymous = true
end
def anonymous?
# @anonymous will be defined by the descendant class
@anonymous == true
end
def api_version(value=nil)
if value
# @api_version will be defined by the descendant
# class where this method is called.
@api_version = value
else
@api_version
@@ -111,8 +117,6 @@ module Aviator
def endpoint_type(value=nil)
if value
# @endpoint_type will be defined by the descendant
# class where this method is called.
@endpoint_type = value
else
@endpoint_type
@@ -122,8 +126,6 @@ module Aviator
def http_method(value=nil)
if value
# @http_method will be defined by the descendant
# class where this method is called.
@http_method = value
else
@http_method
@@ -131,20 +133,46 @@ module Aviator
end
def params_class
all_params = required_params + optional_params
if all_params.length > 0
@params_class ||= Struct.new(*all_params)
end
@params_class
end
def optional_params
@optional_params ||= []
end
def path?
instance_methods.include? :path
end
def querystring?
instance_methods.include? :querystring
end
def required_params
@required_params ||= []
end
private
def required_param(param_name)
required_params << param_name unless required_params.include?(param_name)
end
def requires_param(param_name)
last_num = instance_methods.map{|n| n.to_s.gsub(/^param_validator_/, '').to_i }.max
define_method "param_validator_#{ last_num + 1 }".to_sym, lambda { |params|
raise ArgumentError.new("Missing parameter #{ param_name }.") unless params.keys.include? param_name
}
def optional_param(param_name)
optional_params << param_name unless optional_params.include?(param_name)
end
end

View File

@@ -65,12 +65,12 @@ module Aviator
end
def request(request_name, params)
def request(request_name, &params)
request_class = find_request(request_name)
raise UnknownRequestError.new(request_name) if request_class.nil?
request = request_class.new(params)
request = request_class.new(&params)
http_connection.headers['X-Auth-Token'] = token unless request.anonymous?

View File

@@ -6,8 +6,12 @@ define_request :create_token do
api_version :v2
http_method :post
requires_param :username
requires_param :password
required_param :username
required_param :password
optional_param :tenantName
optional_param :tenantId
def path

View File

@@ -8,10 +8,10 @@ class Aviator::Test
it 'raises an error when a required param is not provided' do
klass = Class.new(Aviator::Request) do
requires_param :someparamname
required_param :someparamname
end
initializer = lambda { klass.new({}) }
initializer = lambda { klass.new }
initializer.must_raise ArgumentError
error = initializer.call rescue $!
@@ -23,10 +23,14 @@ class Aviator::Test
it 'does not raise any error when the required param is provided' do
klass = Class.new(Aviator::Request) do
requires_param :someparamname
required_param :someparamname
end
obj = klass.new({ someparamname: 'someparamvalue' })
# obj = klass.new({ someparamname: 'someparamvalue' })
obj = klass.new do |params|
params.someparamname = 'something'
end
end
end
@@ -230,10 +234,10 @@ class Aviator::Test
end
describe '::requires_param' do
describe '::required_param' do
it 'is a private class method' do
private_method = lambda { Aviator::Request.requires_param }
private_method = lambda { Aviator::Request.required_param }
private_method.must_raise NoMethodError
error = private_method.call rescue $!

View File

@@ -11,14 +11,14 @@ class Aviator::Test
describe '#request' do
def valid_params
{
username: Aviator::Test::Environment.admin[:username],
password: Aviator::Test::Environment.admin[:password]
lambda { |params|
params.username = Aviator::Test::Environment.admin[:username]
params.password = Aviator::Test::Environment.admin[:password]
}
end
def valid_request(params)
def valid_request
service = klass.new(
provider: 'openstack',
service: 'identity',
@@ -29,33 +29,33 @@ class Aviator::Test
}
)
service.request :create_token, params
service.request :create_token, &valid_params
end
it 'knows how to use the bootstrap access_details' do
response = valid_request(valid_params)
response = valid_request
response.status.must_equal 200
end
it 'returns an Aviator::Response object' do
response = valid_request(valid_params)
response = valid_request
response.must_be_instance_of Aviator::Response
end
it 'returns the created Aviator::Request object' do
params = valid_params
response = valid_request(params)
params.each do |key, value|
response.request.params.keys.must_include key
response.request.params[key].must_equal params[key]
end
end
# it 'returns the created Aviator::Request object' do
# params = valid_params.call(Struct.new)
# response = valid_request
#
# params.each do |key, value|
# response.request.params.keys.must_include key
# response.request.params[key].must_equal params[key]
# end
# end
end

View File

@@ -2,6 +2,7 @@ require 'vcr'
VCR.configure do |c|
c.cassette_library_dir = Pathname.new(__FILE__).join('..', '..', 'cassettes')
c.debug_logger = File.open(Pathname.new(__FILE__).join('..', '..', '..', 'vcr.log'), 'w')
c.hook_into :faraday
unless @vcr_port_matcher_registered