Refactorting if-else blocks into more concise code - ruby-on-rails

I have a bunch of if-elsif blocks like this that end in a else statement so my structure looks like this:
if path.end_with?('something')
template_name = 'something.json.erb'
res.body = ERB.new(File.read(File.expand_path("../#{template_name}", __FILE__))).result(binding)
res.status = 200
res['Content-Type'] = 'application/json'
elsif path.end_with?('somethingELSE')
template_name = 'somethingELSE.json.erb'
res.body = ERB.new(File.read(File.expand_path("../#{template_name}", __FILE__))).result(binding)
res.status = 200
res['Content-Type'] = 'application/json'
# a couple more similar if-elsif blocks in here
else
res.status = 400
res['Content-Type'] = 'text/plain'
res.body = "Invalid path"
So there is a lot of repeated code in the section that has blocks of if-elsif that are just repeared. Basically just the line that sets the template_name is necessary and we should be able to factore out the rest of those next three lines but then I have that else at the end that prevents me from doing that.
How would you suggest to refactor this code to be more concise and less repeated code in it?

['something', 'somethingELSE', 'somethingAGAIN'].each DO |match|
substitute = match if path.end.with?(match)
end
if substitute
template_name = "#{substitute}.json.erb"
res.body = ERB.new(File.read(File.expand_path("../#{template_name}", __FILE__))).result(binding)
res.status = 200
res['Content-Type'] = 'application/json'
else
res.status = 400
res['Content-Type'] = 'text/plain'
res.body = "Invalid path"
end

Below is one way to do it.
if path.end_with?('something') || path.end_with?('somethingELSE')
if path.end_with?('something')
template_name = 'something.json.erb'
elsif path.end_with?('somethingELSE')
template_name = 'somethingELSE.json.erb'
# a couple more similar if-elsif blocks in here
end
res.body = ERB.new(File.read(File.expand_path("../#{template_name}", __FILE__))).result(binding)
res.status = 200
res['Content-Type'] = 'application/json'
else
res.status = 400
res['Content-Type'] = 'text/plain'
res.body = "Invalid path"
end
You could probably also parse the something and somethingELSE off of your path to get the template name, simplifying this even further.
Assuming you've got a proper path, a/path/to/something you could do:
template_name = "/assuming/this/is/a/path".split('/').last + '.json.erb'
But I can't say without seeing your other conditionals.

Related

Trouble with low level caching in rails

