how to refactor IEX exchange data calls for grabbing data from api in ruby program - ruby

I am using the iex exchange api for grabbing info about stocks. It's working great but my code is very ugly was wondering how to refactor the code.
stock = IEX::Api::Client.new(
publishable_token: token,
endpoint: 'https://sandbox.iexapis.com/v1'
)
This is what is needed to grab the info. The problem is that I have to put the code above in every method that utilizes the api. For instance,
def self.stock_price(ticker)
stock = IEX::Api::Client.new(
publishable_token: token,
endpoint: 'https://sandbox.iexapis.com/v1'
)
company = stock.company(ticker)
quote = stock.quote(ticker.upcase)
puts "#{company.company_name}: #{quote.latest_price}"
end
def self.week_52_high(ticker)
stock = IEX::Api::Client.new(
publishable_token: token,
endpoint: 'https://sandbox.iexapis.com/v1'
)
key_stats = stock.key_stats(ticker)
puts "52-week high: #{key_stats.week_52_high}"
end
def self.week_52_low(ticker)
stock = IEX::Api::Client.new(
publishable_token: token,
endpoint: 'https://sandbox.iexapis.com/v1'
)
key_stats = stock.key_stats(ticker)
puts "52-week low: #{key_stats.week_52_low}"
end
Is there any way to factor that call out to a different file and call the method that way? The code is very repetitive as is. The "stock" variable is what i need to actually work with, should I made that a global variable? I've heard that's a no-no but is this case an exception? Also, where i have
publishable_token: token,
that token variable is actually my actual, hard-coded token, not the "token" variable you see, I simply changed it for security issues. What should I do instead of hard-coding it? The documentation says to turn it into an environment variable but i dont know what that means. Thanks in advance!

What should I do instead of hard-coding it? The documentation says to
turn it into an environment variable but i dont know what that means.
An environment variable is a variable whose value is set outside the the application, typically through functionality built into the operating system or shell. You need to check the documentation for your setup to see how to set env vars.
You can get env vars in Ruby through the ENV hash.
ENV['FOO']
ENV.fetch('FOO') # will raise a KeyError if it is not set instead of just returning nil
Rails 5.2 and up have secure credentials that can be used instead. It stores your credentials in an encrypted YAML file that can be checked into source control.
How do I refactor this?
One way to refactor this would be to use delegation instead of bunch of largely static methods:
require 'forwardable'
class MyClient
extend Forwardable
TOKEN = ENV.fetch('IEX_API_TOKEN')
ENDPOINT = ENV.fetch('IEX_API_ENDPOINT', 'https://sandbox.iexapis.com/v1')
def_delegators :#client, :company, :quote, :key_stats
def initialize(publishable_token: TOKEN, endpoint: ENDPOINT, client: nil)
# This is know as constructor injection and makes it easy to mock out
# the dependency in tests
#client = client || IEX::Api::Client.new(publishable_token: TOKEN, endpoint: ENDPOINT)
end
def stock_price(ticker)
company_name = company(ticker).company_name
price = quote(ticker.upcase).latest_price
puts "#{company_name}: #{price}"
end
def week_52_high(ticker)
puts "52-week high: #{key_stats(ticker).week_52_high}"
end
def week_52_low(ticker)
puts "52-week low: #{key_stats(ticker).week_52_low}"
end
end
#client = MyClient.new
#client.week_52_low(ticker)

Related

Sinatra Multiple Parallel Requests Variable Behaviour

I am fairly new to ruby and would like to understand how class instance variables behave in case of multiple parallel requests.
I have a method inside my controller class which is called everytime for each request for a specific operation (create in this case)
class DeployProvision
def self.create(data)
raise "Input JSON not received." unless data
# $logger.info input_data.inspect
failure = false
response_result = ""
response_status = "200"
#validator = SchemaValidate.new
validation = #validator.validate_create_workflow(data.to_json)
end
end
This method is called as (DeployProvision.create(data))
I am a little confused on how #validator class instance variable behaves when multiple requests come. Is it shared among multiple requests. Is it a good idea to declare this as class instance variable instead of a local variable ?
I am working on an existing code base and would like to understand the intent of creating #validator as a class instance variable instead of local variable.
You can write ultra-simple script like this:
require 'sinatra'
class Foo
def self.bar
#test = Time.now
puts #test
end
end
get '/' do
Foo.bar
end
and you'll see it does nothing, because with every call, you're creating new instance of Time(SchemaValidate in your code).
If you used memoization and had something like #validator ||= SchemaValidate.new you would have one instance of SchemaValidate stored between requests.
I don't think that'd change anything in terms of performance and I don't have idea why would anyone do something like that.
You can have some fun with ultra-simple scripts with sinatra to test how it behaves.
Good luck with this code!

How do I make a class conditionally return one of two other classes?

