HttParty: set default header for all requests - ruby-on-rails

In some HTTP Clients theres one way to do something like:
HttpClient.default_headers = { "my-header": "a-value" }
And then every request done with that client will include those headers.
Is there any way to do this with jnunemaker/httparty?

There is!
class MyIntegrationClass
include HTTParty
base_uri 'https://somedomain.com/api'
headers 'my-header' => 'a-value'
def some_method
response = get('/endpoint')
JSON.parse(response.to_s, symbolize_names: true)
end
end
The get request will have the my-header header set.

I found one way to make this work running without having to use class methods:
HTTParty::Basement.headers 'my-header' => 'a value'
You could use it with hooks from rails like this:
require "httparty"
module DefaultHeadersForHttparty
extend ActiveSupport::Concern
included do
before_action :apply_http_party_headers
private
def apply_http_party_headers
HTTParty::Basement.headers 'my-header' => 'a value'
end
end
end

Related

Design pattern to use results of different APIs

I have two APIs with different resources:
www.api-A.com**/consumers,
which returns: {consumers: ['mike', 'Anna', 'Danilo']}
www.api-B.com**/clients,
which returns: {clients: ['Jack', 'Bruce', 'Mary']}
I would like to use these two results in one controller. I want to treat them like if there were just one.
Do I have to create a wrapper for each api like:
module ApiAWrapper
#code here
end
module ApiBWrapper
#code here
end
and call the following inside my controller?
MyController
def index
#clients << ApiAWrapper.most_recent
#clients << ApiBWrapper.most_recent
#clients
end
end
Doing this, #clients will be:
['mike', 'Anna', 'Danilo', 'Jack', 'Bruce', 'Mary']
Is this the right way to use these different APIs with similar responses? Is there a design pattern that I can use or I should read about to guide me?
When I need external services to respond in a common way, I implement a parser. In other languages, you could use interfaces to enforce a method signature contract, but Ruby doesn't have this feature because of the duck typing.
This parser could be a function or a module. For example:
module GitHub
class Service
BASE_URI = 'https://api.github.com'
def self.fetch
response = HTTP.get("#{BASE_URI}/clients")
raise GitHub::ApiError unless response.ok?
Parser.new(response).to_common
end
end
class Parser
def initialize(response)
#response = response
end
def to_common
json_response = JSON.parse(#response)
json_response[:customers] = json_response.delete :clients
# more rules
# ...
json_response
end
end
end
Ok, there you go. Now you've got a Service, for fetching and handling the HTTP part, and the Parser, that handles the response body from the HTTP request. Now, let's suppose that you want to use another API, the BitBucket API, for instance:
module BitBucket
class Service
BASE_URI = 'https://bitbucket.com/api'
def self.fetch
response = HTTP.get("#{BASE_URI}/customers")
raise BitBucket::ApiError unless response.ok?
Parser.new(response).to_common
end
end
class Parser
def initialize(response)
#response = response
end
def to_common
json_response = JSON.parse(#response)
json_response[:clients] = (json_response.delete(:data).delete(:clients))
# more rules
# ...
json_response
end
end
end
This way, you'll have both services returning using the same interface. To join the results, you could do:
data = [GitHub::Service.fetch, BitBucket::Service.fetch, ...]
names = data.map { |customer_list| customer_list[:name] }
names.uniq
You should have wrappers for your API calls anyway because the controller should have as little logic as possible.
Regardless, I would create a class Client with a method to deserialize an array of client jsons into an array of clients. That way, in both wrappers you would call this method and return the array of clients ready to concat in the controller.
Something like:
class Client
attr_accessor :name
def initialize(client_json)
#name = client_json['name']
end
def self.deserialize_clients(clients_json)
clients_json.map{ |c| Client.new(c) }
end
end
Then for the wrappers:
module ApiAWrapper
def self.most_recent
response = #api call here
Client.deserialize_clients(JSON.parse(response.body))
end
end
What do you think?

Abstracting out common rspec functionality

In a large number of our controller tests we must stub out pundit policy functionality like so:
policy = double("policy", :show? => true)
allow(UserPolicy).to receive(:new).and_return(policy)
My goal was to end up with something like allow_policy(UserPolicy).to(:show?) instead which reads much easier.
In order to accomplish this I wrote this little module which I included in my spec.
module PolicyFaker
def allow_policy(policy)
fake_policy = FakedPolicy.new
fake_policy.policy = policy
fake_policy
end
class FakedPolicy
attr_accessor :policy
def to(action)
policy = double("policy", action => true)
allow(policy).to receive(:new).and_return(policy)
end
def not_to(action)
policy = double("policy", action => false)
allow(policy).to receive(:new).and_return(policy)
end
def to_not(action)
not_to(action)
end
end
end
Unfortunately, you cannot use double or allow outside of a describe block and so this module does not work.
How can I accomplish something similar?
Are you including the module via rspec configure?
RSpec.configure do |c|
c.include PolicyFaker
end
If I remember correctly, allow_policy will be available and property scoped if you do it that way.

Access to Rails request inside ActiveSupport::LogSubscriber subclass

I am trying to make a bit of a custom Rails logger which ultimately will log to a database. However, I don't have access to things like the request object, which I very much would like to have.
I'm currently trying to use the LogSubscriber (notification) interface to do the bulk of this; perhaps this is not the right approach. I do know I could abuse Thread.current[] but I was hoping to avoid doing that.
Here's the code I have which is as basic as I can get it for an example. This is loaded in an initializer.
module RequestLogging
class LogSubscriber < ActiveSupport::LogSubscriber
def process_action(event)
pp request # <--- does not work
pp event
end
end
RequestLogging::LogSubscriber.attach_to :action_controller
Probably you need to override process_action in ActionController::Instrumentation and then request object will be accessible like event.payload[:request]. I think you can put code somewhere in config/initializers, code example:
ActionController::Instrumentation.class_eval do
def process_action(*args)
raw_payload = {
controller: self.class.name,
action: self.action_name,
params: request.filtered_parameters,
format: request.format.try(:ref),
method: request.method,
path: (request.fullpath rescue "unknown"),
request: request,
session: session
}
ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
result = super
payload[:status] = response.status
append_info_to_payload(payload)
result
end
end
end
you can get the even.payload then pass it your own CustomLogger(formatted_log(even.payload) and then there you can define a module and save it.
You may want to customise your formatted_log function to beautify the payload accordingly.
def process_action(event)
CustomLogger.application(formattedLog(event.payload))
end
def formattedLog(payload)
# some restructuring of data.
end

Rails 4: prevent 'to_json' from adding a root node?

This feels like it should be simple, but after Googling for an hour I can't figure this out.
I'm POSTing an Amazon S3 'policy document' as JSON to my server.
I need to encode the JSON as is, but Rails is adding stuff to 'params' which is cluttering the JSON I need to encode.
Here is what I have:
class Api::Amazons3Controller < Api::BaseController
def sign_policy
policy_document = params.except(:action, :controller)
encoded_policy_document = Base64.encode64(policy_document.to_json).gsub(/\n|\r/, '')
signature = Base64.encode64(
OpenSSL::HMAC.digest(
OpenSSL::Digest::Digest.new('sha1'),
ENV['AWS_SECRET_ACCESS_KEY'],
policy_document)
).gsub(/\n/, '')
response = { policy: policy_document, signature: signature }
render json: response
end
end
I'm trying to 'clean up' the params with params.except(:action, :controller), but policy_document.to_json adds a root note called 'amazons3' (the controller name) around the JSON, which I don't want. I just need to encode the pure json from the request.
Any help would be highly appreciated!
class Api::Amazons3Controller < Api::BaseController
self.include_root_in_json = false
end
Try this then
config/initializers/wrap_parameters.rb
if defined?(ActiveRecord)
ActiveRecord::Base.include_root_in_json = false
end
I was able to disable the Parameter Wrapping for this Controller by adding:
class Api::Amazons3Controller < Api::BaseController
wrap_parameters format: []

Add api key to every request in ActiveResource

I have 2 RESTful Rails apps I'm trying to make talk to each other. Both are written in Rails 3 (beta3 at the moment). The requests to the service will require the use an api key which is just a param that needs to be on every request. I can't seem to find any information on how to do this.
You define the url the resource connects to via the site= method. There should be an equivalent query_params= method or similar.
There is one good blog post I found related to this and it's from October 2008, so not exactly useful for Rails 3.
Update: I had a thought. Would a small Rack middleware or Metal be the answer to this? It could just pass through the request, tacking it's api_key on.
Use model#prefix_options which is a hash for passing params into query string (or even as substitions for parts of the Model.prefix, e.g. "/myresource/:param/" will be replaced by the value of prefix_options[:param] . Any hash keys not found in the prefix will be added to the query string, which is what we want in your case).
class Model < ActiveResource::Base
class << self
attr_accessor :api_key
end
def save
prefix_options[:api_key] = self.class.api_key
super
end
end
Model.site = 'http://yoursite/'
Model.api_key = 'xyz123'
m = Model.new(:field_1 => 'value 1')
# hits http://yoursite:80/models.xml?api_key=xyz123
m.save
I recently was faced with a similar issue, if you are on Rails3, it supports using custom header which makes life much easier for these situations.
On the side you are making the request from, add
headers['app_key'] = 'Your_App_Key'
to the class you are inheriting from ActiveResource::Base
On you are server, for Authentication, simply receive it as
request.headers['HTTP_APP_KEY']
For Example:
class Magic < ActiveResource::Base
headers['app_key'] = 'Your_App_Key'
end
now Magic.get, Magic.find, Magic.post will all send the app_key
I have much nicer solution ! I try with Rack in middleware but i no find any solution in this way....
I propose you this module for override methods of ActiveReouse::Base
Add this lib in /lib/active_resource/extend/ directory don't forget uncomment
"config.autoload_paths += %W(#{config.root}/lib)" in config/application.rb
module ActiveResource #:nodoc:
module Extend
module AuthWithApi
module ClassMethods
def element_path_with_auth(*args)
element_path_without_auth(*args).concat("?auth_token=#{self.api_key}")
end
def new_element_path_with_auth(*args)
new_element_path_without_auth(*args).concat("?auth_token=#{self.api_key}")
end
def collection_path_with_auth(*args)
collection_path_without_auth(*args).concat("?auth_token=#{self.api_key}")
end
end
def self.included(base)
base.class_eval do
extend ClassMethods
class << self
alias_method_chain :element_path, :auth
alias_method_chain :new_element_path, :auth
alias_method_chain :collection_path, :auth
attr_accessor :api_key
end
end
end
end
end
end
in model
class Post < ActiveResource::Base
include ActiveResource::Extend::AuthWithApi
self.site = "http://application.localhost.com:3000/"
self.format = :json
self.api_key = 'jCxKPj8wJJdOnQJB8ERy'
schema do
string :title
string :content
end
end
Based on Joel Azemar's answer, but I had to make some changes..
First of all, in the active resource gem I used (2.3.8), there is no 'new_element_path', so aliasing that obviously failed..
Second, I updated the way the token is added to the query, because as was, it would break as soon as you add more params yourself. E.g. request for http://example.com/api/v1/clients.xml?vat=0123456789?token=xEIx6fBsxy6sKLJMPVM4 (notice ?token= i.o. &token=)
Here's my updated snippet auth_with_api.rb;
module ActiveResource #:nodoc:
module Extend
module AuthWithApi
module ClassMethods
def element_path_with_auth(id, prefix_options = {}, query_options = nil)
query_options.merge!({:token => self.api_key})
element_path_without_auth(id, prefix_options, query_options)
end
def collection_path_with_auth(prefix_options = {}, query_options = nil)
query_options.merge!({:token => self.api_key})
collection_path_without_auth(prefix_options, query_options)
end
end
def self.included(base)
base.class_eval do
extend ClassMethods
class << self
alias_method_chain :element_path, :auth
# alias_method_chain :new_element_path, :auth
alias_method_chain :collection_path, :auth
attr_accessor :api_key
end
end
end
end
end
end
An Active Resource currently has no good way of passing an api key to the remote service. Passing api_key as a parameter will add it to the objects attributes on the remote service, I assume that this is not the behaviour you'd except. It certainly wasn't the behaviour I needed
I'd recommend that you have a base class inheriting from ActiveResource::Base and override the self.collection_path and self.element_path class methods to always inject the API KEY something like:
class Base < ActiveResource::Base
def self.collection_path(prefix_options = {}, query_options = {})
super(prefix_options, query_options.merge(api_key: THE_API_KEY))
end
def self.element_path(id, prefix_options = {}, query_options = {})
super(id, prefix_options, query_options.merge(api_key: THE_API_KEY))
end
end
class User < Base; end
User.all # GET /users/?api_key=THE_API_KEY
This will always inject your API_KEY in your ActiveResource method calls.
An Active Resource Object behaves much like a (simplified) Active Record object.
If you wish to pass through a new param, then you can set it on the AR object by adding it as an attribute. eg:
jane = Person.create(:first => 'Jane', :last => 'Doe', :api_key => THE_API_KEY)
it should pass the api_key as a parameter, along with all the others.

Resources