Using HTTParty gem to make an endpoint (Rails5) - ruby-on-rails

I'm a bit confused on how to handle this. Essentially I'm trying to learn more about Angular on Rails, so I'm using Rails in API Mode - However, currently, I'm trying to build an API into the non-API version of Rails I initiated (Not quite relevant, but I'm curious how to get it to work for both).
Very basic test off the bat, I'd like to his
/steam/:id endpoint (in this example) and get the return for
GetPlayedSummaries. HTTParty is installed, and working in terminal.
class SteamController < ApplicationController
#steamKey = "{REDACTED}"
#baseUrl = "http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key='+#steamKey+'&steamids='+#steamId+'"
def get_player_summaries
#steamId = params[:id]
puts #baseUrl
#games = HTTParty.get(#baseUrl).to_json
puts response.body, response.code, response.message, response.headers.inspect
end
end
This is my very basic controller. Realistically I have no reason to have steamKey separated but it helps encourage where I need help.
Originally I had #baseUrl as
#baseUrl = "http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=#{#steamKey}+'&steamids=#{#steamId}"
With the full version I get a bad argument (expected URI object or URI string) pointing at the #baseUrl so I feel like I'm not including it properly. I'm working on that but even once it works I'm not quite sure how to make it so it just throws it out into the UI as pure JSON? I have this done with a lot of my other elements using JSON Builders etc, but that's DB Data. This is just an API call.
Insight/documents I can check out?
EDIT: I believe my issue regarding using #steamKey is related to This SO Article. # is even being passed down to get_player_summaries

As mentioned in my edit, I was able to find the article to explain how to properly use URLs.
My working code is:
class SteamController < ApplicationController
def initialize
#steamKey = "{REDACTED}"
#baseUrl = "http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key="+#steamKey+"&steamids="
end
def get_player_summaries
#playerUrl = #baseUrl + params[:id]
#games = HTTParty.get(#playerUrl).to_json
puts response.body, response.code, response.message, response.headers.inspect
render json: #games
end
end
It's a bit slow on the response but could be an issue with C9.

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

Displaying GET request from HTTparty on view

I currently have a simple ruby file named example.rb. How can I make a view that allows a user to submit information into a form and then have the information from the GET request returned to them? I understand how to use these requests from the console, but not from the front-end.
Resources on this topic would also be greatly appreciated.
require 'rubygems'
require 'httparty'
class StackExchange
include HTTParty
base_uri 'api.stackexchange.com'
def initialize(service, page)
#options = {query: {site: service}}
end
def questions
self.class.get('/2.2/questions', #options)
end
def users
self.class.get('/2.2/users', #options)
end
end
stack_exchange = StackExchange.new('stackoverflow',1)
puts stack_exchange.users
Make sure the HTTParty gem is in your application's Gemfile.
Take example.rb and put it in /app/models/stack_exchange.rb — yes the file name does matter[0] (this isn't the purists place to put this, but for beginners it's fine and perfectly acceptable). Remove the code at the bottom you're using to test it as well.
in routes.rb add this route: get '/test' => 'application#test'
in your application_controller.rb add this method:
def test
stack_client = StackExchange.new('stackoverflow', 1)
#users = stack_client.users
end
in app/views/application/test.html.erb put the following:
<% #users.each do |user| %><%=user.inspect%><br/><br/><% end %>
Note: I would otherwise recommend adding views to ApplicationController but because I don't know anything about your application, I'll default to it.
hit http://localhost:3000/test and you should see the expected result.
[0] Rails does a lot "magic" under the scenes — it's really not magic but metaprogramming — where it tries to assume a lot of things about your application structure and naming conventions. If your class was named Stackexchange (note the lowercase e), stackexchange.rb would be automatically "mapped" to the class Stackexchange. More info: http://guides.rubyonrails.org/autoloading_and_reloading_constants.html

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!

undefined method {....}_url when calling create in api controller

I'm working on a rails app that serves some json and I'm having hard time understanding what is going on in the code below (simplified for the purpose of understanding the issue).
module Api
class ProjectController < ApplicationController
respond_to :json
def show
x = {"id"=>17, "name"=>"abc", "version"=>1}
respond_with x.to_json, status: 200
end
def create
x = {"id"=>17, "name"=>"abc", "version"=>1}
respond_with x.to_json, status: 200
end
end
end
The show action works fine but when I call the create action I get
NoMethodError (undefined method '{"id":17,"name":"abc","version":1}_url' for
Api::ProjectsController:0x007fbb2294cd18)
Why do I get this error while show works just fine? is it because create makes a post instead of a get?
How can I solve it?
Thanks for your help and have a nice day.
The issue is that your controller is in a module (Api). That affects the namespace, and thus the routing; you must include that namespace as part of what you pass to respond_with.
Normally, I'd direct you to this stackoverflow answer (credit goes there). But since you are using a hash instead of a model, this answer might be more applicable.
Note how Rails is trying to call a ..._url method. The ... is your hash, in JSON format. You need to help Rails here on how to render.

Loading a page into memory in Rails

My rails app produces XML when I load /reports/generate_report.
On a separate page, I want to read this XML into a variable and save it to the database.
How can I do this? Can I somehow stream the response from the /reports/generate_report.xml URI into a variable? Or is there a better way to do it since the XML is produced by the same web app?
Here is my generate_report action:
class ReportsController < ApplicationController
def generate_report
respond_to do |format|
#products = Product.all
format.xml { render :layout => false }
end
end
end
Here is the action I am trying to write:
class AnotherController < ApplicationController
def archive_current
#output = # get XML output produced by /reports/generate_report
# save #output to the database
respond_to do |format|
format.html # inform the user of success or failure
end
end
end
Solved: My solution (thanks to Mladen Jablanović):
#output = render_to_string(:file => 'reports/generate_report.xml.builder')
I used the following code in a model class to accomplish the same task since render_to_string is (idiotically) a protected method of ActionController::Base:
av = ActionView::Base.new(Rails::Configuration.new.view_path)
#output = av.render(:file => "reports/generate_report.xml.builder")
Perhaps you could extract your XML rendering logic to a separate method within the same controller (probably a private one), which would render the XML to a string using render_to_string, and call it both from generate_report and archive_current actions.
What I typically do in this type of situation is to create a separate module/class/model to generate the report (it could even potentially be right in the Product model). This separate component could be in app/models or it could be in lib. In any case, once you have it extracted you can use it anywhere you need it. The controller can call it directly. You can generate it from the console. You can have a cron job generate it. This is not only more flexible, but it also can help smooth out your request response times if the report becomes slow to generate.
Since you are using a template it's understandable that the controller route is convenient, but even if you have to include some kind of ruby templating system in your auxiliary lib, it's still probably going to be less hassle and more flexible then trying to go through the controller.
#output = Product.all.to_xml
I'm sorry, is you question about Xml or about sessions? I mean is the fact that your action generates Xml material to the question? Or do you just want to save the output of the action for latter use?
You said on a "separate" page - you mean on another request? (like after user approved it?)
Why do you want to save the output? Because it should be saved exactly as rendered? (for example user can get frustrated if he clicked to save one report and you saved another)
Or is this thing expensive to generate?
Or may be, I got it wrong and it's about refactoring?

Resources