Rails upload pdf file via POST request - ruby-on-rails

In my Rails 7 app third party API require to send the PDF file created by my application. I've got sample curl from the API docs:
curl -X POST --header 'Content-Type: multipart/form-data' --header 'Accept: application/json' {"type":"formData"} 'https://some_path.com'
I'm using Faraday gem with below implementation of client and resource:
# app/clients/test_api/client.rb
module TestApi
class Client
include ::Errors
API_ENDPOINT = 'https://some_path.com'
ACCESS_TOKEN = Rails.application.credentials.api_token
def initialize
#access_token = ACCESS_TOKEN
end
def post(path, options = {})
handle_response(client.public_send(:post, path.to_s, options.to_json))
end
attr_reader :access_token
private
def client
#client =
Faraday.new(API_ENDPOINT) do |client|
client.request :url_encoded
client.response :json, content_type: /\bjson$/
client.adapter Faraday.default_adapter
client.headers['Accept'] = 'application/json'
client.headers['Content-Type'] = 'application/json'
client.headers['apiToken'] = access_token.to_s if access_token.present?
end
end
def handle_response(response)
return response_body(response) if response.success?
raise error_class(response.status)
end
def response_body(response)
response&.body
end
end
end
# app/clients/test_api/resources.rb
module TestApi
class Resources
def upload_document(file)
client.post('/sspfile/uploadtemporary', body: file)
end
private
def client
#client ||= Client.new
end
end
end
But how to send the file as a body params? I've got the API error with my implementation so seems like it's not that simple.

You have to use Faraday::UploadIO class to first create an UploadIO object and pass that into the request.
Make changes in these lines of your code:
# app/clients/test_api/client.rb
module TestApi
class Client
...
def post(path, options = {})
handle_response(client.public_send(:post, path.to_s, options.to_json))
end
...
end
end
# app/clients/test_api/resources.rb
module TestApi
class Resources
def upload_document(file)
uploaded = Faraday::UploadIO.new(file, 'application/pdf')
client.post('/sspfile/uploadtemporary', file: uploaded)
end
...
end
end
Can read here for more info on UploadIO class: https://rubydoc.info/gems/multipart-post/1.1.0/UploadIO

Related

WebMock model's function when called via controller

I have existing functionality, where specs calls HTTP PUT method of a controller, which in-turn calls model's method to get some JSON data via. API.
class SomeModel
def GetData()
uri = URI.parse('http://api.mydomain.com/getmydata/123/ohmyurl')
Net::HTTP.start(uri.host, uri.port, :read_timeout => 200) do |http|
request = Net::HTTP::Get.new(uri.request_uri)
response = http.request(request)
unless response.body == "null"
return JSON.parse(response.body)
end
#......
end
end
end
class SomeController < ApplicationController
def update
#...
#model.GetData()
#...
end
end
#......specs............
put :update
I need to mock the API in SomeModel. So far I have tried:
#......specs............
before do
stub_request(:get, /api.mydomain.com/)
.with(headers: {'Accept'=>'application/json' })
.to_return(status: 200, body: {id: 1, secondParam: '324'}.to_json, headers: {})
end
#.......
put :update
result = JSON.load response.body
expect(result['secondParam']).to eq("324")
Which is not able to mock the API call and actual API is called.
kindly ignore syntax errors

How should I test this code with rspec rails?

In my rails application I have a module with an API call that I would like to write rspec tests for. What would you write as proper tests for this? I have read not to combine httparty with rspec because it is very slow and now I have no idea how to do this. Thanks for the advice!
module BandsInTown
class API
attr_reader :artist_name, :info, :events
def initialize(artist_name)
#artist_name = artist_name
end
def artist_info
#info = fetch_data
end
def artist_events
#events = fetch_data 'events'
end
private
def fetch_data(endpoint = nil)
request_url = "http://api.bandsintown.com/artists/#{[#artist_name, endpoint].compact.join('/')}.json?api_version=2.0&app_id=AUTH_KEY"
resp = HTTParty.get(request_url)
return false if resp.code != 200
resp
end
end
end
I would use webmock to get around making API requests.
Here's an example on how the code in yout spec_helper.rb could look like:
require 'webmock/rspec'
def stub_api_requests
response = File.read('spec/fixtures/payload.json')
stub_request(:get, %r{http:\/\/api.bandsintown.com\/artists/\/(.)*})
.to_return(
status: 200,
body: response
)
end
config.before(:each) do
stub_api_requests
end

How to setup angular-rails 4.2 - devise registration/authentication?