I have a design problem.
I'm writing a REST client in ruby. For reasons beyond my control, it has to extend another gem that uses my networks zookeeper instance to do service lookup. My client takes a user provided tier, and based on that value, queries the zookeeper registry for the appropriate service url.
The problem is that I also need to be able to run my client against a locally running version of the service under test. When the service is running locally, zookeeper is obviously not involved, so I simply need to be able to make GET requests against the localhost resource url.
When a user instantiates my gem, they call something like:
client = MyRestClient.new(tier: :dev)
or in local mode
client = MyRestClient.new(tier: :local)
I would like to avoid conditionally hacking the constructor in MyRestClient (and all of the GET methods in MyRestClient) to alter requests based on :local vs. :requests_via_the_zk_gem.
I'm looking for an elegant and clean way to handle this situation in Ruby.
One thought was to create two client classes, one for :local and the other for :not_local. But then I don't know how to provide a single gem interface that will return the correct client object.
If MyClient has a constructor that looks something like this:
class MyClient
attr_reader :the_klass
def initialize(opts={})
if opts[:tier] == :local
#the_klass = LocalClass.new
else
#the_klass = ZkClass.new
end
#the_klass
end
end
then I end up with something like:
test = MyClient.new(tier: :local)
=> #<MyClient:0x007fe4d881ed58 #the_klass=#<LocalClass:0x007fe4d883afd0>>
test.class
=> MyClient
test.the_klass.class
=> LocalClass
those who then use my gem would have to make calls like:
#client = MyClient.new(tier: :local)
#client.the_klass.get
which doesn't seem right
I could use a module to return the appropriate class, but then I'm faced with the question of how to provide a single public interface for my gem. I can't instantiate a module with .new.
My sense is that this is a common OO problem and I just haven't run into it yet. It's also possible the answer is staring me in the face and I just haven't found it yet.
Most grateful for any help.
A common pattern is to pass the service into the client, something like:
class MyClient
attr_reader :service
def initialize(service)
#service = service
end
def some_method
service.some_method
end
end
And create it with:
client = MyRestClient.new(LocalClass.new)
# or
client = MyRestClient.new(ZkClass.new)
You could move these two into class methods:
class MyClient
self.local
new(LocalClass.new)
end
self.dev
new(ZkClass.new)
end
end
And instead call:
client = MyRestClient.local
# or
client = MyRestClient.dev
You can use method_missing to delegate from your client to the actual class.
def method_missing(m, *args, &block)
#the_class.send(m, *args, &block)
end
So whenever a method gets called on your class that doesn't exist (like get in your example) it wil be called on #the_class instead.
It's good style to also define the corresponding respond_to_missing? btw:
def respond_to_missing?(m, include_private = false)
#the_class.respond_to?(m)
end
The use case you are describing looks like a classic factory method use case.
The common solution for this is the create a method (not new) which returns the relevant class instance:
class MyClient
def self.create_client(opts={})
if opts[:tier] == :local
LocalClass.new
else
ZkClass.new
end
end
end
And now your usage is:
test = MyClient.create(tier: :local)
=> #<LocalClass:0x007fe4d881ed58>
test.class
=> LocalClass

Ruby 1.9.2: How to change scope/binding of a block

