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.
Related
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
Before coinmarketcap made their API into tiered free/payed you could get it working with
class Currency < ApplicationRecord
def current_price`
url = 'https://api.coinmarketcap.com/v1/ticker/'
request = HTTParty.get(url + self.slug)
response = JSON.parse(request.body)
end
end
But now it requires an API key which you can get on the basic free tier but I'm at a loss as to where to implement the API key in the above code?
Like I know i need a get and to include the API key but they only mention how to do that with Python C#
The docs clearly state that:
You can supply your API Key in REST API calls in one of two ways:
Preferred method: Via a custom header named X-CMC_PRO_API_KEY
Convenience method: Via a query string parameter named CMC_PRO_API_KEY
With HTTParty providing headers is trivial:
url = 'https://pro-api.coinmarketcap.com/v1/ticker/'
request = HTTParty.get(url + self.slug,
headers: { "X-CMC_PRO_API_KEY" => Rails.application.credentials.coinmarketcap[:pro_api_key] }
)
But you really should avoid doing HTTP calls from your model as it already has way to many responsibilities. Create a separate class instead that touches the application boundary:
class CoinMarketCapClient
include HTTParty
format :json
base_uri "https://pro-api.coinmarketcap.com"
attr_reader :api_key
def intialize(api_key:)
#api_key = api_key
end
def ticker(slug, **opts)
self.class.get("/ticker/#{slug}", headers: {
"X-CMC_PRO_API_KEY" => api_key
})
end
end
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 })%>
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.
How to write the response from the controller using the ActionDispatch::Response object. There seems to be no api that does http://api.rubyonrails.org/classes/ActionDispatch/Response.html.
The below code works which does not use any view. Is the same can be achived using a response object. The reason being having a necessity to write some binary data to html(which is required for the the current rails app being written)
class HelloController < ApplicationController
def index
render :text => "hello" # want to use ActionDispatch::Response object instead of this
end
end
Have you taken a look at send_data? It may be what you're looking for.