I'm writing a wrapper for a third party REST API using HTTParty. I would like to be able to call the third party API using a call to my local module like this:
APIWrapper::APIObject::APIMethod
I would like to write a magic function inside APIWrapper that accepts the function call and decodes the Object and Method to make a call to the third party API. So the call above would do something like this:
params = {
'format' = 'json',
'object' = api_object,
'method' = api_method,
}
get(APIWrapper::BASE_URI, {:query => params})
I want to be able to populate api_object and api_method automatically based on the method called so I don't have to explicitly write a method for every API call I want to be able to make. In PHP, this would be done using a magic __get method. Is there an equivalent in Ruby?
This can definitely be done in Ruby. You need to implement const_missing on APIWrapper, which will return an object that implements method_missing to get the API method part:
module APIWrapper
def const_missing(const_name)
anon_class = Class.new do
def method_missing(name, *params, &block)
params = {
'format' = 'json',
'object' = const_name.to_s,
'method' = name.to_s,
}
get(APIWrapper::BASE_URI, {:query => params})
end
end
end
end
Related
I have a question about lambda function handler in Ruby https://docs.aws.amazon.com/lambda/latest/dg/ruby-handler.html. They say that you can namespace your function logic in a modules and class, like so:
module LambdaFunctions
class ApiHandler
def self.process(event:,context:)
"Hello!"
end
end
end
and then in CDK the handler setting would be source.LambdaFunctions::Handler.process
const apiProxyLambda = new lambda.Function(this, 'apiProxyLambda', {
runtime: lambda.Runtime.RUBY_2_7,
handler: 'source.LambdaFunctions::Handler.process',
});
What if I wanted to define the entry point to ApiHandler#process instance method, like so?
module LambdaFunctions
class ApiHandler
def process(event:,context:)
do_some_stuff
end
private
def do_some_stuff
...
end
end
end
I suspect that the correct reference to the instance method in this case would be source.LambdaFunctions::Handler#process. Does anyone know if this is correct? If this is possible, the my question is, how would lambda 'know' that it needs to instantiate my class first
handler = LambdaFunctions::ApiHandler.new
and then call handler.process(event: event, context: context)?
Is it possible to pass parameters from URL to a view function decorated with #api_view or I need to use APIView class instead?
Yes it's possible. What you've to do is access the request.query_params as below,
#api_view()
def sample_view(request, kw, *args,**kwargs):
url_params = request.query_params
# ypur code
I am trying to stub out the method that makes an external request for some JSON using RSpec 3. I had it working before by placing this into the spec_helper.rb file, but now that I refactored and moved the method into it own class, the stub no longer works.
RSpec.configure do |config|
config.before do
allow(Module::Klass).to receive(:request_url) do
JSON.parse(File.read(File.expand_path('spec/fixtures/example_data.json')))
end
end
end
the class looks like this
module Module
class Klass
# public methods calling `request_url`
...
private
def request_url(url, header = {})
request = HTTPI::Request.new
request.url = url
request.headers = header
JSON.parse(HTTPI.get(request).body)
end
end
end
Despite keeping the spec_helper.rb the same and trying to place the stub right before the actual spec, an external request is still being made.
Your request_url method is an instance and not a class method, therefore you have to write:
allow_any_instance_of(Module::Klass).to receive(:request_url) do
JSON.parse(File.read(File.expand_path('spec/fixtures/example_data.json')))
end
I have the following controller:
class ApiController < ApplicationController
before_filter :authenticate_user_from_token!
private
def authenticate_user_from_token!
#json = JSON.parse(request.body.read)
--auth logic goes here extracting user credentials goes here--
request.body.rewind
if auth_valid
authenticate
else
render nothing: true, status: :unauthorized
end
end
end
Testing this method has proven to be surprisingly difficult. I have tried the following approaches:
1) Sending the private method directly:
#controller.send(authenticate_user_from_token!)
The flaw here, is that I am unsure how to mock out request.body to contain valid/invalid credentials. I have tried the following:
before do
class Hash
def body
self['body']
end
end
#request = {}
#request['body'] =StringIO.new({auth_goes_here}.to_json)
end
However, in the method, request still gets overriden with a brand new ActionController::TestRequest.
2) Posting directly:
before do
post :authenticate_user_from_token!, my_preconstructed_credentials, format: :json
end
which results in:
*** AbstractController::ActionNotFound Exception: The action 'authenticate_user_from_token!' could not be found for ApiController
3) Defining an exposed method at runtime:
before do
#controller.class_eval <<-RUBY_EVAL
public
def update
end
RUBY_EVAL
end
followed by post :update, which still results in *** NoMethodError Exception: undefined methodfoobar' for #`
My question is: how can I test a private before filter on a controller that depends on request.body? I realize I could mock out request and body in my third approach, but I would still require it to respond to read/rewind. How is this kind of method usually tested?
In the end, the following worked:
before do
class ::TestApiController < ApiController
def hello
render nothing: true
end
end
Rails.application.routes.draw do
match 'hello', to: 'test_api#hello'
end
#controller = TestApiController.new
#request.env['RAW_POST_DATA'] = my_awesome_json
post :hello
end
I am just a little confused at why I can't stub a local variable in my controller spec.
Here is my controller:
Class UsersController < ApplicationController
...
def get_company
resp = Net::HTTP.get("http://get_company_from_user_id.com/#{params[:id]}.json")
#resp = JSON.parse(resp.body)
...
My spec looks like:
class ResponseHelper
def initialize(body)
#body = body
end
end
describe "Get company" do
it "returns successful response" do
stub_resp_body = '{"company": "example"}'
stub_resp = ResponseHelper.new(stub_resp_body)
controller.stub!(:resp).and_return(stub_resp)
get :get_company, {:id => #test_user.id}
expect(response.status).to eq(200)
end
end
I still get an error saying that:
Errno::ECONNREFUSED:
Connection refused - connect(2)
What am I doing wrong? If I am stubbing the resp variable why is it still trying to do the HTTP request and how would I stub the resp variable in this case?
You just cannot stub a local variable, you can only stub methods. In your case you can stub the Net::HTTP.get method:
Net::HTTP.stub(:get).and_return(stub_resp)
There is no such thing as 'stubbing a local variable'. The only thing that can be stubbed are method calls.
You would need the stub the Net::HTTP.get call to return something that looks like a Net::HTTPResponse that the rest of your code can work with.
I quite often like to tidy this up by having a client class for each api that knows how to generate the url from the arguments (in this case the id) and how to parse the response. This keeps those details out of the controller and also makes testing easy, since now you can provide a mock client object
You cannot stub a local variable. Just a method. As there were answers above you may want to stub Net::HTTP.get call. However, if you don't want to have you code rely upon a particular HTTP client library you can extract the http request into another method of a controller and stub this method
Class UsersController < ApplicationController
...
def get_company
resp = make_request(params[:id)
#resp = JSON.parse(resp.body)
end
protected
def make_request(id)
Net::HTTP.get('http://get_company_from_user_id.com/#{id}.json')
end
controller.
should_receive(:make_request).
with(#test_user.id).
and_return(stub_resp)