Hi I have something like the folowing:
class TrialRequest
attr_accessor :trial_email
def initialize(email)
#trial_email = email
puts "Trial_email: #{trial_email}"
end
def create
#email = ::Gmail.connect!(gmail_name, gmail_password) do |gmail|
email = gmail.compose do
to 'trial#domain.com'
from trial_email
subject trial_email
text_part do
content_type 'text/html; charset=UTF-8'
body 'Sign me up.'
end
end
#binding.pry
gmail.deliver!(email)
end
end
end
The problem is that inside the compose block trial_email is not defined:
NameError: undefined local variable or method `trial_email' for #<Mail::Message:0x0000000431b830>
Is this a Ruby 1.9 issue or a gmail gem issue?
How should I go about making this method 'visible'/within the scope of the compose block?
Update:
This is an issue/feature of the gmail gem - ruby 1.9 blocks have changed but not this much!
In addition to the accepted answer, another workaround is to pass the data in as a method parameter:
def create(trial_email)
...
end
Looks like a GMail issue to me. Inside the blocks, self will be some object from the GMail gem so that you can have to, from, and similar DSL niceties available. You should be able to put self.trial_email into a local variable and then access that inside the blocks:
email_address = self.trial_email
#email = ::Gmail.connect!(gmail_name, gmail_password) do |gmail|
email = gmail.compose do
to 'trial#domain.com'
from email_address
subject email_address
#...
You're expecting (as you're entitled to) that the block should preserve the value of self, as it usually does. It looks like the gmail gem is using instance_exec here which allows it to change the value of self for the block to an instance of Mail::Message (which is why you can call to and from in that block even though you define no such methods)
While instance_exec is handy for producing nice DSLs, it is not without its downsides. Local variable scope isn't affected so you could store either trial_email or self in a local variable prior to the block and then use that local variable inside the block
The problem is that the block you pass to compose method is later passed to Mail.new and finally to Message.new (if I traced the chain correctly) and then this block is evaluated like that here:
instance_eval(&block)
As it's performed inside initialize method of a different object (instance of Message class) you do not have access to attributes of your TrialRequest object.
You can do the same thing without having any troubles like that:
email = gmail.compose
email.to = 'trial#domain.com'
email.from = trial_email
email.subject = trial_email
email.text_part do
content_type 'text/html; charset=UTF-8'
body 'Sign me up.'
end

Calling Sinatra from within Sinatra

I have a Sinatra based REST service app and I would like to call one of the resources from within one of the routes, effectively composing one resource from another. E.g.
get '/someresource' do
otherresource = get '/otherresource'
# do something with otherresource, return a new resource
end
get '/otherresource' do
# etc.
end
A redirect will not work since I need to do some processing on the second resource and create a new one from it. Obviously I could a) use RestClient or some other client framework or b) structure my code so all of the logic for otherresource is in a method and just call that, however, it feels like it would be much cleaner if I could just re-use my resources from within Sinatra using their DSL.
Another option (I know this isn't answering your actual question) is to put your common code (even the template render) within a helper method, for example:
helpers do
def common_code( layout = true )
#title = 'common'
erb :common, :layout => layout
end
end
get '/foo' do
#subtitle = 'foo'
common_code
end
get '/bar' do
#subtitle = 'bar'
common_code
end
get '/baz' do
#subtitle = 'baz'
#common_snippet = common_code( false )
erb :large_page_with_common_snippet_injected
end
Sinatra's documentation covers this - essentially you use the underlying rack interface's call method:
http://www.sinatrarb.com/intro.html#Triggering%20Another%20Route
Triggering Another Route
Sometimes pass is not what you want, instead
you would like to get the result of calling another route. Simply use
call to achieve this:
get '/foo' do
status, headers, body = call env.merge("PATH_INFO" => '/bar')
[status, headers, body.map(&:upcase)]
end
get '/bar' do
"bar"
end
I was able to hack something up by making a quick and dirty rack request and calling the Sinatra (a rack app) application directly. It's not pretty, but it works. Note that it would probably be better to extract the code that generates this resource into a helper method instead of doing something like this. But it is possible, and there might be better, cleaner ways of doing it than this.
#!/usr/bin/env ruby
require 'rubygems'
require 'stringio'
require 'sinatra'
get '/someresource' do
resource = self.call(
'REQUEST_METHOD' => 'GET',
'PATH_INFO' => '/otherresource',
'rack.input' => StringIO.new
)[2].join('')
resource.upcase
end
get '/otherresource' do
"test"
end
If you want to know more about what's going on behind the scenes, I've written a few articles on the basics of Rack you can read. There is What is Rack? and Using Rack.
This may or may not apply in your case, but when I’ve needed to create routes like this, I usually try something along these lines:
%w(main other).each do |uri|
get "/#{uri}" do
#res = "hello"
#res.upcase! if uri == "other"
#res
end
end
Building on AboutRuby's answer, I needed to support fetching static files in lib/public as well as query paramters and cookies (for maintaining authenticated sessions.) I also chose to raise exceptions on non-200 responses (and handle them in the calling functions).
If you trace Sinatra's self.call method in sinatra/base.rb, it takes an env parameter and builds a Rack::Request with it, so you can dig in there to see what parameters are supported.
I don't recall all the conditions of the return statements (I think there were some Ruby 2 changes), so feel free to tune to your requirements.
Here's the function I'm using:
def get_route url
fn = File.join(File.dirname(__FILE__), 'public'+url)
return File.read(fn) if (File.exist?fn)
base_url, query = url.split('?')
begin
result = self.call('REQUEST_METHOD' => 'GET',
'PATH_INFO' => base_url,
'QUERY_STRING' => query,
'rack.input' => StringIO.new,
'HTTP_COOKIE' => #env['HTTP_COOKIE'] # Pass auth credentials
)
rescue Exception=>e
puts "Exception when fetching self route: #{url}"
raise e
end
raise "Error when fetching self route: #{url}" unless result[0]==200 # status
return File.read(result[2].path) if result[2].is_a? Rack::File
return result[2].join('') rescue result[2].to_json
end

ruby and HTTParty

I'm requesting an internal API with lots of different interface, each is REST :
/users/
/users/username/contacts/
/users/__username_/properties/
.....
All this interfaces have a common authentication via a call to
/login
and return an auth_key that need to be used in every others calls
So I made a class for each interface, something like :
class MyApi::Users
include HTTParty
def initialize(username, password)
init = MyApi::Auth.new(username, password)
#auth_token = init.auth["id"]
end
def create(options)
end
def show(username)
end
def update(username, options)
end
def destroy(username)
end
end
So after coding all the class, my problem is if I need to call 2 different interfaces one after the other like :
def test
user = MyApi::Users.new("user", "password")
user = user.show("toto")
contacts = MyApi::Contacts.new("user", "password")
contacts = contacts.list(user.id)
end
As you see I need to authenticate twice and my instances have each a different token, do you have an idea how I could improve that without login twice.
Thanks,
Hi mike why are you authenticating on every call? Without knowing your app it would appear that you should be using sessions,and only authenticating the very first time its needed
There should be no need to pass round the username and password in the you controllers outside the login/auth controller
Something like auth logic will have a before_action making sure you user is authenticated and giving you access to current_user

Resources