Can you give an advice or recommend some resources related to this topic? I understand how to it in a theory. But I also heard about jwt etc. What are the best practices to implement device/angular/rails role based auth/registration?
The short answer is to read this blog post which goes into details of how the concept is minimally implemented
This would be a long code answer, but I plan to write separate blog post on how to implement it in much more details...
but for now, here is how I implemented it in some project...
First the angular app part, you can use something like Satellizer which plays nicely...
here is the angular auth module in the front-end app
# coffeescript
config = (
$authProvider
$stateProvider
) ->
$authProvider.httpInterceptor = true # to automatically add the headers for auth
$authProvider.baseUrl = "http://path.to.your.api/"
$authProvider.loginRedirect = '/profile' # front-end route after login
$authProvider.logoutRedirect = '/' # front-end route after logout
$authProvider.signupRedirect = '/sign_in'
$authProvider.loginUrl = '/auth/sign_in' # api route for sign_in
$authProvider.signupUrl = '/auth/sign_up' # api route for sign_up
$authProvider.loginRoute = 'sign_in' # front-end route for login
$authProvider.signupRoute = 'sign_up' # front-end route for sign_up
$authProvider.signoutRoute = 'sign_out' # front-end route for sign_out
$authProvider.tokenRoot = 'data'
$authProvider.tokenName = 'token'
$authProvider.tokenPrefix = 'front-end-prefix-in-localstorage'
$authProvider.authHeader = 'Authorization'
$authProvider.authToken = 'Bearer'
$authProvider.storage = 'localStorage'
# state configurations for the routes
$stateProvider
.state 'auth',
url: '/'
abstract: true
templateUrl: 'modules/auth/auth.html'
data:
permissions:
only: ['guest']
redirectTo: 'profile'
.state 'auth.sign_up',
url: $authProvider.signupRoute
views:
'sign_up#auth':
templateUrl: 'modules/auth/sign_up.html'
controller: 'AuthenticationCtrl'
controllerAs: 'vm'
.state 'auth.sign_in',
url: $authProvider.loginRoute
views:
'sign_in#auth':
templateUrl: 'modules/auth/sign_in.html'
controller: 'AuthenticationCtrl'
controllerAs: 'vm'
this is the basic configurations for satellizer... as for the authentication controller... it's something like following
#signIn = (email, password, remember_me) ->
$auth.login
email: email
password: password
remember_me: remember_me
.then(success, error)
return
#signUp = (name, email, password) ->
$auth.signup
name: name
email: email
password: password
.then(success, error)
return
this is the basics for authenticating
as for the backend (RoR API) you should first allow CORS for the front-end app. and add gem 'jwt' to your gemfile.
second implement the API controller and the authentication controller
for example it might look something like the following
class Api::V1::ApiController < ApplicationController
# The API responds only to JSON
respond_to :json
before_action :authenticate_user!
protected
def authenticate_user!
http_authorization_header?
authenticate_request
set_current_user
end
# Bad Request if http authorization header missing
def http_authorization_header?
fail BadRequestError, 'errors.auth.missing_header' unless authorization_header
true
end
def authenticate_request
decoded_token ||= AuthenticationToken.decode(authorization_header)
#auth_token ||= AuthenticationToken.where(id: decoded_token['id']).
first unless decoded_token.nil?
fail UnauthorizedError, 'errors.auth.invalid_token' if #auth_token.nil?
end
def set_current_user
#current_user ||= #auth_token.user
end
# JWT's are stored in the Authorization header using this format:
# Bearer some_random_string.encoded_payload.another_random_string
def authorization_header
return #authorization_header if defined? #authorization_header
#authorization_header =
begin
if request.headers['Authorization'].present?
request.headers['Authorization'].split(' ').last
else
nil
end
end
end
end
class Api::V1::AuthenticationsController < Api::V1::ApiController
skip_before_action :authenticate_user!, only: [:sign_up, :sign_in]
def sign_in
# getting the current user from sign in request
#current_user ||= User.find_by_credentials(auth_params)
fail UnauthorizedError, 'errors.auth.invalid_credentials' unless #current_user
generate_auth_token(auth_params)
render :authentication, status: 201
end
def sign_out
# this auth token is assigned via api controller from headers
#auth_token.destroy!
head status: 204
end
def generate_auth_token(params)
#auth_token = AuthenticationToken.generate(#current_user, params[:remember_me])
end
end
The AuthenticationToken is a model used to keep track of the JWT tokens ( for session management like facebook)
here is the implementation for the AuthenticationToken model
class AuthenticationToken < ActiveRecord::Base
## Relations
belongs_to :user
## JWT wrappers
def self.encode(payload)
AuthToken.encode(payload)
end
def self.decode(token)
AuthToken.decode(token)
end
# generate and save new authentication token for the user
def self.generate(user, remember_me = false)
#auth_token = user.authentication_tokens.create
#auth_token.token = AuthToken.generate(#auth_token.id, remember_me)
#auth_token.save!
#auth_token
end
# check if a token can be used or not
# used by background job to clear the authentication collection
def expired?
AuthToken.decode(token).nil?
end
end
it uses a wrapper called AuthToken which wraps the JWT functionality
here is it's implementation
# wrapper around JWT to encapsulate it's code
# and exception handling and don't polute the AuthenticationToken model
class AuthToken
def self.encode(payload)
JWT.encode(payload, Rails.application.secrets.secret_key_base)
end
def self.decode(token)
payload = JWT.decode(token, Rails.application.secrets.secret_key_base)[0]
rescue JWT::ExpiredSignature
# It will raise an error if it is not a token that was generated
# with our secret key or if the user changes the contents of the payload
Rails.logger.info "Expired Token"
nil
rescue
Rails.logger.warn "Invalid Token"
nil
end
def self.generate(token_id, remember_me = false)
exp = remember_me ? 6.months.from_now : 6.hours.from_now
payload = { id: token_id.to_s, exp: exp.to_i }
self.encode(payload)
end
end