I have some issues with caching in rails. I don't find how should i setting it.
Here's the code :
submit_key = nil
pairs_email = Hash.new
pairs_type = Rails.cache.fetch("cache_typeform", :expires_in => 1.day) do
(0..9).each do
if submit_key.present?
url = "https://api.typeform.com/forms/#{typeform_id}/responses?page_size=1000&until=#{submit_key}"
response = RestClient.get url, {:Authorization => 'Bearer XXXXXXXXXXX'}
parsed = JSON.parse(response.body)
else
response = RestClient.get "https://api.typeform.com/forms/#{typeform_id}/responses?page_size=1000", {:Authorization => 'Bearer XXXXXXXXXXXXXXX}
parsed = JSON.parse(response.body)
end
parsed['items'].each do |item|
pairs_email[item['hidden']['email']] = item['token'] if item['hidden']['email'].present?
end
submit_key = parsed['items'][-1]['submitted_at'].chop
end
end
Then it should return a pairs containing an email and an ID and this pairs is used after to get more informations. However, nothing is returning.
Does someone can tell me what I've done wrong in my code? Am I missing something somewhere?
UPDATE
I want to use my cache for getting informations from the typeform API :
results = Hash.new
if pairs_email[email].present?
url = "https://api.typeform.com/v1/form/#{typeform_id}?key=#{ENV['TYPEFORM_API_KEY']}&token=#{pairs_email[email]}"
response = RestClient.get(url)
parsed = JSON.parse(response.body)
results["email"] = parsed["responses"][0]["hidden"]["email"] # Email
results["first_name"] = parsed["responses"][0]["answers"]["textfield_25078009"] # prénom
results["last_name"] = parsed["responses"][0]["answers"]["textfield_25078014"] # nom
results["phone_number"] = parsed["responses"][0]["answers"]["textfield_25444504"] #N°
results["job"] = parsed["responses"][0]["answers"]["textfield_24904749"] # métier
results["status_legal"] = parsed["responses"][0]["answers"]["list_24904751_choice"] # statut légal ?
results["birthdate"] = parsed["responses"][0]["answers"]["date_24904754"] # Date de naissance
results["zipcode"] = parsed["responses"][0]["answers"]["number_24904755"] # Code postal
results["has_partner"] = parsed["responses"][0]["answers"]["yesno_53894471"] # has_partner
results["children"] = parsed["responses"][0]["answers"]["list_53894494_choice"] # Nombre d'enfants
results["optical_option"] = parsed["responses"][0]["answers"]["list_24904752_choice_32209601"] # optical_option
results["dental_option"] = parsed["responses"][0]["answers"]["list_24904752_choice_32209602"] # dental_option
results["sick_15d"] = parsed["responses"][0]["answers"]["list_24904752_choice_32209603"] # Sick_15d
results["target_year"] = parsed["responses"][0]["answers"]["list_24905736_choice"] # target_year
results["monthly_income"] = parsed["responses"][0]["answers"]["number_24904756"] # monthly_income
results["independent"] = parsed["responses"][0]["answers"]["yesno_53895024"] # independent_1_year
#results["subject_to_discuss"] = parsed["responses"][0]["answers"]["textarea_24904759"] # Avez-vous des sujets dont vous voulez discuter
end
Here's something you must try before getting caching right. Attaching a screenshot from my machine.
Also if you are in development environment you would need to enable caching to see the effect. You could add config.action_controller.perform_caching = true and config.cache_store = :memory_store, { size: 64.megabytes } to your development.rb config file to enable caching.
This is just an idea of how caching happens and check if it really works, this should help you get going with your task.
Rails.cache.fetch stores the value evaluated from the block passed into this method (if there is one, of course). In your example, you're returning (0..9) range from the block, instead of actually evaluated [email id] pairs.

Pretty print webservice response on ruby

I have to call a webservice but I don't know the format of the response. In any case (xml, json or html) I have to pretty print the response.
For example, if it is a xml I have to indent and show it properly. Same thing if it is a json. I have two problems here:
Detecting the format
Apply a format depending on the type.
I think that (1) is the most challenging problem.
Any help?
As several of the comments have suggested, the http header will contain the content type.
net/http has methods for this: http://ruby-doc.org/stdlib-2.0.0/libdoc/net/http/rdoc/Net/HTTP.html#method-i-head
require 'net/http'
require 'json'
require 'rexml/document'
response = nil
Net::HTTP.start('www.google.com', 80) {|http|
response = http.get('/index.html')
}
header = response['content-type'].split(';').first # => "text/html"
body = response.read_body
then you can conditionally operate:
if header == "text/html"
puts response.read_body
elsif header == "application/json"
puts JSON.pretty_generate(JSON.parse(body))
elsif header == "text/xml"
xml = REXML::Document.new body
out = ""
xml.write(out, 1)
puts out
end
Most of this was pulled form other SO posts:
pretty JSON: How can I "pretty" format my JSON output in Ruby on Rails?
pretty XML: How to beautify xml code in rails application
This is the code that I finally used:
raw_response = response.body
response_html = ''
if response.header['Content-Type'].include? 'application/json'
tokens = CodeRay.scan(raw_response, :json)
response_html = tokens.div
elsif response.header['Content-Type'].include? 'application/xml'
tokens = CodeRay.scan(raw_response, :xml)
response_html = tokens.div
elsif response.header['Content-Type'].include? 'text/html'
tokens = CodeRay.scan(raw_response, :html)
response_html = tokens.div
else
response_html = '<div>' + raw_response + '</div>'
end
It's using the coderay gem.

Stubbing result of Net::HTTP.new(url.host, url.port)

I have a funciton that calls Net::HTTP.new(google_url.host, google_url.port) and I am trying to figure out how to stub the result for testing. Basically I don't want to be hitting the google URL shortener every time I run my test.
def shorten_url(long_url)
google_url = URI.parse("https://www.googleapis.com/urlshortener/v1/url")
data = JSON.generate({"longUrl" => "#{long_url}"})
header = {"Content-Type" => "application/json"}
http = Net::HTTP.new(google_url.host, google_url.port)
http.use_ssl = true
short_url = ""
res = http.request_post(google_url.path, data, header)
jsonResponse = JSON.parse(res.body)
short_url = jsonResponse["id"]
end
Basically I want to be able to set the result of that function.
I've tried things like: Net::HTTP.any_instance.stubs(:HTTP.new).returns("www.test.com") but cannot figure out how to get it to work.
class HTTP < Protocol
....
# Creates a new Net::HTTP object.
# If +proxy_addr+ is given, creates an Net::HTTP object with proxy support.
# This method does not open the TCP connection.
def HTTP.new(address, port = nil, p_addr = nil, p_port = nil, p_user = nil, p_pass = nil)
h = Proxy(p_addr, p_port, p_user, p_pass).newobj(address, port)
h.instance_eval {
#newimpl = ::Net::HTTP.version_1_2?
}
h
end
....
end
Check out webmock - this sounds like it will do exactly what you're looking for.

How to get original curl request made by patron gem in rails

I am using patron gem for creating curl request. Here is the code
s = Patron::Session.new
s.connect_timeout = 15
s.timeout = 15
s.base_url = API_URL
s.headers['Date'] = DATE_HEADER
s.headers['Accept'] = 'application/xml'
s.headers['Content-Type'] = 'application/json'
s.headers['Authorization'] = "API" + " " + auth_token
response = s.delete(uri)
response.status
How can I get the original curl request made by this gem?
You can't. Err... you can, by monkey_patching the Patron::Session.request method, and yielding the request just before the handling. But beware that this is not the "libcurl" request, as this only exists in C code, it's a Patron::Request instance.
Also, beware that your monkey-patching may break at any time, as you have to rewrite the whole method!
You add a yield just before handling the request to libcurl, so you have the opportunity to get it with a block.
req = nil
response = s.delete(uri) do |r|
req = r
end
# now req should be your request instance.
Here is a hint for a patch:
class Patron::Session
# You have to patch all the standard methods to accept a block
def get(url, headers = {}, &block)
request(:get, url, headers, &block)
end
# do the same for get_file, head, delete, put, put_file, post, post_file and post_multipart
def request(action, url, headers, options = {}, &block)
# If the Expect header isn't set uploads are really slow
headers['Expect'] ||= ''
req = Request.new
req.action = action
req.headers = self.headers.merge headers
req.timeout = options.fetch :timeout, self.timeout
req.connect_timeout = options.fetch :connect_timeout, self.connect_timeout
req.max_redirects = options.fetch :max_redirects, self.max_redirects
req.username = options.fetch :username, self.username
req.password = options.fetch :password, self.password
req.proxy = options.fetch :proxy, self.proxy
req.proxy_type = options.fetch :proxy_type, self.proxy_type
req.auth_type = options.fetch :auth_type, self.auth_type
req.insecure = options.fetch :insecure, self.insecure
req.ignore_content_length = options.fetch :ignore_content_length, self.ignore_content_length
req.buffer_size = options.fetch :buffer_size, self.buffer_size
req.multipart = options[:multipart]
req.upload_data = options[:data]
req.file_name = options[:file]
base_url = self.base_url.to_s
url = url.to_s
raise ArgumentError, "Empty URL" if base_url.empty? && url.empty?
uri = URI.join(base_url, url)
query = uri.query.to_s.split('&')
query += options[:query].is_a?(Hash) ? Util.build_query_pairs_from_hash(options[:query]) : options[:query].to_s.split('&')
uri.query = query.join('&')
uri.query = nil if uri.query.empty?
url = uri.to_s
req.url = url
yield req if block_given? # added line
handle_request(req)
end
end

Ruby, forming API request without implicitly stating each parameter

I'm trying to make a request to a web service (fwix), and in my rails app I've created the following initializer, which works... sorta, I have two problems however:
For some reason the values of the parameters need to have +'s as the spaces, is this a standard thing that I can accomplish with ruby? Additionally is this a standard way to form a url? I thought that spaces were %20.
In my code how can I take any of the options sent in and just use them instead of having to state each one like query_items << "api_key=#{options[:api_key]}" if options[:api_key]
The following is my code, the trouble area I'm having are the lines starting with query_items for each parameter in the last method, any ideas would be awesome!
require 'httparty'
module Fwix
class API
include HTTParty
class JSONParser < HTTParty::Parser
def json
JSON.parse(body)
end
end
parser JSONParser
base_uri "http://geoapi.fwix.com"
def self.query(options = {})
begin
query_url = query_url(options)
puts "querying: #{base_uri}#{query_url}"
response = get( query_url )
rescue
raise "Connection to Fwix API failed" if response.nil?
end
end
def self.query_url(input_options = {})
#defaults ||= {
:api_key => "my_api_key",
}
options = #defaults.merge(input_options)
query_url = "/content.json?"
query_items = []
query_items << "api_key=#{options[:api_key]}" if options[:api_key]
query_items << "province=#{options[:province]}" if options[:province]
query_items << "city=#{options[:city]}" if options[:city]
query_items << "address=#{options[:address]}" if options[:address]
query_url += query_items.join('&')
query_url
end
end
end
For 1)
You API provider is expecting '+' because the API is expecting in a CGI formatted string instead of URL formatted string.
require 'cgi'
my_query = "hel lo"
CGI.escape(my_query)
this should give you
"hel+lo"
as you expect
for Question 2) I would do something like
query_items = options.keys.collect { |key| "#{key.to_s}=#{options[key]}" }
def self.query_url(input_options = {})
options = {
:api_key => "my_api_key",
}.merge(input_options)
query_url = "/content.json?"
query_items = []
options.each { |k, v| query_items << "#{k}=#{v.gsub(/\s/, '+')}" }
query_url += query_items.join('&')
end
I'm a developer at Fwix and wanted to help you with your url escaping issue. However, escaping with %20 works for me:
wget 'http://geoapi.fwix.com/content.xml?api_key=mark&province=ca&city=san%20francisco&query=gavin%20newsom'
I was hoping you could provide me with the specific request you're making that you're unable to escape with %20.

Resources