So I created this request which gives me a response as json.
require 'dotenv/load'
require 'faraday'
class OverviewController < ApplicationController
def api_key
ENV["API_KEY"]
end
def url
"https://example.com"+api_key
end
def index
conn = Faraday.new(url, request: {open_timeout: 1, timeout: 1}) do |c|
c.response :json, :content_type => /\bjson$/
c.adapter Faraday.default_adapter
end
response = conn.get url
#hash = response.body['data']
end
end
Response:
{
"type": "products",
"version": "x.x.x",
"data": {
"Product 1": {
"title": "xxx",
"attributes": {
"x"=1
},
"id": 22,
"name": "Product 1"
},
"Product 2": {
"title": "xXx",
"attributes": {
"x"=2
},
"id": 25,
"name": "Product 2"
},
...
This works great so far. But since the data in this json changes really rarely and there is a policy to not request the api as much I would like to cache my result.
I tried different solutions with "faraday-http-cache" but I can't get it to work. But I like to not use another lib.
I read the "rails - Guides - caching" segment and I think I need low level caching Rails.cache.fetch
I would be really glad if someone could help me :-)
EDIT(after Panic's comment):
I tried this, but I need some more help
require 'dotenv/load'
require 'faraday'
class StatsClient
def api_key
ENV["API_KEY"]
end
def url
"https://example.com"+api_key
end
def index
conn = Faraday.new(url, request: {open_timeout: 1, timeout: 1}) do |c|
c.response :json, :content_type => /\bjson$/
c.adapter Faraday.default_adapter
end
response = conn.get url
#hash = response.body['data']
end
end
class OverviewController < ApplicationController
def index
#hash = Rails.cache.fetch('something', expires_in: 15.minutes) do
StatsClient.products
end
end
end
Like this? What has to go actually as 'something'. Also I get the error that "StatsClient.products is not recognised.
Move the code from your controller into a separate class (or module) StatsClient. Then in your controller:
def index
#hash = Rails.cache.fetch('something', expires_in: 15.minutes) do
StatsClient.products
end
end
Related
I'm running a rails application that calls Simplecasts API to display my podcast episodes. I followed a tutorial to setup the API services using Faraday. My question is how to only display published episodes on my index page? Normally, I would add a .where(:status => "live") in my controller, IE #podcasts = Episodes.where(:status => "published") but this doesn't seem to work.
Simplecast's API for the podcast returns a collection that contains all the available episodes, each has a status node.
Any help would be appreciated as I'm new to working with external APIs in Rails
Sample API response
"collection": [
{
"updated_at": "2020-03-25T17:57:00.000000-04:00",
"type": "full",
"token": "lgjOmFwr",
"title": "Test",
"status": "draft",
Episode.rb
module Simplecast
class Episodes < Base
attr_accessor :count,
:slug,
:title,
:status
MAX_LIMIT = 10
def self.episodes(query = {})
response = Request.where('/podcasts/3fec0e0e-faaa-461f-850d-14d0b3787980/episodes', query.merge({ number: MAX_LIMIT }))
episodes = response.fetch('collection', []).map { |episode| Episode.new(episode) }
[ episodes, response[:errors] ]
end
def self.find(id)
response = Request.get("episodes/#{id}")
Episode.new(response)
end
def initialize(args = {})
super(args)
self.collection = parse_collection(args)
end
def parse_collection(args = {})
args.fetch("collection", []).map { |episode| Episode.new(episode) }
end
end
end
Controller
class PodcastsController < ApplicationController
layout "default"
def index
#podcasts, #errors = Simplecast::Episodes.episodes(query)
#podcast, #errors = Simplecast::Podcast.podcast(query)
render 'index'
end
# GET /posts/1
# GET /posts/1.json
def show
#podcast = Simplecast::Episodes.find(params[:id])
respond_to do |format|
format.html
format.js
end
end
private
def query
params.permit(:query, {}).to_h
end
end
Looks like collection is just an array of hashes so rails ActivrRelations methods aka .where are not supported. However It is an array so you can just filter this array:
published_episodes = collection.filter { |episode| episode[:status] == “ published” }
Also look through their API - may be the do support optional filtering params so you would get only published episodes in the first place.
BTW: second thought is to save external API request data in your own DB and then fetch require episodes with standard .where flow.
I have a json file as below names.json. When you append the URL /list?name=Canada or /list?name=CANADA be it Uppercase or Lowercase, I want to check if the param[:name] is inside names.json file and throw error if not there.
[
{
"id": 1,
"name": "Canada"
},
{
"id": 17,
"name": "Denmark"
},
{
"id": 23,
"name": "Austria"
}
]
Here is what I have done but did not work…..
controller/concerns
require 'json'
JSON_NAMES = 'names.json'.freeze
module NameFileLoader
class JsonLoader
def self.json_data_hash
file = File.read(JSON_NAMES)
JSON.parse(file)
end
end
end
name_controller.rb
def check_name_validity_in_file
data = NameFileLoader::JsonLoader.json_data_hash
name = data.each { |item| item['name'] } # The problem is here.
if name.include?(params[:name])
{ errorCode: 400, message: 'Name provided is not valid' }
end
end
You’d better cache the JSON once loaded from the file in the first place. Also you probably want to maintain a cached list of allowed countries in the lowercase to compare.
module NameFileLoader
class JsonLoader
class << self
def json_data_hash
#json ||= JSON.parse(File.read(JSON_NAMES))
end
def countries
#countries ||= json_data_hash.map { |h| h['name'].downcase }
end
end
end
end
Now upon receival a parameter you might check it as:
if NameFileLoader::JsonLoader.countries.include?(params[:name].downcase)
...
end
I thought I was getting closer to wrapping my head around Rails until this challenge. I have an initializer agilecrm.rb - content show below. I am using AgileCRM Ruby code to try and connect my app with AgileCRM system. When using the code below, with the test Create Contact array at the bottom, it successfully creates a contact in my AgileCRM account, so I know at least this part works. What I need to do is create a new AgileCRM user every time I create a new Devise user. I have a feeling that I am looking at this the wrong way and probably need a controller for this, but this is not completely foreign to me, but I still can't figure out when way to go. Thank you.
config/initializers/agilecrm.rb
require 'net/http'
require 'uri'
require 'json'
class AgileCRM
class << self
def api_key=(key)
##api_key = key
end
def domain=(d)
##domain = d
end
def email=(email)
##email = email
end
def api_key
##api_key
end
def domain
##domain
end
def email
##email
end
def request(method, subject, data = {})
path = "/dev/api/#{subject}"
case method
when :get
request = Net::HTTP::Get.new(path)
when :post
request = Net::HTTP::Post.new(path)
request.body = data.to_json
when :put
request = Net::HTTP::Put.new(path)
request.body = data.to_json
when :delete
request = Net::HTTP::Delete.new(path)
else
raise "Unknown method: #{method}"
end
uri = URI.parse("https://#{domain}.agilecrm.com")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request['Content-Type'] = 'application/json'
request['Accept'] = 'application/json'
request.basic_auth AgileCRM.email, AgileCRM.api_key
response = http.request(request)
response.body
end
end
end
AgileCRM.api_key = '*******'
AgileCRM.domain = '*******'
AgileCRM.email = '*******'
# ======================Create Contact====================================
contact_data = '{
"star_value": "4",
"lead_score": "92",
"tags": [
"Lead",
"Likely Buyer"
],
"properties": [
{
"type": "SYSTEM",
"name": "first_name",
"value": "John"
}
]
}'
parsed_contact_data = JSON.parse(contact_data)
print(AgileCRM.request :post, 'contacts', parsed_contact_data)
You might want to move this logic into your User model, and have a after_save hook to push data to agilecrm. Assuming that the Devise user model is called User :
class User < ApplicationRecord
...
after_save :sync_to_agilecrm
def sync_to_agilecrm
# your agilecrm api calls go here
...
end
end
The above should do what you are trying to achieve.
I am looking for a way to add an identifier to my JSON output so it can be more easily parsed. Currently, the output is :
[
{
"id":9,
"name":"Test Location",
"description":"Test Description",
"address":"123 Fake Street",
"latitude":-85.0,
"longitude":-101.10101,
"created_at":"2015-11-15T21:25:08.643Z",
"updated_at":"2015-11-15T21:27:23.419Z"
},
{
"id":10,
"name":"Test Location",
"description":"testest",
"address":"estesets",
"latitude":1.0,
"longitude":1.0,
"created_at":"2015-11-15T22:05:39.224Z",
"updated_at":"2015-11-15T22:05:39.224Z"
}
]
The ideal output would be:
{ locations:
[
{
"id":9,
"name":"Test Location",
"description":"Test Description",
"address":"123 Fake Street",
"latitude":-85.0,
"longitude":-101.10101,
"created_at":"2015-11-15T21:25:08.643Z",
"updated_at":"2015-11-15T21:27:23.419Z"
},
{
"id":10,
"name":"Test Location",
"description":"testest",
"address":"estesets",
"latitude":1.0,
"longitude":1.0,
"created_at":"2015-11-15T22:05:39.224Z",
"updated_at":"2015-11-15T22:05:39.224Z"
}
]
}
My current controller is:
module Api
module V1
class LocationsController < ApplicationController
unless Rails.env.test?
before_filter :restrict_access
end
respond_to :json
def index
#locations = Location.all
respond_with #locations
end
private
def restrict_access
api_key = ApiKey.find_by_access_token(params[:access_token])
head :unauthorized unless api_key
end
end
end
end
I would like for it to have a name of Locations so I can more easily parse it. Thanks for the help!
def index
#locations = Location.all
respond_with locations: #locations
end
Results in proper output
Just work with your #locations.
You can do something like:
#new_locations = {}
#new_locations = {'locations' => #locations}
This is my ruby code / JSON File. Three functions required, I have implemented the first two but am having trouble with the third one. I have only recently started learning ruby - any simplified explanations/answers are much appreciated
class Company
attr_accessor :jobs
jobs = Array.new
## TODO: Implement this method to load the given JSON file into Ruby built-in data
## structures (hashes and arrays).
def self.load_json(filepath)
require 'json'
file = File.read(filepath)
data_hash = JSON.parse(file)
end
## TODO: This method should update the `jobs` property to an array of instances of
## class `Job`
def initialize(filepath)
# Load the json file and loop over the jobs to create an array of instance of `Job`
# Assign the `jobs` instance variable.
load_json(filepath)
data_hash.each { |jobs|
array_of_jobs.insert(jobs['name'])
}
end
## TODO: Impelement this method to return applicants from all jobs with a
## tag matching this keyword
def find_applicants(keyword)
# Use the `jobs` instance variable.
end
end
Below is the JSON file code I am supposed to retrieve the information from.
{
"jobs": [
{
"id": 1,
"title": "Software Developer",
"applicants": [
{
"id": 1,
"name": "Rich Hickey",
"tags": ["clojure", "java", "immutability", "datomic", "transducers"]
},
{
"id": 2,
"name": "Guido van Rossum",
"tags": ["python", "google", "bdfl", "drop-box"]
}
]
},
{
"id": 2,
"title": "Software Architect",
"applicants": [
{
"id": 42,
"name": "Rob Pike",
"tags": ["plan-9", "TUPE", "go", "google", "sawzall"]
},
{
"id": 2,
"name": "Guido van Rossum",
"tags": ["python", "google", "bdfl", "drop-box"]
},
{
"id": 1337,
"name": "Jeffrey Dean",
"tags": ["spanner", "BigTable", "MapReduce", "deep learning", "massive clusters"]
}
]
}
]
}
Code provided by you will not compile and approach used is not very convenient.
Steps you may follow to implement it:
First implement your models. May look like:
class Applicant
attr_accessor :id, :name, :tags
def initialize(id, name=nil, tags=nil)
#id = id
#name = name
#tags = tags
end
end
class Job
attr_accessor :id, :title, :applicants
def initialize(id, title=nil, applicants=nil)
#id = id
#title = title
#applicants = applicants
end
end
Then define your Company class that works with jobs
class Company
attr_accessor :jobs
def initialize(jobs)
#jobs = jobs
end
def find_applicants(keyword)
# Now you can iterate through jobs,
# job's applicants and finally applicant's tags
# like this
applicants = []
#jobs.each do |job|
job.applicants.each do |applicant|
applicant.tags.each do |tag|
if keyword.eql? tag
# ...
end
end
end
end
applicants
end
end
And then you can load data from Json file and construct proper objects:
require 'json'
class DataLoader
def load(filepath)
hash = JSON.parse(filepath)
construct(hash)
end
private
def validate(hash)
# validate your data here
end
def construct(hash)
validate(hash)
jobs = []
hash['jobs'].each do |job|
applicants = []
job['applicants'].each do |applicant|
applicants << Applicant.new(applicant['id'], applicant['name'], applicant['tags'])
end
jobs << Job.new(job['id'], job['title'], applicants)
end
jobs
end
end
And all together will look like:
tag = 'google'
data = DataLoader.new.load(File.read('data.json'))
company = Company.new(data)
applicants = company.find_applicants(tag)
puts "Applicants that have '#{tag}' in taglist"
applicants.each do |applicant|
puts " #{applicant.id}: #{applicant.name}"
end
#Applicants that have google in taglist
# 2: Guido van Rossum
# 42: Rob Pike
Here is a simple implementation of find_applicants. JSON objects can be iterated through like any other data structure.
Ideone example here.
def find_applicants(myJson, keyword)
names = []
myJson["jobs"].each do |job|
job["applicants"].each do |applicant|
tags = applicant["tags"]
if tags.include? keyword then
names << applicant["name"]
end
end
end
names
end