how should I test whether a route has params in Sinatra? - ruby

In a Sinatra app, I have many routes that use a date. They are all formatted:
get '/foo/:bar/:year/:month' do
# code
end
I want to create a before hook setting a requested date according to the route params. This shouldn't run if the route doesn't have month and year params.
I tried this:
before do
if params[:year].any? && params[:month].any?
#requested_date = Date.new(params[:year].to_i, params[:month].to_i, 01)
end
end
and this:
before do
if defined?(params[:year]) && defined?(params[:month])
#requested_date = Date.new(params[:year].to_i, params[:month].to_i, 01)
end
end
But I keep running into the same error: Date::Error - invalid date:

To reliably check if params hash have year/month keys you can use Hash#key?, smth. like:
before do
if params.key?(:year) && params.key?(:month)
<set date>
end
end
But there is still a problem. The route '/foo/:bar/:year/:month' will match things like /foo/bar/baz/qux with Sinatra router resolving params to { bar: 'bar', year: 'baz', month: 'qux'}.
So you still cannot just feed the params to Date constructor and expect it to give a valid date for you. In simplest case you can just write a helper method like
def build_date(year, month)
Date.new(year.to_i, month.to_i, 01)
rescue Date::Error
# So what now?
end
and use it in your before block, but another question arise - what to do in case of an error? The easiest solution is to just respond with 404, but you might need something more sophisticated (for example, to communicate the invalid date format to the user).
Another thing to mention is Sinatra's capability to match routes using regexps: for example, you could force Sinatra router to recognize only routes that contain 4 digits for year and integers in range 1-12 for month. I'd probably avoid it (makes routes harder to reason about, also accessing the matched params becomes a bit cluttered), but still a good thing to remember about...

Related

How do I route based on a url parameter in sinatra?

