What does Sinatra::Base.condition actually do? - ruby

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.

Related

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

Accessing sinatra variables from a proc

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

Detect which method called the routing condition in Sinatra

I'm using Sinatra with namespace.
When I tried to use condition, I met a problem.
Here's the snippet of code
class MainApp < Sinatra::Base
register Sinatra::Namespace
set(:role) do |role|
condition{
### DETECT WHERE THIS IS CALLED
p role
true
}
end
namespace '/api', :role => :admin do
before do
p "before"
end
get '/hoo' do
p "hoo"
end
end
namespace '/api' do
get '/bar' do
p "bar"
end
end
end
The above code outputs following message to console when accessing /api/hoo
:admin
:admin
"before"
:admin
"hoo"
I could not understand why :admin is displayed three times. However, maybe one is from namespace, and other twos are from before and get '/hoo'.
On the other hand, accessing /api/bar shows :admin two times.
I just want to do the filtering only before get '/hoo'. Is there any idea?
NOTE: I don't wan't to change URL from /api/hoo to something like /api/baz/hoo
You can debug the steps using the caller:
http://ruby-doc.org/core-2.0/Kernel.html#method-i-caller
(Note: I wouldn't recommend to leave caller in production code unless you absolutely need it for introspection, because it's quite slow.)
Re the Sinatra filters in particular, note that you can at the very least qualify the route and conditions they apply to:
http://www.sinatrarb.com/intro#Filters
before '/protected/*' do
authenticate!
end
before :agent => /Songbird/ do
# ...
end
I can't recollect how to get the http method, but if you look at the sinatra source code you'll likely find it -- last I looked, I recollect each of get, post, etc. to forward their call to the same function, with a method parameter.

How to return the receiver instance's self from should_receive block

I'd like to have instance methods of a class return self, and be init with another class instance self.
However I'm struggling to see how to spec this succintly:
::Api.should_receive(:new).once do |arg|
arg.should be_an_instance_of(::Cli)
end
When running this spec, this ensures that the next method is called on true instead of the Api instance, as expected, that is the return value of the block. Example:
class Cli
def eg
api = Api.new(self)
api.blowup # undefined method for true
end
end
I'd really like the block to return the Api instance self without invoking another call to Api.new(...) in the spec, the example below does this and to my mind a non-rspec reader would wonder why the spec passes when clearly Api.new(...) has been called more than once.
Can anyone suggest how best to do this?
Current solution:
This reads like ::Api.new(...) is called thrice: once to create api, once to create cli, once to create start. Yet the spec of one call passes. I understand why and that this is correct, so not a bug. However I'd like a spec that a reader not familiar with rspec could scan and not have the impression that Api.new has been called more than once. Also note that ...once.and_return(api){...} does not work, the block needs to return api in order to pass.
let(:cli){ ::Cli.start(['install']) }
let(:start){ ::Cli.start(['install']) }
it 'is the API' do
api = ::Api.new(cli)
::Api.should_receive(:new).once do |arg|
arg.should be_an_instance_of(::Cli)
api
end
start
end
You can save the original method (new) in a local variable and then use it to return the new api from within the block:
original_method = ::Api.method(:new)
::Api.should_receive(:new).once do |arg|
arg.should be_an_instance_of(::Cli)
original_method.call(arg)
end
This will run the expectation, checking that the argument is an instance of ::Cli, and then return the value from the original method (i.e. the api).

How to use yield self within Rspec

This is a description of how to create a helper method in Rspec taken from the Rspec book (page 149). This example assumes that there is a method called 'set_status' which is triggered when the 'Thing' object is created.
Both sets of code create a new 'Thing' object, set the status, then do 'fancy_stuff'. The first set of code is perfect clear to me. One of the 'it' statements it triggered, which then calls the 'create_thing' method with options. A new 'Thing' object is created and the 'set_status' method is called with the 'options' attribute as the parameter.
The second set of code is similar. One of the 'it' statements is triggered, which then calls the 'given_thing_with' method while passing ':status' hash assignment as a parameter. Within the 'given_thing_with' method the 'yield' is triggered taking the 'Thing.new' as a parameter. This is where I am having trouble. When I try to run this code I get an error of "block given to yield". I understand that whatever attributes that are passed by yield will be returned to the 'thing' in pipe brace from the 'it' statement that called the 'given_thing_with' method. I can get the new
What I don't understand is why the code block is not called in the 'given_thing_with' method after the 'yield' command. In other words, I can't code in that block to run.
Thanks in advance for your help.
The remainder of this question is quoted directly from the Rspec book:
describe Thing do
def create_thing(options)
thing = Thing.new
thing.set_status(options[:status])
thing
end
it "should do something when ok" do
thing = create_thing(:status => 'ok')
thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil)
...
end
it "should do something else when not so good" do
thing = create_thing(:status => 'not so good')
thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil)
...
end
end
One idiom you can apply to clean this up even more is to yield self from initializers in your objects. Assuming that Thing's initialize() method does this and set_status() does as well, you can write the previous like this:
describe Thing do
def given_thing_with(options)
yield Thing.new do |thing|
thing.set_status(options[:status])
end
end
it "should do something when ok" do
given_thing_with(:status => 'ok') do |thing|
thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil)
...
end
end
it "should do something else when not so good" do
given_thing_with(:status => 'not so good') do |thing|
thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil)
...
end
end
end
The example in the book is a bit confusing because the implementation of Thing is not shown. To make this work you need to write Thing like so:
class Thing
def initialize
yield self
end
end
When given_thing_with is called it yields a new Thing, which will yield itself when it is constructed. This means that when the inner code block (the one containing thing.set_status) is executed it will have a reference to he newly built Thing.
There are 2 issues with the code from book.
1. Setting up the initializer to yield itself
When the Thing object is created, it needs an initializer and need yield itself.
class Thing
def initialize
yield self
end
end
However, this alone will still causes an error, at least on my system, which is Ruby 1.9.3. Specifically, the error is 'block given to yield (SyntaxError)'. This doesn't make much sense, since that is what we want it to do. Regarless, that is the error I get.
2. Fixing the 'block given to yield' error
This is not as obvious and has something to do with either Ruby or the 'yield' statement, but creating a block using 'do...end' as was written in the book and is shown below causes the error.
yield Thing.new do |thing|
thing.set_status(options[:status])
end
Fixing this error is simlpy a matter of creating the block using braces, '{...}', as is shown below.
yield Thing.new { |thing|
thing.set_status(options[:status])
}
This is not good form for multiline Ruby code, but it works.
Extra. How the series of yields works to set the parameters of the 'Thing' object
The problem is already fixed, but this explains how it works.
the "caller block" calls 'given_thing_with' method with a parameter
that method yields back to the "caller block" a new "Thing" and a block (I'll call it the "yield block")
to execute the "yield block", the Thing class needs the initialization and 'yield self', otherwise the 'set_status' method will never be run because the block will be ignored
the new "Thing" is already in the "caller block" and has it's status set and now the relevant method is executed

Resources