rails 4-- accessing api data - ruby-on-rails

I am building a sample rails 4 app and I'm unclear about something. I want to access an external API to pull data on sports news via an ajax call.
So for example if you have a list of teams in the teams#index view, when you click on one team a widget will get populated with the latest results / scores for that team-- the results info is provided by an external API service, not the local database.
Do I need to create a controller for this service to allow the rails ajax request to have a local endpoint? Should the actual request mechanism happen in this controller? or would it be better to build a helper for the data request and call that from the controller?
On the other hand it's possible to do it all via javascript in the browser.
Thanks-- I realize there's a dozen ways to do things in rails, I'm just unclear on the "right" way to handle this type of situation.

I tend to do this with a helper module that you can unit test independently. To give you a similar, trivial example, here's a module that you could use to wrap the Gravatar API:
# /lib/gravatar.rb
module Gravatar
def self.exists email
url = self.image_url email
url = url + '?d=404'
response = HTTParty.get url
return response.code != 404
end
def self.image_url email, size=nil
gravatar_id = self.gravatar_id email
size_url = size ? '?s=' + size.to_s : ''
"http://gravatar.com/avatar/#{gravatar_id}.png" + size_url
end
def self.gravatar_id email
Digest::MD5::hexdigest(email.downcase)
end
end
Then, you can make a call to Gravatar::image_url as necessary. If you wanted to be able to access a Gravatar image via an ajax call, you could simply wrap it in a controller:
# /app/controllers/api/users_controller.rb
class Api::UsersController < Api::BaseController
def gravatar_for_user_id
user = User.find_by_id(params[:id])
render plain: Gravatar::image_url user.email, :status => 200
end
end
This model can be applied to whatever external APIs you need to hit, and modularizing your interface will always make unit testing more straightforward.

Related

HTTParty: Post action is resulting in error Net::HTTPServerException (403 "Forbidden")

I am trying to implement post action using httparty gem and this is what I have. I am running everything in docker and I have code below that will run as active job. I is in one service and I am trying to make post to api in other service. I am able to do get but not having any luck with post. I looked and searched a lot online but I am not sure what is it I am doing wrong. I always get error 403 at self.class.post line. I also tried to do a postman call to api and I am able to hit the api but with the code below its not even reaching to the other service.
Any help is appreciated. Thanks.
require 'uri'
class CustomerProductAPI
include HTTParty
format :json
def initialize(customer_product_id)
#customer_product = CustomerProduct.find(customer_product_id)
#customer = Customer.find(#customer_product.student_id)
#product = Product.find(#customer_product.product_id)
self.class.base_uri environment_based_uri + '/customer_product_api'
end
def create_customer_product
uri = URI(self.class.base_uri + "/customer/#{customer.id}")
self.class.post(uri, body: body_hash).response.value
end
private
attr_reader :customer_product, :customer, :product
def body_hash
{
token: ENV['CUSTOMER_PRODUCT_API_TOKEN'],
customer: customer.name,
product: product.name,
}
end
def environment_based_uri
ENV['CUSTOMER_PRODUCT_URL']
end
end
While we can't actually be sure of exactly what the server thats accepting the response expects you're definately doing quite a few non-idiomatic things here which will aggrevate trouble shooting.
base_uri should just be set in the class body. Not in initialize for each instance. You also do not need to construct a URI with HTTParty. Just pass a path and it will construct the request uri relative to the base_uri.
When getting configuration from ENV use ENV.fetch instead of the bracket accessors as it will raise a KeyError instead of just letting a nil sneak through.
Your HTTP client class should not be concerned with querying the database and handling the potential errors that can occur if the records cannot be found. That should be the responsibility of the controller/job/service object that calls the client. Since you're only actually using three simple attributes it doesn't actually need records at all as input and its actually better that it doesn't have to know about your models and their assocations (or lack thereof in this case).
class CustomerProductAPI
# lets you stub/inspect the constant
CUSTOMER_PRODUCT_URL = ENV.fetch('CUSTOMER_PRODUCT_URL') + '/customer_product_api'
include HTTParty
format :json
base_uri CUSTOMER_PRODUCT_URL
def initialize(id:, product_name:, customer_name:)
#id = id
#product_name = product_name
#customer_name = customer_name
end
def create_customer_product
self.class.post("/customer/#{#id}", body: {
token: ENV.fetch('CUSTOMER_PRODUCT_API_TOKEN'),
customer: #customer_name,
product: #product_name
})
# don't return .response.value as it will make error handling impossible.
# either handle unsuccessful responses here or return the whole response
# for the consumer to handle it.
end
end

