use parameters in routes definition in sinatra - ruby

I've been trying for hours and still got no way to configure my routes the way I want to.
What I want to achieve is:
http://fooo.bar/prefix1234
I want to capture everything that starts with specifix prefix.
Pretty easy:
get "/prefix:id" do
puts params[:id]
end
But I don't want it to be a fixed prefix. I want to put the prefix in a config file
settings.rb:
set :prefix, 'pre'
get "/#{settings.prefix}:id" do
puts params[:id]
end
this won't work (undefined method `prefix' for Sinatra::Application:Class (NoMethodError)). I also tried capturing with regex:
before do
#prefix = settings.prefix
end
get %r{#{#prefix}(\d+)} do |id|
puts "Params: #{id}"
end
This doesn't work either (URL will not be captured)
Anyone got anything?

I was able to get your example working almost without modification. This allowed me to do what you described:
before do
#prefix = "test"
end
get %r{#{#prefix}(\d+)} do |c|
puts "#{#prefix} #{c}"
erb :test, :locals => {:id => c}
end
I then ran shotgun to test the output and called /test123. The output was:
test 123
My view also reiterated that this was working properly. If the problem is that the URL is not being captured, you may need to reorganize your structure so that it is more like:
before do
#prefix = "test"
end
get "/#{#prefix}/:id" do
puts "#{#prefix} #{params[:id]}"
erb :test, :locals => {:id => params[:id]}
end
I don't know if the latter is feasible for your application, but if you are not specific enough in the routing, you are leaving yourself open for frequent bad matches. In my experience, the more RESTful your application is, the better off you will be when it comes time to writing these types of operations.
Alternatively, perhaps a YAML file to store your settings in, and then parsed by a script would give you better results for the route. For example, a YAML file with these contents:
prefix: test
And then a helper script that parses that, which would look something like this:
helpers do
def config
#config = YAML.load_file("config.yml")
end
end
You could then replace your before block with this:
before do
#prefix = config["prefix"]
end
My coding tastes make me lean toward using the YAML method, but I think any of these solutions should be viable.

The String/Regexp is generated right away. This works:
require 'sinatra'
set :prefix, '/foo'
get "#{settings.prefix}/bar" do
request.path_info
end

Related

Problems with rspec scope in before blocks

I've searched for an answer to this but I just can't seem to figure out what's going wrong. I have an api client test that looks like the following:
module MyTests
describe '#update' do
# using a before(:all) block for setup
before(:all) do
#client1 = Client.new
#initial_payload_state = #client1.update.payload
end
context 'with a known starting payload' do
# The payload is some nasty nested json so I grab an existing one
# and then use a helper method to convert it to a full payload.
# Then I update the client with the new payload. I'm using before(:each)
# so I can get the client into this state for every test.
before(:each) do
#full_payload_state = helper_method(#initial_payload_state)
end
context 'alter_payload_1 works' do
# now that I have the payload in its full state I'd like to alter it to
# produce a certain output
before(:all) do
#new_payload_state = alter_payload_1(#full_payload_state)
end
# I now want to update the client with the altered payload and make sure
# it has the same data. The request and response bodies are formatted slightly
# differently in this case.
it 'works' do
#updated_payload_state = #client1.update(#new_payload_state)
expect(payloads_equal?(#full_payload_state, #new_payload_state).to eq true
end
end
context 'alter_payload_2 works' do
before(:all) do
#new_payload_state = alter_payload_2(#full_payload_state)
end
it 'works' do
#updated_payload_state = #client1.update(#new_payload_state)
expect(payloads_equal?(#full_payload_state, #new_payload_state).to eq true
end
end
In reality, my before block for setup is much longer, so I think it makes sense to keep it that way. I tried to use a before(:each) block so I could have the same known state to start each of the alter_payload contexts. The problem is that with this setup, I get a no method error for this line:
#new_payload_state = alter_payload_1(#full_payload_state)
suggesting that #full_payload_state is nil. I'm certain I've got something wrong with respect to scope, but I'm not sure why or how to fix it. Any help greatly appreciated!
Looks like a scope issue with before(:all).
In general, it's wise to stop using before(:all) because it entangles your tests.
Replace your before(:all) lines with before(:each), and this will make each of your tests independent of the others. This will likely help you find your glitch.

Data driven testing with ruby testunit

I have a very basic problem for which I am not able to find any solution.
So I am using Watir Webdriver with testunit to test my web application.
I have a test method which I would want to run against multiple set of test-data.
While I can surely use old loop tricks to run it but that would show as only 1 test ran which is not what I want.
I know in testng we have #dataprovider, I am looking for something similar in testunit.
Any help!!
Here is what I have so far:
[1,2].each do |p|
define_method :"test_that_#{p}_is_logged_in" do
# code to log in
end
end
This works fine. But my problem is how and where would I create data against which I can loop in. I am reading my data from excel say I have a list of hash which I get from excel something like
[{:name =>abc,:password => test},{:name =>def,:password => test}]
Current Code status:
class RubyTest < Test::Unit::TestCase
def setup
#excel_array = util.get_excel_map //This gives me an array of hash from excel
end
#excel_array.each do |p|
define_method :"test_that_#{p}_is_logged_in" do
//Code to check login
end
end
I am struggling to run the loop. I get an error saying "undefined method `each' for nil:NilClass (NoMethodError)" on class declaration line
You are wanting to do something like this:
require 'minitest/autorun'
describe 'Login' do
5.times do |number|
it "must allow user#{number} to login" do
assert true # replace this assert with your real code and validation
end
end
end
Of course, I am mixing spec and test/unit assert wording here, but in any case, where the assert is, you would place the assertion/expectation.
As this code stands, it will pass 5 times, and if you were to report in story form, it would be change by the user number for the appropriate test.
Where to get the data from, that is the part of the code that is missing, where you have yet to try and get errors.

How can I refactor my Sinatra app?

I've just started writing a reasonably straightforward site using sinatra. My problem is that I wanted to refactor the main app.rb file but am getting errors trying to access the url params.
In my get '/' action, Sinatra's looking at which params are set and then needs to do a few different things depending on what's in the url. Something like this.
class App < Sinatra::Application
...
get '/' do
if params['code1']
#network = 'code1'
mode code here
elsif params['called'] && params['mac']
#network = 'code2'
mode code here
elsif params['code3']
#network = 'code3'
mode code here
end
end
The problem is that I need to require a file that also uses the params.
I've put the following in the above code:
require File.dirname(__FILE__) + '/lib/networks/code1.rb'
Where code1.rb includes:
class App < Sinatra::Application
if params['login'] # == 'login'
pass = 'uampass'
elsif
...
But that gives me the following error:
undefined local variable or method `params' for main:Object
How can I refactor this without causing an error
As far as i know you can't use two (or more) Sinatra applications in, well one application. Since both files define a Sinatra::Application descendant this isn't possible.
Also if you want to use values from the params-hash you should define helper methods Helper Documentation, which you call when processing the route, or you just create Class which has class or instance methods which take params-values as parameters. Actually calling params from another file/class doesn't seem like good practice.
To put this in context: Sinatra applications are organised as handlers. The Sinatra::Application descendant is something like the main handler which uses support methods(helpers and instance methods of the Sinatra::Application descendant) or support Classes, which are usually defined in other files, but do not descend from Sinatra::Application.
To make this a little bit more clearly:
Your main Sinatra file:
require_relative 'another_file.rb'
class App < Sinatra::Application
# ...
#a_handler = MyHandler.new
get '/' do
if params['something'] == 'wanted_value'
#a_handler.handle_it(params)
end
end
Another file ('another_file.rb'):
class MyHandler
def initialize
#an_instance_variable = 'foobar'
end
def handle_it(params_hash)
if params_hash['login'] # == 'login'
pass = 'uampass'
elsif
# ...
end
# ...
# do some stuff
# ....
return pass
end
end
Actual code would of course depend on the real problem you're trying to solve, so if you would elaborate i could be more precise...
The error message contains everything you need to know, and it's nothing to do with Sinatra.
You are requiring code1.rb, which contains this (slightly edited so it will run):
require 'sinatra'
class App < Sinatra::Application
if params['login'] # == 'login'
pass = 'uampass'
end
end
Ruby evaluates code as it encounters it. In this case, you've required 'code1.rb', so it evaluates the code in that file. It encounters 'params' and asks "is there a local variable or method with that name?". There isn't, so it fails as you've seen. Open an irb session and check it out.
Class definitions in ruby are just an expression with a scope.
In relation to Sinatra: params is available in the block that a route declaration takes.
I'd recommend reading Sinatra: Up and Running, which explains some of the 'magic' that is going on (a good companion to the Sinatra Book).

Padrino, name route differently from path?

I want to be able to follow a convention closer to what Rails does with resourceful routing. For example, I'm considering "signups" to be a resource, with it's own controller containing "new" and "create" actions.
In app/controllers/signup.rb I have:
MyApp.controllers :signups do
get :index do
# ...
end
post :index do
# ...
end
end
Is there any way I can use these route names, while actually responding on a path other than '/signups'? It feels like Padrino's route naming system is very tightly coupled with the URLs the routes map to.
I've tried:
MyApp.controllers :signups, :map => '/another-path' do
# ...
end
Among various other things without success. Perhaps I should just go back to using Rails... I was just getting frustrated with the startup overhead in TDD and I'm embarking on a new project at the moment (please don't refer me to Spork... that has it's own issues).
This is how I would do what you are asking
# in app/controller/signups.rb
MyApp.controllers :'another-path' do
get '/' do
# ...
end
end

Writing a Sinatra Extension using options in routes

Lets say I'm writing a sinatra extension which mounts a second public directory at a given mount point.
require 'sinatra'
require 'sinatra/moar-public'
set :moar_local, './downloads/'
set :moar_remote, 'dls'
I now expect a user going to http://myapp.com/downloads/thing.bin to be given the file at [sinatra_root]/dls/thing.bin.
Writing this extension (obviously, it's a simplified example) I have something like this:
require 'sinatra/base'
module Sinatra
module MoarPublic
def self.registered(app)
app.set :moar_local, './downloads/'
app.set :moar_remote, 'downloads'
app.get "/#{app.options.moar_remote}/:filename" do
# Logic
end
end
end
register MoarPublic
end
But app.get has already been called with the default value for moar_remote so the download files are available at /downloads/thing.bin, not at /dls/thing.bin as I'd like. Any ideas?
You're asking for dynamic routes, but Sinatra compiles the route information so it won't work the way you're looking for.
As a work around, you might consider defining a catch-all route, and checking the route information inside the catch-all, e.g.
get %r{^/(*)/bar$} do |capture|
if settings.url_prefix == capture # or perhaps check against request.path_info
# get file
else
status 404
end
end
Obviously, there are still many things to be done there, but you get the drift.
I had no problem registering an extension explicitily in a modular configuration. Illustration below.
class Service < Sinatra::Base
set :url_prefix, 'foo'
register Common
end
module Common
def self.registered(app)
app.get "/#{app.options.url_prefix}/bar" do
"hello world"
end
end
end

Resources