Sinatra + Rack::Test + Rspec2 - Using Sessions? - session

It's the first time I'm working with Sinatra and I just can't get sessions to work in my tests. I have enable :sessions in my app.
I tried:
get "/controller/something", {}, "rack.session" => {:session => "Aa"}
or
get "/controller/something", {}, "session" => {:session => "Aa"}
But no session variables are being set in my request. I've looked around the web and tried several suggestions but nothing seems to work. Am I missing something?
Thanks!

Rack doesn't support passing in sessions via the request anymore (Rack >= v1.0). Read this post for more detailed information on that.
The best way to set a session variable in your app is to call an action inside of your application that will set the session variable. For instance, if you have a route inside your app that sets a session variable like this:
post '/set_sess_var/:id'
session[:user_id] = params[:id]
end
Let's pretend there's another route that you actually wanted to test which is using the session variable like this:
get '/get_user_attributes'
User.find(session[:user_id]).attributes
end
Then in your tests, you should first call the route which sets the session, then go onto another route which uses it. Here is rspec notation, since that is what I use for testing:
it "should print out user attributes" do
user_id = 1
post '/set_sess_var/' + user_id
get '/get_user_attributes'
last_response.body.should == User.find(user_id).attributes
end
If you were going to be using the route frequently in your tests, then you could write a method to accomplish this in your test file (if you're using Rspec, then this method could go in your spec_helper.rb or in your controller_spec.rb file):
def set_session_var(user_id)
post '/set_sess_var/' + user_id
end
and then call it in your tests when you needed it to be set:
it "should print out user attributes" do
set_session_var(1)
get '/get_user_attributes'
last_response.body.should == User.find(1).attributes
end

You need to use the keys that will end up in env:
get "/controller/something", {}, "rack.session" => {:session => "Aa"}

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)

sessions arent working on sinatra

So i've been using sinatra and well no matter what i try i cannot seem to get sessions to work as intended.
Im enabling session/cookies with the following:
use Rack::Session::Cookie, :key => 'localhost_tester',
:path => '/',
:expire_after => 14400, # In seconds
:secret => 'secret_stuff'
And im trying to create a login page, the post data seems to be getting sent but no session is being created. This is what im using:
get '/account-login' do
#title = 'Adnetwork'
erb :accounts
end
post '/account-login' do
email = params[:email]
password = params[:password]
user = User.new()
if user.login(email, password)
#session isnt being made...
session['email'] = email
#redirect once session is complete
redirect to'/dashboard'
else
erb :accounts
end
end
The session wont actually be called "email" thats just an example i was using while testing. But it never actually creates the session. I have cookie editor plugin on chrome to see whats happening and the only thing thats being created is a session called "localhost" tester.
Am i being an idiot and doing it all wrong or is it something else that im missing?

Accessing controller instance variables with Minitest

I'm trying to access the instance variables inside my controllers with minitest.
For example:
microposts_controller.rb:
def destroy
p "*"*40
p #cats = 42
end
How would I test the value of #cats with inside microposts_controller_test.rb with minitest?
I know I can submit the delete request from the browser and check my server logs and find:
"****************************************"
42
I read in another answer that I have access to an assigns hash with all the instance variables but it didn't work. I've also tried looking inside the controller object. Shown below:
microposts_controller.rb:
test "#cats should exist in destroy method" do
delete micropost_path(#micropost)
p controller.instance_variables
p assigns[:cats]
end
output:
[:#_action_has_layout, :#_routes, :#_request, :#_response, :#_lookup_context, :#_action_name, :#_response_body, :#marked_for_same_origin_verification, :#_config, :#_url_options]0:04
nil
I was expecting to see the #cats instance variable inside the controller object. I was also expecting to see 42 being output.
What am I missing here?
You can use view_assigns:
# asserts that the controller has set #cats to true
assert_equal #controller.view_assigns['cats'], true
I had a before_action that checks to make sure the user is logged in, so the delete request was getting redirected.
I also have a test helper that will put a valid user id into the session. Using that everything works as expected :)
microposts_controller_test.rb:
test "#cats should exist?" do
log_in_as(users(:michael))
delete micropost_path(#micropost)
p controller.instance_variables
p assigns[:cats]
end
test_helper.rb:
def log_in_as(user)
session[:user_id] = user.id
end
output:
[:#_action_has_layout, :#_routes, :#_request, :#_response, :#_lookup_context, :#_action_name, :#_response_body, :#marked_for_same_origin_verification, :#_config, :#current_user, :#_params, :#micropost, :#cats, :#_url_options]
42

Is it better to create new controller instance for each HTTP request in Rack based app or to use the same instance?

I'm creating a very simple Rack based application as I want it to do a very specific task.
The server.rb looks something like this:
Path= File.expand_path("#{File.dirname __FILE__}/../../")
require "bundler/setup"
require "thin"
require "rack"
%w(parser auth controller).each do |file|
require "#{Path}/app/server/#{file}.rb"
end
builder = Rack::Builder.app do
use Auth
run Parser.new
end
Rack::Handler::Thin.run(builder, :Port => 8080, :threaded => true)
parser.rb looks like:
class Parser
def initialize
#controller = Controller.new
end
def call(env)
req = Rack::Request.new(env).params
res = Rack::Response.new
res['Content-Type'] = "text/plain"
command= req[:command]
if command =~ /\A(register|r|subscribe|s)\z/i
#controller.register
end
res.write command
res.finish
end
end
Now my question here, from design prospective, is it better to create one instance of Controller and use it with each request(like Idid with the code above), or to create new controller instance for each request(change #controller.register to Controller.new.register)? which is better to use and why?
Thanks in advance
The overhead of creating a new controller per request is likely not that large.
If you store state in the controller (instance variables etcetera) and you reuse it, you could run into concurrency issues such as race conditions or deadlock when under load.
If you take care to ensure that your Controller object stores no state, you can reuse it. If it does any sort of state storage per request, you will need to ensure that the shared resources are property synchronized.
My 2c - create a new controller per request, until you can confirm that you have a performance hit from creating a new controller per request. It's simpler, cleaner, and less prone to strange bugs.

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

Resources