Why is Rails encoding slashes for catch all (splat) routes? - ruby-on-rails

When using a catch all route the URLs have the forward slash encoded as %2F which means I can not lookup a record using request.path
map.document '*path', :controller => 'documents', :action => 'show'
Page.find_by_permalink('/blog/my_first_post') # Record found
Page.find_by_permalink('blog%2Fmy_first_post') # Record not found
Firstly why is this encoding happening, and secondly is there a way to turn it off?
PS. I know I could decode request.path before using in the find but I would prefer a pretty URL.

try renaming your glob variable to request_path and use params[:request_path], I do a similar thing and I've never had this problem. however, I believe I snip off the end and search by slug, then do a comparison of the path to what I believe it should be.

Related

how do you determine if request.referrer matches a Rails restful path?

As we know from this post, in Rails, you can get the previous url by calling
request.referrer
But how do you check if the previous url matches one of the restful paths in
your Rails application?
By restful paths, I mean paths provided by Rails, such as,
books_path
book_path(book)
edit_book_path(book)
Of course I can do a regular expression string match on request.referrer, but I think it's a bit ugly.
One particular case, in my application, is that request.referrer can be
"localhost:3000/books?page=4"
and I want to it to match
books_path
which returns "/books"
In this case, how can I check if there's a match without doing regular expression string
match? (if this is at all possible)
Thanks
P.S. I have tried regular expression string match, and it works. I just thought there might be a better way in Rails.
You could extract the path portion of the request.referer using the following:
URI(request.referer).path
You can then use Rails.application.routes.recognize_path to check if path maps to a controller and action, e.g.:
my_path = URI(request.referer).path
# => /books
Rails.application.routes.recognize_path(my_path)
# => {:action=>"show", :controller=>"books", :page=>"4"}
Though not certain of what you want to do with that, pretty sure rails support better ways of controlling redirects. Nevertheless, I guess this is what you are looking for:
request.referer == books_url(page: params[:page])
UPDATED:
This way, even if there's no params[:page]. This would still work properly.
Since recognize_path was deprecated, this is another way of doing it:
Name your route in your routes config using the as keyword:
get 'foo/bar' => 'foo#bar', as: :foo_bar
Then you can do the check like this:
if URI(request.referer).path == foo_bar_path
do_something()
end

Rails catch-all/globbing routes

I am using rails 3.0.14, and I am constructing routes.rb using the resourceful style. I'd like to have a wildcard route that catches all requests that do not match to any route stated.
What's the appropriate way to construct such a route?
put
match '*path' => 'your_controller#your_action'
at the end of the routes.rb file. This is important, since the routes are stepped through top down.
See also http://guides.rubyonrails.org/routing.html -> 3.10
For Rail 4, you need to specify the request type:
match "*path", to: "application#custom_action", via: :all
As others have said, put this at the very bottom of your routes file.
It is not mandatory to use exactly "path" in the match '*path' statement. You can set a whatever token there:
get "*string1"
or
get "*string2"
Rails will assign your real HTTP-query to the param named after your token, example:
get "*user" => "users#show"
in console:
Started GET "/john" ....
Processing by UsersController#show as HTML
Parameters: {"user"=>"john"}
You can use more than one asterisks, say get "*id*user". But in this case you will get some unpredictable result, because Rails processes 2 or more asterisks "in an intuitive way" - for more info see http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments
In addition to #steel and #awenkhh, I recommend adding the following to that route's controller action
respond_to do |format|
format.html
# other formats you already support
format.all { render text: '' }
end
Otherwise, you'll wind up with some ActionView::MissingTemplate: Missing template errors for formats that you weren't expecting.
[rant]Particularly helpful for those people trying erroneous attack vectors around /wp-admin/css/wp-admin.css and the like. I seem to get about 100 requests for /wp-admin/* a day, from super annoying people who apparently would like me to get a more expensive Rollbar account plan.[/rant]

RSpec controller example param with periods

In my controller spec, when I execute the line:
post :register_pass, :device_id => 'DEV1C3', :pass_type_id => 'pass.com.example.GSPassType', :serial_no => '5ER14L'
I get an ActionController::RoutingError:
Failure/Error: post :register_pass, :device_id => 'DEV1C3', :pass_type_id => 'pass.com.example.GSPassType', :serial_no => '5ER14L'
ActionController::RoutingError:
No route matches {:action=>"register_pass", :controller=>"purchases/passbook/registrations", :pass_type_id=>"pass.com.example.GSPassType", :serial_no=>"5ER14L", :device_id=>"DEV1C3"}
Even though rake routes | grep register_pass returns
register_pass POST /v1/devices/:device_id/registrations/:pass_type_id/:serial_no(.:format) {:action=>"register_pass", :controller=>"purchases/passbook/registrations"}
However, when I remove the periods in the :pass_type_id value, the post line listed above executes and the route is recognized (I verified this using rspec, and even curled it directly, putting a breakpoint in the expected controller action, the route works).
I tried using Rack::Utils.escape on the value with periods, but that failed too. I also tried manually changing the periods to their URL-encoded values, but rails didn't seem to decode them in the params hash.
Why don't periods work in this case?
And how can I get a value with periods passed in (without manually decoding it using String#gsub in the controller?
There are two problems you're encountering: 1) a period is a valid character in a URL sequence, so it's won't be replaced by Rack::Utils.escape or URI.escape, 2) Rails treats a period as a path separator to enable format parsing at the end of urls, e.g. .json, .xml, etc. (as defined in ActionController::Routing::SEPARATORS).
For this use case, I'd recommend adding constraints to this route that allow you to be permissive of any character for :post_id. I don't see what your route file looks like, but it could be something like this with the change:
post "/v1/devices/:device_id/registrations/:pass_type_id/:serial_no(.:format)" =>
"purchases/passbook/registrations#register_pass",
:constraints => { :pass_type_id => /.*/ }
Lots of great info on other ways to add this constraint in the Rails guide on routing

