ruby and HTTParty - ruby

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

Related

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

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)

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

Blocking Users From Specific Pages using Ruby on Rails and cancan

I am learning Ruby on Rails and was looking into utilizing cancan to help restrict users access to actions that they shouldn't have and to pages depending on who they are. I currently understand how to restrict actions, but I was curious if someone could help with actually restricting certain pages and unique pages.
One example is I have a home page for admin users and one for regular users, how would I restrict the admin page from the normal user?
Thanks, and any pointers on if I am doing something wrong is greatly appreciated.
If you want to use cancan :
Admit you add in your user controller a method admin_home :
def admin_home
#user = current_user
authorize! :admin_home
end
You need to specify in ability.rb file you want to restrict access to admin_home for standard users :
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.admin?
#Authorize all actions
can :manage, User
else
#authorize only self modifications and restrict access to admin_home
can :manage, User, :id => user.id
cannot :admin_home, User
end
end
end
You can find great resources about cancan in official wiki like
https://github.com/ryanb/cancan/wiki/Defining-Abilities and
https://github.com/ryanb/cancan/wiki/Authorizing-controller-actions
Hope this help
Note: I am just giving you an example, you are not supposed to use it as it is, but you can have an Idea that how you will be able to put your logic.
class AdminsController < ApplicationController
before_filter :check_admin, :only => [:index, :show]
def index
#admins = //whatever your query for this action
end
def show
#admin = //whatever your query for this action
end
protected
def check_admin
if(my_condition to check if user type is admin)
{
return true // or anything u want for ur admin user
}
else
{
//anything here when user is not admin
1. you can redirect to users home page using redirect_to
2. you can redirect to a specific page which shows "You are not authorized to see this web page"
}
end
end
end

Strong parameters and Nested Routes - Rails 4.0

I have no idea how this works in rails but I set up routes like this:
resources :users do
resources :api_keys
end
(User has_many: api_keys, api_key belongs_to: user)
So I then (since I only care about API Keys), created the following controller:
class ApiKeysController < ApplicationController
before_action :authenticate_user!
def index
#user = User.find(params[:user_id])
#api_key = User.apikeys
end
def create
#user = User.find(params[:user_id])
#api_key = ApiKey.new(create_new_api_key)
create_api_key(#api_key, #user)
end
def destroy
destroy_api_key
end
private
def create_new_api_key
params.require(:api_key).permit(user_attributes: [:id], :api_key)
end
end
Which states, authenticate user before every action, index fetches all api keys based on a user id. create is suppose to create an api key based on a user id, (note: create_api_key(#api_key, #user) just an abstracted method that states - if we saved, redirect to user_path with a message, if we failed, back to user path with a error message)
And destroy, well that just finds an api key, destroys it and redirects (again with the abstraction).
Whats the issue?
the create_new_api_key method. Its freaking out and saying:
syntax error, unexpected ')', expecting => (SyntaxError)
I thought this is how I pass in the user id ??
You need to change the order of the arguments passed in to permit to fix the syntax error:
def create_new_api_key
params.require(:api_key).permit(:api_key, user_attributes: [:id])
end

Selectively allow some urls through Rack::Auth::Basic

I've set up a blog that I'd like to be minimally secured (i.e., I just want to keep out random people I don't know, I'm not trying to implement NSA-like security measures). I'm using toto with Rack::Auth::Basic to "secure" the site. I'd like to let through index.xml so that blog readers will be able to read the feed without dealing with password (and yes, I know that this is a big hole in my "security").
How do I let through this one url with Rack::Auth::Basic?
This is how I added basic auth to my site:
use Rack::Auth::Basic, "blog" do |username, password|
[username, password] == ['generic', 'stupidanddumbpassword']
end
How about some good ol' fashioned inheritance? Rack::Auth::Basic is a simple rack app (source: https://github.com/rack/rack/blob/master/lib/rack/auth/basic.rb), so it's possible to override the #call method and skip authentication when the request path matches '/index.xml':
class BlogAuth < Rack::Auth::Basic
def call(env)
request = Rack::Request.new(env)
case request.path
when '/index.xml'
#app.call(env) # skip auth
else
super # perform auth
end
end
end
use BlogAuth, "blog" do |username, password|
[username, password] == ['generic', 'stupidanddumbpassword']
end
For more background on rack, check out: http://rack.rubyforge.org/doc/SPEC.html
I haven't tried #Iain's suggestion about Rack::URLMap, but it looks like it could also be a good option.
Thanks for the answer!
I used this solution too, but made a small change.
because the current solution will probably result in a duplication of code if an app will require more then one path to be accessible,
I changed the code to:
class AppBasicAuth < Rack::Auth::Basic
def call(env)
request = Rack::Request.new(env)
allowed_paths = ['/api/v2/get_new.json']
if allowed_paths.include? request.path
#app.call(env) # skip auth
else
super # perform auth
end
end
end

Resources