Checking user email calling Closio API in Rails - HTTParty and no method errors

I'm working with a team on checking a user's email input when they sign up for a web app. The user will not be allowed to sign up if their email is not found with the following API call using HTTParty. We are getting method_errors for whatever syntax is first within the function. For, example, in the method below, "include" comes up as an undefined method error.
def email_checker
include HTTParty
default_params :output => 'json'
format :json
base_uri 'app.close.io'
basic_auth 'insert_api_code_here', ' '
response = HTTParty.get('/api/v1/contact/')
#email_database = []
response['data'].each do |x|
x['emails'].each do |contact_info|
#email_database << contact_info['email']
end
end
unless #email_database.include? :email
errors.add :email, 'According to our records, your email has not been found!'
end
end
UPDATE: So we went with the inline version of using HTTParty and our registrations controller (working with devise) looks like this:
class RegistrationsController < Devise::RegistrationsController
def email_checker(email)
YAML.load(File.read('config/environments/local_env.yml')).each {|k, v| ENV[k.to_s] = v}
api_options = {
query: => {:output => 'json'},
format: :json,
base_uri: 'app.close.io',
basic_auth: ENV["API_KEY"], ' '
}
response = HTTParty.get('/api/v1/contact/', api_options)
#email_database = []
response['data'].each do |x|
x['emails'].each do |contact_info|
#email_database << contact_info['email']
end
end
unless #email_database.include? email
return false
else
return true
end
end
def create
super
if email_checker == false
direct_to 'users/sign_up'
#and return to signup with errors
else
User.save!
end
end
end
We're getting syntax error: "syntax error, unexpected =>" Did we screw up the format?
There are two different ways to use HTTParty, and you're trying to use both. Pick one :).
The class-based method would look something like this:
class CloseIo
include HTTParty
default_params :output => 'json'
format :json
base_uri 'app.close.io'
basic_auth 'insert_api_code_here', ' '
end
class UserController
def email_checker
response = CloseIo.get('/api/v1/contact/')
# ... the rest of your stuff
end
end
An inline version would look something like this
class UserController
def email_checker
api_options = {
query: :output => 'json',
format: :json,
base_uri: 'app.close.io',
basic_auth: 'insert_api_code_here'
}
response = HTTParty.get('/api/v1/contact/', api_options)
# ... do stuff
end
end

net/http hanging requests causing failure - RoR

I have a bit of code that checks the response code of a list of URL's and presents them back - I am having trouble with a few of URL's that are hanging which causes the application not load at all. How can I make the request to give up after 30 seconds and check the next URL marking the skipped URL as failure.
below is my current code;
(Model/status.rb)
require "net/http"
require "uri"
class Status
def initialize(url)
#url = url
end
def get_status
response.code
end
def active?
["200","203","302"].include?(get_status) ? true : false
end
private
def lookup
URI.parse(#url)
end
def http
Net::HTTP.new(lookup.host, lookup.port)
end
def request
Net::HTTP::Get.new(lookup.request_uri)
end
def response
http.request(request)
end
end
(controllers/welcome_controller.rb)
class WelcomeController < ApplicationController
def index
#syndication = [
["http://v1.syndication.u01.example.uk/organisations?apikey=bbccdd", "U.INT 01"],
["http://v1.syndication.u02.example.uk/organisations?apikey=bbccdd", "U.INT 02"],
].collect { |url| logger.info("Boom #{url[0]}"); ["#{url[1]} (#{url[0]})", Status.new(url[0]).active?] }
end
end
Got the answer..
adding the following to my "def get_status"
def get_status
begin
response.code
rescue Exception => e
Rails.logger.info("Error #{e}")
end
end
This logged the error and the went to the next URL

Resources