Accessing sinatra variables from a proc - ruby

i'm using modular Sinatra, with these code
def is_login?
session[:auth_token].nil? # error: undefined variable or method `session`
end
if is_login?
menu['Logout'] = '/logout'
else
menu['Login'] = '/login'
end
get '/logout' do
session[:auth_token] = nil
end
those code returns an error undefined local variable or method 'session' for main:Object, because session only works in inside of get, how to make session hash accessible from outside?
i've tried another alternative, that is moving is_login? into helper and as a define_method so it could access session, but the similar problem arise, that i could not call is_login? as it's not defined, here's the code:
helpers do
define_method :logged_in? do
session[:access_token].nil?
end
if is_login? # error: undefined method `logged_in?`
menus['Logout'] = '/logout'
else
menus['Login'] = '/login'
end
end

A session only makes sense within the context of a request. Thus, using it outside (such as in the if expressions you gave above) will not succeed.
Instead, use it within a request. For example
get '/' do
if is_login?
# your code
else
# your code
end
end

Related

Return immediately from `call(env)` after providing a response

I have a Rack web server app to validate in call(env) the provided HTTP query string and return different responses immediately (with appropriate erb) if those bits of validations fail.
I'm calling the following method to provide a response:
def respond(http_status, http_headers, html_body = '')
# Provide HTTP response
html_body = yield if block_given?
[http_status, http_headers, [html_body]]
end
which I gleaned from the web.
I'm then calling respond(...) from different points inside my call(env) method rather like this:
def call(env)
case blah
when '/'
if validation_a_fails
respond(invalid_a)
else
set up a variable for later use...
end
if validation_b_fails
respond(invalid_b)
else
set up another variable for later use...
end
if validation_c_fails
respond(invalid_c)
else
set up something else for later use...
end
else # not root url
respond(404_situation)
end
end
end
I expected that a call to respond(invalid_a) would exit the call method immediately. However, it doesn't. Instead, the rest of the call method are executed.
How can I get the respond(...) to return immediately to the calling browser?
if validation_a_fails
return respond(invalid_a)
else
in your case you can't omit return because you have multiple if-elses so it'd go through them.

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

How to distinguish between the Sinatra request object and the Rack Test request method?

I have a method that runs in the Sinatra app scope that checks to see if the request is secure:
secure_request?
request.env[ 'HTTPS' ] == 'on'
end
This works fine, but when I call it from another class that does not share the Sinatra app scope, it attempts to make an Rack Test request, raising an error: wrong number of arguments (0 for 1).
So, is there a way to specify the Sinatra app request explicitly, such as self.request or app.request?
Calling a request method from another class smells like poor code design, tightly coupling that other class to your app. Where is secure_request? defined? Is it a helper?
I would personally call a method from Sinatra to that other class and pass in the request value, instead of having that other method poll to find out. For example:
class OtherClass
def some_method( opts={} )
if opts[:secure]
# …
else
# …
end
end
end
class MyApp < Sinatra::Application
helpers do
secure_request?
request.env[ 'HTTPS' ] == 'on'
end
end
get '/' do
#otherclass.some_method( secure: secure_request? )
end
end

Can't access current_user inside .new do block in the ApplicationController

I'm using devise and the bitbucket api gem and I have a method in my ApplicationController which creates an instance so I can make API calls. To do that, it tries to read the token and secret from the current_user.
This works fine with hardcoded token and secret strings, I'm also able to do puts current_user.inspect before the do block, and that all works fine. I'm also sure that bb_token and bb_secret exist (I'm able to call puts on them individually).
But once I try to create my bitbucket instance, it can't read current_user anymore. Any ideas?
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :current_user
def bitbucket
puts "token----------"
puts current_user
#bitbucket = BitBucket.new do |config|
config.oauth_token = current_user.bb_token # replaceing this with hardcoded string works
config.oauth_secret = current_user.bb_secret # replaceing this with hardcoded string works
config.client_id = 'xx'
config.client_secret = 'yy'
config.adapter = :net_http
end
end
end
And the error:
NameError (undefined local variable or method `current_user' for #<BitBucket::Client:0x007fbebc92f540>):
app/controllers/application_controller.rb:12:in `block in bitbucket'
app/controllers/application_controller.rb:11:in `bitbucket'
It seems block passed to BitBucket.new is executed in context of new BitBucket::Client instance (BitBucket.new is really BitBucket::Client.new, according to this).
A glance to the source confirms this supposition.
If you want to pass current_user, you can recall that the blocks are closures, so they keep the context in which they are defined. So you can do something like this:
def bitbucket
# (...)
user = current_user # local variable assignment
#bitbucket = BitBucket.new do |config|
config.oauth_token = user.bb_token # it works because user is local variable and the block is closure
# (...)
end
end
Inside BitBucket.new do..end block,self is set to config. But current_user is not a instance method of BitBucket class. Thus a valid error is thrown.

What does Sinatra::Base.condition actually do?

I've come across the sinatra condition method and am puzzled in how it works.
I have a piece of code:
def auth user
condition do
redirect '/login' unless user_logged_in?
end
end
Which checks to see if a user is logged for certain routes, an example route:
get '/', :auth => :user do
erb :index
end
The method user_logged_in? is defined in a helper file in the lib directory of the project:
def user_logged_in?
if session[:user]
#user = session[:user]
return #user
end
return nil
end
So, the question is:
How does the condition block know what the session[:user] contains, when at the get '/' route the session[:user] hasn't even been set?
The condition method is defined in the following GitHub page: sinatra base condition method
Thanks.
When you define a route, the key of each member of the options hash is called as a method, with the value passed as the arguments.
So when you do get '/', :auth => :user do ... the method auth gets called with the argument :user. This in turn calls the condition method with the block.
The condition method is actually defined just above where you link to which is a usage of it. It looks like this:
def condition(name = "#{caller.first[/`.*'/]} condition", &block)
#conditions << generate_method(name, &block)
end
The generate_method method converts the block into a method with the given name, and then this method is saved in the #conditions array. The contents of #conditions are then saved with the definition of the route, and #conditions is cleared ready for the next route definition.
At this point, the block of code passed to condition hasn't been executed. It has in effect been saved for later.
When an actual request comes in, if the request path matches the route, then each condition associated with that route is executed to check that it is fulfilled. In this example, this is when redirect '/login' unless user_logged_in? is first executed, so the session will have been set up and session[:user] will be available (or not if they're not logged in).
The important thing to understand about this is that when you pass a block to a method, the code in that block is not necessarily called right away. In this case the code in the block is only called when an actual request arrives.
Because Sinatra is responsible for calling both the condition methods and the route methods. Therefore, it should be safe to assume that whatever is set when your route method executes is also set when your condition execute.
Take a look at the code starting here: conditions are called one by one; if all conditions match, then the block gets called. Nothing much happens between checking conditions and calling the block: they are basically run with the same context.

Resources