How can I redirect multiple variants to the same URL?

When using match in rails routing, as:
match "/about/" => about#index
both mysite.com/about/ and mysite.com/about show the same page.
This may create some duplicate content issues.
What I what to accomplish is that mysite.com/about will be directed to the other URL varient, i.e. mysite.com/about/.
I tried to accomplish simple by writing:
match "/about" => "/about"
but this cause a major error in my routing file and the application failed.
Obviously I'm doing something wrong.
Please advise.
use the following line in application.rb to generate a tailing slash for all the url
config.action_controller.default_url_options = { :trailing_slash => true }

Rails route if no route matched

Is there a solution to redirect the user to a specific controller#action if no route matched? I had a "last rule" like this:
match ":rest" => "application#notfound", :constraints => { :rest => /.*/ }
to redirect all "other" stuff to application#notfound. The problem is that plugins with own routes where set below shis rule and never gets called.
Make a catch-all route and put it as the last rule in your routes.rb file.
match "*rest" => "application#notfound"
you also get whatever the path was as a parameter thru params[:rest] in your controller.
You are definitely not the only person having this problem. I know it's frustrating, but give it some time - I'm confident the Rails team will put together something simple and elegant. I'm in a similar position, only I need to know the incorrect url resource the user entered.
A quick Google search and I found this article demonstrating a full solution to the Rails 3.0 rescue_from ActionController::RoutingError problem. The solution involves Route Globbing and solves my need to both handle incorrect urls and capture the url entered. Hope this helps!
Put this rule last.. Routing matches from top to bottom so if it did find a match then it stops there.
Maybe it's not the best approach, but it will works while you don't find better solution
for rails 2.3, at the application controller, catch the exception like
rescue_from(ActionController::RoutingError) { redirect_to "/xpto" #add your code here }
Rails 3
At initializer put a code like:
module ActionDispatch
class ShowExceptions
def render_exception(env, exception)
if exception.kind_of? ActionController::RoutingError
render(500, 'it was routing error')
else
render(500, "some other error")
end
end
end
end
or something more clean like:
https://gist.github.com/522944
https://gist.github.com/522924
or:
match '/:anything', :to => "home#routing_error", :constraints => { :anything => /.*/ }
but maybe this last match won't be good for you as it'll override all routes from plugins/engines. I think that best solution is to use custom rack middleware for handling special cases.

Resources