How to fetch local JSON data from Rails e.g bookings.json

Hi all very noob question.
I'm trying to store data in a react calendar but it needs to store it using JSON.
I've noticed that when you scaffold, rails automatically also gives you a JSON version.
In my case - http://localhost:3000/users/1/bookings.json
Which returns [{"first_name":"Fake Name","booking_time":"2019-04-22T02:03:00.000Z","pick_up_time":"2019-04-22T02:03:00.000Z"}] in JSON.
I know how to fetch JSON data from a external URL and parse it through however all these external URL's are public whereas in my case the bookings are private.
Is there a way for me to fetch from bookings.json and store it in a variable and also by making it private where I wouldn't need to publicise it?
class HomeController < ApplicationController
def dashboard
#lookup_booking = ???("/users/1/bookings.json")???
end
end
React dashboard
<%= react_component("Booking", { booking: #lookup_booking})%>
You could make a local request to the Bookings JSON endpoint the same way you'd make any external request - using something like HTTParty or Faraday might work:
#lookup_booking = HTTParty.get(user_bookings_url(1))
But this won't be authenticated, so it'll need the same authentication as any other request.
It's a little weird to do it this way unless whatever is generating the bookings is a separate service, or if you want it to be one. If you're going to be using one codebase, you might want to do something similar to what arieljuod suggested in the comments, and simply share the code.
You could break the BookingsController code into an ActiveSupport::Concern or a module, or a Service Object (or, more simply, a method on the User class) and that would then allow you to cleanly share the code between the BookingsController and HomeController. It might look something like this:
# app/services/lookup_user_bookings.rb
class LookupUserBookings
def self.bookings_as_json(user_id)
# complicated logic to find user bookings goes here...
bookings.as_json
end
end
# bookings_controller.rb
class BookingsController
def index
#bookings = LookupUserBookings.bookings_as_json(current_user)
render json: #bookings
end
end
# home_controller
class HomeController
def dashboard
#bookings = LookupUserBookings.bookings_as_json(current_user)
end
end
# dashboard.html.erb
<%= react_component("Booking", { booking: #bookings.to_json })%>

How can I implement Whatsapp life QR code authentication

How can I create a dynamic QR code on a rails app such that the moment it is scanned and successfully processed, the open page bearing the QR code can then just redirect to the success page.
This is similar to the whatsapp web implementation where the moment the android app scans the QR code, the page loads the messages.
Am more interested in is the management of the sessions. When the QR is scanned am able to reload the page where it was displayed and then redirect to another page. any idea?
You could update the User model to be able to store an unique token value to use in you QR Codes; e.g.
$ rails generate migration add_token_to_user token:string
Or a separate related model
$ rails generate model Token value:string user:belongs_to
Then generate unique Token value that can be used within an URL and encode it
into a QRCode
# Gemfile
gem "rqrcode"
# app/models/token.rb
require "securerandom"
class User < ActiveRecord::Base
def generate_token
begin
self.token = SecureRandom.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg"
end while self.class.exists?(token: token)
end
def qr_code
RQRCode::QRCode.new(
Rails.application.routes.url_helpers.url_for(
controller: "session",
action: "create",
email: email,
token: token
)
)
end
end
Then display this QRCode somewhere in your application
# app/views/somewhere.html.erb
<%= #token.qr_code.as_html %>
Then wire up your application's routes and controllers to process that generated
and encoded QRCode URL
# config/routes.rb
Rails.application.routes.draw do
# ...
get "/login", to: "sessions#new"
end
# app/controller/sessions_controller.rb
class SessionsController < ApplicationController
def create
user = User.find_by(email: params[:email], token: params[:token])
if user
session[:user_id] = user.id # login user
user.update(token: nil) # nullify token, so it cannot be reused
redirect_to user
else
redirect_to root_path
end
end
end
References:
whomwah/rqrcode: A Ruby library that encodes QR Codes
Module: SecureRandom (Ruby 2_2_1)
#352 Securing an API - RailsCasts
I am adding a new answer for two reasons:
1. Acacia repharse the question with an emphasis on What's App redirection of
the page with the QR Code being view, which I did not address in my initial
solution due a misunderstanding of the problem, and
2. Some people have found the first answer helpful and this new answer would
change it significantly that whilst similar, but no longer the same
When the QR is scanned am able to reload the page where it was displayed and
then redirect to another page
-- Acacia
In order to achieve this there requires to some kind of open connection on the
page that is displaying the QRCode that something interpretting said QRCode can
use to effect it. However, because of the application you trying to mimic
requires that only that one User viewing the page is effected, whilst not
actually being logged in yet, would require something in the page to be unique.
For the solution to this problem you will need a couple of things:
An unique token to identify the not logged-in User can use to be contacted /
influenced by an external browser
A way of logging in using JavaScript, in order to update the viewed page to
be logged after previous step's event
Some kind of authentication Token that can be exchange between the
application and the external QRCode scanner application, in order to
authentication themselves as a specific User
The following solution stubs out the above 3rd step since this is to
demonstrate the idea and is primarily focused on the server-side of the
application. That being said, the solution to the 3rd step should be as simple
as passing the know User authentication token by appending it to the URL within
the QRCode as an additional paramater (and submitting it as a POST request,
rather than as a GET request in this demonstration).
You will need some random Tokens to use to authentication the User with and
exchange via URL embedded within the QCcode; e.g.
$ rails generate model Token type:string value:string user:belongs_to
type is a reserverd keyword within Rails, used for Single Table Inheritance.
It will be used to specific different kinds of / specialized Tokens within this
application.
To generate unique Token value that can be used within an URL and encode it
into a QRCode, use something like the following model(s) and code:
# Gemfile
gem "rqrcode" # QRCode generation
# app/models/token.rb
require "securerandom" # used for random token value generation
class Token < ApplicationRecord
before_create :generate_token_value
belongs_to :user
def generate_token_value
begin
self.value = SecureRandom.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg"
end while self.class.exists?(value: value)
end
def qr_code(room_id)
RQRCode::QRCode.new(consume_url(room_id))
end
def consume_url(room_id)
Rails.application.routes.url_helpers.url_for(
host: "localhost:3000",
controller: "tokens",
action: "consume",
user_token: value,
room_id: room_id
)
end
end
# app/models/external_token.rb
class ExternalToken < Token; end
# app/models/internal_token.rb
class InternalToken < Token; end
InternalTokens will be only used within the application itself, and are
short-lived
ExternalTokens will be only used to interact with the application from
outside; like your purposed mobile QRCode scanner application; where the User
has either previously registered themselves or has logged in to allow for
this authentication token to be generated and stored within the external app
Then display this QRCode somewhere in your application
# e.g. app/views/tokens/show.html.erb
<%= #external_token.qr_code(#room_id).as_html.html_safe %>
I also hide the current #room_id within the <head> tags of the application
using the following:
# e.g. app/views/tokens/show.html.erb
<%= content_for :head, #room_id.html_safe %>
# app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>QrcodeApp</title>
<!-- ... -->
<%= tag("meta", name: "room-id", content: content_for(:head)) %>
<!-- ... -->
</head>
<body>
<%= yield %>
</body>
</html>
Then wire up your application's routes and controllers to process that generated
and encoded QRCode URL.
For Routes we need:
Route to present the QRCode tokens; "token#show"
Route to consume / process the QRCode tokens; "token#consume"
Route to log the User in with, over AJAX; "sessions#create"
We will also need some way of opening a connection within the display Token page
that can be interacted with to force it to login, for that we will need:
mount ActionCable.server => "/cable"
This will require Rails 5 and ActionCable to implment, otherwise another
Pub/Sub solution; like Faye; will need to be used instead with older versions.
All together the routes look kind of like this:
# config/routes.rb
Rails.application.routes.draw do
# ...
# Serve websocket cable requests in-process
mount ActionCable.server => "/cable"
get "/token-login", to: "tokens#consume"
post "/login", to: "sessions#create"
get "/logout", to: "sessions#destroy"
get "welcome", to: "welcome#show"
root "tokens#show"
end
Then Controllers for those actions are as follows:
# app/controller/tokens_controller.rb
class TokensController < ApplicationController
def show
# Ignore this, its just randomly, grabbing an User for their Token. You
# would handle this in the mobile application the User is logged into
session[:user_id] = User.all.sample.id
#user = User.find(session[:user_id])
# #user_token = Token.create(type: "ExternalToken", user: #user)
#user_token = ExternalToken.create(user: #user)
# keep this line
#room_id = SecureRandom.urlsafe_base64
end
def consume
room_id = params[:room_id]
user_token = params[:user_token] # This will come from the Mobile App
if user_token && room_id
# user = Token.find_by(type: "ExternalToken", value: user_token).user
# password_token = Token.create(type: "InternalToken", user_id: user.id)
user = ExternalToken.find_by(value: user_token).user
password_token = InternalToken.create(user: user)
# The `user.password_token` is another random token that only the
# application knows about and will be re-submitted back to the application
# to confirm the login for that user in the open room session
ActionCable.server.broadcast("token_logins_#{room_id}",
user_email: user.email,
user_password_token: password_token.value)
head :ok
else
redirect_to "tokens#show"
end
end
end
The Tokens Controller show action primarily generates the #room_id value for
reuse in the view templates. The rest of the code in the show is just used to
demonstrate this kind of application.
The Tokens Controller consume action requires a room_id and user_token to
proceed, otherwise redirects the User back to QRCode sign in page. When they are
provided it then generates an InternalToken that is associated with the User
of the ExternalToken that it will then use to push a notification / event to
all rooms with said room_id (where there is only one that is unique to the
User viewing the QRCode page that generate this URL) whilst providing the
necessary authentication information for a User (or in this case our
application) to log into the application without a password, by quickly
generating an InternalToken to use instead.
You could also pass in the User e-mail as param if the external application
knows about it, rather than assuming its correct in this demonstration example.
For the Sessions Controller, as follows:
# app/controller/sessions_controller.rb
class SessionsController < ApplicationController
def create
user = User.find_by(email: params[:user_email])
internal_token = InternalToken.find_by(value: params[:user_password_token])
# Token.find_by(type: "InternalToken", value: params[:user_password_token])
if internal_token.user == user
session[:user_id] = user.id # login user
# nullify token, so it cannot be reused
internal_token.destroy
# reset User internal application password (maybe)
# user.update(password_token: SecureRandom.urlsafe_base64)
respond_to do |format|
format.json { render json: { success: true, url: welcome_url } }
format.html { redirect_to welcome_url }
end
else
redirect_to root_path
end
end
def destroy
session.delete(:user_id)
session[:user_id] = nil
#current_user = nil
redirect_to root_path
end
end
This Sessions Controller takes in the user_email and user_password_token to
make sure that these two match the same User internally before proceeding to
login. Then creates the user session with session[:user_id] and destroys the
internal_token, since it was a one time use only and is only used internally
within the application for this kind of authentication.
As well as, some kind of Welcome Controller for the Sessions create action to
redirect to after logging in
# app/controller/welcome_controller.rb
class WelcomeController < ApplicationController
def show
#user = current_user
redirect_to root_path unless current_user
end
private
def current_user
#current_user ||= User.find(session[:user_id])
end
end
Since this aplication uses
ActionCable, we
have already mounted the /cable path, now we need to setup a Channel that is
unique to a given User. However, since the User is not logged in yet, we use the
room_id value that was previously generated by the Tokens Controller show
action since its random and unique.
# app/channels/tokens_channel.rb
# Subscribe to `"tokens"` channel
class TokensChannel < ApplicationCable::Channel
def subscribed
stream_from "token_logins_#{params[:room_id]}" if params[:room_id]
end
end
That room_id was also embedded within the <head> (although it could a hidden
<div> element or the id attribtue of the QRCode, its up to you), which means
it can be pulled out to use in our JavaScript for receiving incoming boardcasts
to that room/QRCode; e.g.
// app/assets/javascripts/channels/tokens.js
var el = document.querySelectorAll('meta[name="room-id"]')[0];
var roomID = el.getAttribute('content');
App.tokens = App.cable.subscriptions.create(
{ channel: 'TokensChannel', room_id: roomID }, {
received: function(data) {
this.loginUser(data);
},
loginUser: function(data) {
var userEmail = data.user_email;
var userPasswordToken = data.user_password_token; // Mobile App's User token
var userData = {
user_email: userEmail,
user_password_token: userPasswordToken
};
// `csrf_meta_tags` value
var el = document.querySelectorAll('meta[name="csrf-token"]')[0];
var csrfToken = el.getAttribute('content');
var xmlhttp = new XMLHttpRequest();
// Handle POST response on `onreadystatechange` callback
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE ) {
if (xmlhttp.status == 200) {
var response = JSON.parse(xmlhttp.response)
App.cable.subscriptions.remove({ channel: "TokensChannel",
room_id: roomID });
window.location.replace(response.url); // Redirect the current view
}
else if (xmlhttp.status == 400) {
alert('There was an error 400');
}
else {
alert('something else other than 200 was returned');
}
}
};
// Make User login POST request
xmlhttp.open(
"POST",
"<%= Rails.application.routes.url_helpers.url_for(
host: "localhost:3000", controller: "sessions", action: "create"
) %>",
true
);
// Add necessary headers (like `csrf_meta_tags`) before sending POST request
xmlhttp.setRequestHeader('X-CSRF-Token', csrfToken);
xmlhttp.setRequestHeader("Content-Type", "application/json");
xmlhttp.send(JSON.stringify(userData));
}
});
Really there is only two actions in this ActionCable subscription;
received required by ActionCable to handle incoming requests/events, and
loginUser our custom function
loginUser does the following:
Handles incoming data to build a new data object userData to POST back to
our application, which contains User information; user_email &
user_password_token; required to login over AJAX using an authentication
Token as the password (since its somewhat insecure, and passwords are usually
hashed; meaning that they unknown since they cannot be reversed)
Creates a new XMLHttpRequest() object to POST without jQuery, that sends a
POST request at the JSON login URL with the userData as login information,
whilst also appending the current HTML page CSRF token; e.g.
Otherwise the JSON request would fail without it
The xmlhttp.onreadystatechange callback function that is executed on a
response back from the xmlhttp.send(...) function call. It will unsubscribe
the User from the current room, since it is no longer needed, and redirect the
current page to the "Welcomw page" it received back in its response. Otherwise
it alerts the User something failed or went wrong
This will produce the following kind of application
You can access a copy of the project I worked on at the following URL:
Sonna/remote-url_qrcode-signin: ruby on rails - How can I implement Whatsapp life QR code authentication - Stack Overflow
The only this solution does not address is the rolling room token generation,
which would either require either a JavaScript library to generate/regenerate
the URL with the Room Token or a Controller Action that return a regenerated
QRCode as either image or HTML that can be immediately displayed within the
page. Either method still requires you to have some JavaScript that closes the
current connection and opens a new one with a new room/session token that can
used so that only it can receive mesages from, after a certain amount of time.
References:
Action Cable Overview — Ruby on Rails Guides
whomwah/rqrcode: A Ruby library that encodes QR Codes
Module: SecureRandom (Ruby 2_2_1)
#352 Securing an API - RailsCasts

rails invoking methods controller vs model

I've got a rails app. I'm trying to finetune different models and controllers. I can make the things work, but I'm not sure if I'm doing the preferred way. So I have an event.rb for my own in-app fullcalendar where I can CRUD new events and have social.rb for omniauth authorization (google calendar in this case). I'm trying to display the gcal events in my fullcalendar, so based on the social.rb data (tokens, keys) I make an API call to google to get gcal event times and then display the data in events/index page (fullcalendar).
Here are two of my methods I don't know how/where to put in my app. My questions (I have 3 as they are closely connected):
Method types. As I see init_google_api_calendar_client should be a class method if I put it into the social.rb (I'm not even sure if I should place it there). For the get_busy_events I simply can't decide what type to use.
I guess I should put these method in a model/module, but which one (social/event/something else)?
How should I invoke the methods?
events_controller
def index
#user = current_user
#google = #user.socials.where(provider: "google_oauth2").first
#client = Social.init_google_api_calendar_client(#google) ### Is this the proper way to call this method?
#get_busy_times = #####how to call the get_buys_events method?
#event = Event.new
#events = Event.allevents(current_user)
respond_to do |format|
format.html
format.json { render json: #events }
format.js
end
end
social.rb
def self.init_google_api_calendar_client(google_account)
#method only called if google_oauth2 social exists
client = Google::APIClient.new
client.authorization.access_token = google_account.token
client.authorization.client_id = ENV['GOOGLE_API_KEY']
client.authorization.client_secret = ENV['GOOGLE_API_SECRET']
client.authorization.refresh_token = google_account.refresh_token
return client
end
Where to put this method? Should be class/instance? How should be invoked?
def get_busy_events(client)
service = client.discovered_api('calendar', 'v3')
result = client.execute(
api_method: service.freebusy.query,
body_object: { timeMin: start_at,
timeMax: end_at,
items: items},
headers: {'Content-Type' => 'application/json'})
end
You're asking a good question and in my experience there isn't a universally adopted convention in the Rails community for where to put code that makes server-side API calls to 3rd party services.
When your app uses external APIs in ways that are okay to be asynchronous, it would be appropriate to invoke these calls using ActiveJob or gems like sidekiq directly. Here's a decent write-up (see the section labeled "Talking with external APIs"): https://blog.codeship.com/how-to-use-rails-active-job/
But, if you are building an app that truly dependent on the 3rd-party API and asynchronous calls don't provide you much benefit, then I suggest defining a different type of class that is neither a model, view, nor controller. A common practice to follow when there aren't already gems to meet your needs is to write a ruby wrapper class for the API saved under the lib folder. Keep your model methods scoped towards logic that requires database interactivity. Invoke model methods from your controller as well as webservice methods defined in your wrapper class.
Good luck!

Very Basic Rails 4.1 API Call using HTTParty

Relatively new to Rails. I am trying to call an API and it's supposed to return a unique URL to me. I have HTTParty bundled on my app. I have created a UniqueNumber controller and I have read through several HTTParty guides as far as what I want but maybe I'm just a bit lost and really have no idea what to do.
Basically, all I need to do is call the API, get the URL it returns, then insert that URL into the database for a user. Can anyone point me in the right direction or share some code with me?
Let's assume the API is in a JSON format and returns the data like so:
{"url": "http://example.com/unique-url"}
To keep things tidy and well structured, the API logic should belong in it's own class:
# lib/url_api.rb
require 'httparty'
class UrlApi
API_URL = 'http://example.com/create'
def unique_url
response = HTTParty.get(API_URL)
# TODO more error checking (500 error, etc)
json = JSON.parse(response.body)
json['url']
end
end
Then call that class in the controller:
require 'url_api'
class UniqueNumberController < ApplicationController
def create
api = UrlApi.new()
url = api.unique_url
#user = # Code to retrieve User
#user.update_attribute :url, url
# etc
end
end
Basically HTTParty returns a response object that contains the HTTP response data which includes both the headers and the actual content (.body). The body contains a string of data that you can process as you like. In this case, we're parsing the string as JSON into a Ruby hash. If you need to customise the HTTP request to the API you can see all the options in the HTTParty documentation.

Resources