I am using Sinatra and I want to use something like a referrer code in my urls that will somewhat control access and identify the provenance of a given URL.
/secret-code/rest/of/path
should be rejected if "secret-code" is not in a predetermined list.
I want to use route conditions
set(:valid_tag) { |tag| condition { tag === 'abcd' } }
get '/:tag', :valid_tag => params[:tag] do
'Hello world!'
end
but params is not in scope. Do I need to dispatch in the block? What is the best way to handle multiple routes without having to duplicate the tag checking logic in each one?
/secret/route1/
/secret/route1/blah
/secret/route2/
Is there a way to chain handlers? Can I do
get /:tag/*
# check :tag
redirect_to_handler(params[:splat])
By the sounds of things it looks like you're trying to make use of Sinatra's named parameters. Params is only in scope within the block:
get '/:secret_code/*' do
redirect_to_handler unless secret_codes.include? params[:secret_code]
end
The code above assumes you have a collection of 'secret_codes' that you're going to check with the secret_code from the URL.
(Answering my own question)
Sinatra matches the lexically first rule and you can pass onto the next matching rule using 'pass'. So something like this works as long as it is the first rule that would match.
get '/:tag/*' do
halt_if_bad_tag params[:tag]
pass
end
get '/:tag/route1' do
'hello world'
end

Sinatra matches params[:id] as string type, additional conversion needed to match the database id?

I am using sinatra and DataMapper to access an sqlite3 database. I always get an nil when calling get(params[:id]). But when I call get(params[:id].to_i) I can get the right record. Is there anything wrong such that I have to do the conversion explicitly?
The sinatra app is simple:
class Record
include DataMapper::Resource
property :id, Serial
....
end
get '/list/:id' do
r = Record.get(params[:id])
...
end
Obviously this is a problem with Datamapper (if you believe it should be casting strings to numbers for id's), but there are ways Sinatra can mitigate it. When params come in you need to check:
They exist.
They're the right type (or castable).
They're within the range of values required or expected.
For example:
get '/list/:id' do
r = Record.get(params[:id].to_i)
# more codeā€¦
curl http://example.org/list/ddd
That won't work well, better to check and return an error message:
get '/list/:id' do |id| # the block syntax is helpful here
halt 400, "Supply an I.D. *number*" unless id =~ /\d+/
Then consider whether you want a default value, whether the value is in the right range etc. When taking in ID's I tend to use the regex syntax for routes, as it stops following sub routes being gobbled up too, while providing a bit of easy type checking:
get %r{/list/(\d+)} do |id|
Helpers are also useful in this situation:
helpers do
# it's not required to take an argument,
# the params helper is accessible inside other helpers
# it's but easier to test, and (perhaps) philosophically better.
def id( ps )
if ps[:id]
ps[:id].to_i
else
# raise an error, halt, or redirect, or whatever!
end
end
end
get '/list/:id' do
r = Record.get id(params)
To clarify, the comment in the original question by #mbj is correct. This is a bug in dm-core with Ruby 2.0. It worked fine with ruby 1.9. You are likely on dm-core version 1.2 and need 1.2.1, which you can get by running 'gem update dm-core'.

Parameter from the path includes query string in Sinatra. Is that correct?

I am running a Sinatra app under Passenger. I have an action which looks roughly like this:
get '/pic/:id' do
# do stuff ...
canonical_image_url = "/img/%d.jpg" % params[:id]
end
However I see my app is failing with the following exception
ArgumentError (invalid value for Integer(): "22?fill=width&width=512&sig=173798632b6ce659234a34c05324196c92b9a8ef")
which means that somehow the QS parameters are not being extracted from the path. Is this some kind of a weird escaping problem? (that some part of my app requests with a double-encoded query string) or is this a known problem? Or is it designed that way and path-params and QS params cannot be used at the same time?
A simpler way to write this (which will probably not help solve your problem, but is too long for a comment):
get '/pic/:id' do |id|
# do stuff ...
canonical_image_url = "/img/%d.jpg" % id
end

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

Trouble creating custom routes in Ruby on Rails 3.1

I can't seem to set up a custom URL. All the RESTful routes work fine, but I can't figure out how to simply add /:unique_url to the existing routes, which I create in the model (a simple 4 character random string) and will serve as the "permalink" of sorts.
Routes.rb
resources :treks
match ':unique_url' => 'treks#mobile'
Controller
.
.
def mobile
#trek = trek.find(params[:id])
end
Is this because I'm trying to define a custom action on an existing resource? Can I not create custom methods on the same controller as one with a resource?
By the way, when I change routes.rb to match 'treks/:id/:unique_url' => treks#mobile it works fine, but I just want the url to simply be /:unique_url
Update It seems like find_by_[parameter] is the way to go...
I've been playing in console and I can't seem to get any methods to come forward...I can run Trek.last.fullname for example, but cannot run #trek = Trek.last...and then call...#trek.lastname for example. Any clues why? I think this is my issue.
So is there a field on Trek which stores its unique url? If so you should be doing something like this:
#trek = Trek.find_by_url(params[:unique_url])
trek.find_by_unique_url( params[:unique_url] ) # should do the trick
#pruett no, the find_by_XXX methods are generated on-the-fly via Ruby's method_missing call! So instead of XXX you can use any of the attributes which you defined in a model.
You can even go as far as listing multiple attributes, such as:
find_by_name_and_unique_url( the_name, the_unigue_url)
Check these pages:
http://guides.rubyonrails.org/active_record_querying.html
http://m.onkey.org/active-record-query-interface
if you get a undefined method ... for nil:NilClass , it means that the object you are trying to call that method on does not exist, e.g. is nil.
You probably just missed to put an if-statement before that line to make sure the object is non-nil
Hmm. I usually would do something like this:
map.connect "/:unique_url", :controller => "treks", :action => "mobile"
Then in that controller the ID isn't going to be applicable.. you'd need to change it to something like this:
def mobile
#trek = trek.find_by_unique_url(params[:unique_url])
end
(that's if unique_url is the column to search under)

Resources