Best way to deal with RoutingError in Rails 2.1.x? - ruby-on-rails

I'm playing with the routing.rb code in Rails 2.1, and trying to to get it to the point where I can do something useful with the RoutingError exception that is thrown when it can't find the appropriate path.
This is a somewhat tricky problem, because there are some class of URLs which are just plain BAD: the /azenv.php bot attacks, the people typing /bar/foo/baz into the URL, etc... we don't want that.
Then there's subtle routing problems, where we do want to be notified: /artists/ for example, or ///. In these situations, we may want an error being thrown, or not... or we get Google sending us URLs which used to be valid but are no longer because people deleted them.
In each of these situations, I want a way to contain, analyze and filter the path that we get back, or at least some Railsy way to manage routing past the normal 'fallback catchall' url. Does this exist?
EDIT:
So the code here is:
# File vendor/rails/actionpack/lib/action_controller/rescue.rb, line 141
def rescue_action_without_handler(exception)
log_error(exception) if logger
erase_results if performed?
# Let the exception alter the response if it wants.
# For example, MethodNotAllowed sets the Allow header.
if exception.respond_to?(:handle_response!)
exception.handle_response!(response)
end
if consider_all_requests_local || local_request?
rescue_action_locally(exception)
else
rescue_action_in_public(exception)
end
end
So our best option is to override log_error(exception) so that we can filter down the exceptions according to the exception. So in ApplicationController
def log_error(exception)
message = '...'
if should_log_exception_as_debug?(exception)
logger.debug(message)
else
logger.error(message)
end
end
def should_log_exception_as_debug?(exception)
return (ActionController::RoutingError === exception)
end
Salt for additional logic where we want different controller logic, routes, etc.

Nooooo!!! Don't implement method_missing on your controller! And please try to avoid action_missing as well.
The frequently touted pattern is to add a route:
map.connect '*', :controller => 'error', :action => 'not_found'
Where you can show an appropriate error.
Rails also has a mechanism called rescue_action_in_public where you can write your own error handling logic -- we really should clean it up and encourage people to use it. PDI! :-)

There's the method_missing method. You could implement that in your Application Controller and catch all missing actions, maybe logging those and redirecting to the index action of the relevant controller. This approach would ignore everything that can't be routed to a controller, which is pretty close to what you want.
Alternatively, I'd just log all errors, extract the URL and sort it by # of times it occured.

Related

Rails: Model.find() or Model.find_by_id() to avoid RecordNotFound

I just realized I had a very hard to find bug on my website. I frequently use Model.find to retrieve data from my database.
A year ago I merged three websites causing a lot of redirections that needed to be handled. To do I created a "catch all"-functionality in my application controller as this:
around_filter :catch_not_found
def catch_not_found
yield
rescue ActiveRecord::RecordNotFound
require 'functions/redirections'
handle_redirection(request.path)
end
in addition I have this at the bottom of my routes.rb:
match '*not_found_path', :to => 'redirections#not_found_catcher', via: :get, as: :redirect_catcher, :constraints => lambda{|req| req.path !~ /\.(png|gif|jpg|txt|js|css)$/ }
Redirection-controller has:
def not_found_catcher
handle_redirection(request.path)
end
I am not sure these things are relevant in this question but I guess it is better to tell.
My actual problem
I frequently use Model.find to retrieve data from my database. Let's say I have a Product-model with a controller like this:
def show
#product = Product.find(params[:id])
#product.country = Country.find(...some id that does not exist...)
end
# View
<%= #product.country.name %>
This is something I use in some 700+ places in my application. What I realized today was that even though the Product model will be found. Calling the Country.find() and NOT find something causes a RecordNotFound, which in turn causes a 404 error.
I have made my app around the expectation that #product.country = nil if it couldn't find that Country in the .find-search. I know now that is not the case - it will create a RecordNotFound. Basically, if I load the Product#show I will get a 404-page where I would expect to get a 500-error (since #product.country = nil and nil.name should not work).
My question
My big question now. Am I doing things wrong in my app, should I always use Model.find_by_id for queries like my Country.find(...some id...)? What is the best practise here?
Or, does the problem lie within my catch all in the Application Controller?
To answer your questions:
should I always use Model.find_by_id
If you want to find by an id, use Country.find(...some id...). If you want to find be something else, use eg. Country.find_by(name: 'Australia'). The find_by_name syntax is no longer favoured in Rails 4.
But that's an aside, and is not your problem.
Or, does the problem lie within my catch all in the Application Controller?
Yeah, that sounds like a recipe for pain to me. I'm not sure what specifically you're doing or what the nature of your redirections is, but based on the vague sense I get of what you're trying to do, here's how I'd approach it:
Your Rails app shouldn't be responsible for redirecting routes from your previous websites / applications. That should be the responsibility of your webserver (eg nginx or apache or whatever).
Essentially you want to make a big fat list of all the URLs you want to redirect FROM, and where you want to redirect them TO, and then format them in the way your webserver expects, and configure your webserver to do the redirects for you. Search for eg "301 redirect nginx" or "301 redirect apache" to find out info on how to set that up.
If you've got a lot of URLs to redirect, you'll likely want to generate the list with code (most of the logic should already be there in your handle_redirection(request.path) method).
Once you've run that code and generated the list, you can throw that code away, your webserver will be handling the redirects form the old sites, and your rails app can happily go on with no knowledge of the previous sites / URLs, and no dangerous catch-all logic in your application controller.
That is a very interesting way to handle exceptions...
In Rails you use rescue_from to handle exceptions on the controller layer:
class ApplicationController < ActionController::Base
rescue_from SomeError, with: :oh_noes
private def oh_noes
render text: 'Oh no.'
end
end
However Rails already handles some exceptions by serving static html pages (among them ActiveRecord::RecordNotFound). Which you can override with dynamic handlers.
However as #joshua.paling already pointed out you should be handling the redirects on the server level instead of in your application.

Rails route only if resource is present

I want to have a rails route that triggers if the given id is present, but falls back to further route matching if not.
Specifically, I want to have promo codes at the root level of my site. So, you can go to foo.com/save87 or foo.com/xmasspecial, and it'll treat it as a promo code, but if you go to a promo code that's not there (foo.com/badcode), that route will not match and rails will continue down the route list.
In an ideal world, I'd do something like this in my routes.rb file:
get '/:promo_id' => 'promos#show, constraints => lambda { Promo.exists?(promo_id) }
I know that the above code, without the constraints, would work as a catch-all for foo.com/*, and would sorta work if I put it as the last line in the routes file. Unfortunately, that would result in foo.com/badcode a 'promocode not found' error, rather than a normal 'route not found' error.
So, is there a way to accomplish what I'm trying to accomplish? This is in Rails 3, for reference.
Edit: To clarify a bit-
I want a wildcard url as described above so that our promocode urls are short and memorable (foo.com/save87 instead of foo.com/promo_codes/save87)
I'd prefer to have the option of having other routes after this one. I may, at some point, need another wildcard url at the root level- for example, if I want vanity urls for another resource in my system. For example, if I sell a dozen varieties of widgets, I might want foo.com/widget_deluxe, foo.com/widget_extreme, etc in addition to my promo code urls. I'd have to make sure that there's no collision between promo codes and widget varieties, but that's easily handled elsewhere.
In an ideal world, I'd do something like this in my routes.rb file:
No way. In Rails World, this functionality should go inside controller.
In controller you can do something like
def show
if Promo.exists?(promo_id)
#do something
else
raise ActionController::RoutingError.new('Not Found')
end
end
Update
With routes, you can do something like this
constraints(lambda { |req| Promo.exists?(req.params["promo_id"]) }) do
get '/:promo_id' => 'promos#show
end
Please keep in mind that this constraints will query the database for every request with a url matching the pattern /:promo_id (e.q. /users, /faq). To avoid unnecessary database queries that decrease your website performance, you should add this rule as far as possible to the end of your routes.rb.
Using this routing logic, every request to your application would do an extra search for a promo code before it moved on to the rest of the routes. I recommend looking at your business case and consider doing a Promo controller. If you must do routes, something like this would work but I would put it at the end so that it goes to your regular routes first.
get '*', to: 'promos#show'

URL redirect in Rails 2

In my rails application, for some reason, I have to redirect my URL in to desired URL.
This is how my config setting the routes.rb.
map.connect 'sample/:action/:id.:format', :controller => 'test'
It redirects well when the url is http://example.com/sample. It goes Test controller index method.
When the url is http://example.com/sample/displayname?id=10, it goes to Test controller and searches for displayname method. Obviously it wasn't there, so I got the "undefined" error message.Here I want even though the URL is (http://example.com/sample/displayname?id=10) it quite enough to go Test controller's index method.
Also in the Address Bar I want to URL masking . If I hit http://example.com/sample/ it should redirect & in address Bar http://example.com/test.
How can i do this with Rails-2 application (Rails version 2.3.9)?
First, I don't recommend doing this. It's going against the conventions of how things are done in rails, and that tends to lead to pain.
(As for what I do recommend? Just structure your urls differently. If you do things the way rails makes easy, then you'll have fun using it. Otherwise, your life will be full of pain and suffering.)
BUT, if you really want to, it looks like you can't do regular expression routes in rails. I thought you could, but I see no signs that you can. What you can do, however, is...
def method_missing
index
end
Put that in the controller you want to have this behavior. It'll do what you want, but it also might hide other errors. In any case, don't say I didn't warn you. This seems like a bad idea...
As for the redirect, a before_filter in the test controller will do that.
before_filter :redirect_if_wrong_path
def redirect_if_wrong_path
if request.path =~ /\/sample/
redirect_to request.path.sub('/sample', '/test')
end
end

Ruby on Rails custom routes or how to get request.request_url before the controller is initialized

I am trying to do something for hours and I'm stuck with rails routes.
So.. the idea is to have some even more user-friendly urls like for example /Laptops for a category and /Laptops/Apple-MacBook-Air-and-so-on. I should also use such links for simple pages like /MyDummyPage etc.
So my idea was to get the request_url and check if i can find the page myself. But it seems rails is initialising this request class after defining routes and right before calling the controller.
As you can see I am stuck and can't see any possible solution for my problem.
I will be glad if someone can help me.
Thank you in advance.
All the best!
(Whole thing revised)
If you want to allow dynamic matches along with normal restful routes, there are a couple options- (put it at the end of your routes or it will match everything)
match '*raw' => 'dynamic#show'
And in dynamic_controller.rb
def show
parts = params[:raw].split '/'
# do logic here to set all variables used in views
render #resource_or_page
end
You could also use the input in a search function and redirect to the first result of that search. Or return a 404 if there are no results.
def show
results = search_method_here params[:raw].sub('/', ' ')
if results.any?
redirect_to results.first
else
raise ActionController::RoutingError.new 'Not Found'
end
end
Also, for freindlier urls within restful routes, try out this: https://github.com/norman/friendly_id
I think its important to realize that people generally do not manipulate URLs by hand, and its nice to have readable urls, but its more important for them to be clear on what/where they are doing/going.
In response to your comment, I think you are mislead about routing. If you make 2 routes :category and :page, they match the exact same url, except one of them stores it in params[:category] and the other in params[:page]. To differentiate it, you would need to have a different amount of arguments matched like :category/:product or a namespace, or, perhaps, a restful route which specifies the MVC the route routes to.

What is best strategy to handle exceptions & errors in Rails?

I was wondering if people would share their best practices / strategies on handling exceptions & errors. Now I'm not asking when to throw an exception ( it has been throroughly answered here: SO: When to throw an Exception) . And I'm not using this for my application flow - but there are legitimate exceptions that happen all the time. For example the most popular one would be ActiveRecord::RecordNotFound. What would be the best way to handle it? The DRY way?
Right now I'm doing a lot of checking within my controller so if Post.find(5) returns Nil - I check for that and throw a flash message. However while this is very granular - it's a bit cumbersome in a sense that I need to check for exceptions like that in every controller, while most of them are essentially the same and have to do with record not found or related records not found - such as either Post.find(5) not found or if you are trying to display comments related to post that doesn't exist, that would throw an exception (something like Post.find(5).comments[0].created_at)
I know you can do something like this in ApplicationController and overwrite it later in a particular controller/method to get more granular support, however would that be a proper way to do it?
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordInvalid do |exception|
render :action => (exception.record.new_record? ? :new : :edit)
end
end
Also this would work in case Post.find(5) not found, but what about Post.find(5).comments[0].created_at - I meant I can't throw a full blown exception if the post exists but has not comments, right?
To summarize so far I was doing a lot of manual checking using if/else/unless or case/when ( and I confess occasionally begin/rescue) and checking for nil? or empty?, etc. , but there's got to be a better way it seems.
REPLIES:
#Milan:
Hi Milan
Thanks for a reply - I agree with what you said, and I think I misused the word exception. What I meant is that right now I do a lot of things like:
if Post.exists?(params[:post_id])
#p = Post.find(params[:post_id])
else
flash[:error] = " Can't find Blog Post"
end
And I do a lot of this kind of "exception handling", I try to avoid using begin/rescue. But it seems to me that this is a common enough result/verification/situation that there should be a DRYer way to do this, don't you?
How would you do this kind of check?
Also how would handle it in this case?
Let's say you want to display comment created date in your view:
Last comment for this post at : <%= #post.comments[0].created_at %>
And this post doesn't have any comments.
You can do
Last comment for this post at : <%= #post.comments.last.created_at unless #post.comments.empty? %>
You could do a check in controller. Etc. There are several ways to do it. But what is the "best" way to handle this?
The fact that you do a lot of manual checking for exceptions suggests that you are just not using them right. In fact, none of your examples is exceptional.
As for the non-existing post - you should expect your API users (eg. a user using your web via browser) to ask for non-existing posts.
Your second example(Post.find(5).comments[0].created_at) is not exceptional either. Some posts just don't have comments and you know it up front. So why should that throw an exception?
The same is the case with the ActiveRecord::RecordInvalid example. There's just no reason to handle this case by means of an exception. That a user enters some invalid data into a form is a pretty usual thing and there is nothing exceptional about it.
Using the exception mechanism for these kinds of situations might be very convenient in some situations, but it's incorrect for the reasons mentioned above.
With that said, it doesn't mean you can't DRY the code which encapsulates these situations. There's a pretty big chance that you can do it at least to some extent since these are pretty common situations.
So, what about the exceptions? Well, the first rule really is: use them as sparsely as possible.
If you really need to use them there are two kinds of exceptions in general (as I see it):
exceptions that don't break the user's general workflow inside your app (imagine an exception inside your profile picture thumbnail generation routine) and you can either hide them from the user or you just notify him about the problem and its consequences when neccessary
exceptions that preclude the user from using the app at all. These are the last resort and should be handled via the 500 internal server error in web applications.
I tend to use the rescue_from method in the ApplicationController only for the latter, since there are more appropriate places for the first kind and the ApplicationController as the topmost of the controller classes seems to be the right place to fall back to in such circumstances (although nowadays some kind of Rack middleware might be even more appropriate place to put such a thing).
-- EDIT --
The constructive part:
As for the first thing, my advice would be to start using find_by_id instead of find, since it it doesn't throw an exception but returns nil if unsuccessful. Your code would look something like this then:
unless #p = Post.find_by_id(params[:id])
flash[:error] = "Can't find Blog Post"
end
which is far less chatty.
Another common idiom for DRYing this kind of situations is to use the controller before_filters to set the often used variables (like #p in this case). After that, your controller might look as follows
controller PostsController
before_filter :set_post, :only => [:create, :show, :destroy, :update]
def show
flash[:error] = "Can't find Blog Post" unless #p
end
private
def set_post
#p = Post.find_by_id(params[:id])
end
end
As for the second situation (non-existing last comment), one obvious solution to this problem is to move the whole thing into a helper:
# This is just your way of finding out the time of the last comment moved into a
# helper. I'm not saying it's the best one ;)
def last_comment_datetime(post)
comments = post.comments
if comments.empty?
"No comments, yet."
else
"Last comment for this post at: #{comments.last.created_at}"
end
end
Then, in your views, you'd just call
<%= last_comment_datetime(post) %>
In this way the edge case (post without any comments) will be handled in it's own place and it won't clutter the view.
I know, none of these suggests any pattern for handling errors in Rails, but maybe with some refactorings such as these you'll find that a great deal of the need for some kind of strategy for exception/error handling just disappears.
Exceptions are for exceptional circumstances. Bad user input is typically not exceptional; if anything, it's quite common. When you do have an exceptional circumstance, you want to give yourself as much information as possible. In my experience, the best way to do that is to religiously improve your exception handling based on debugging experience. When you bump into an exception, the very first thing you should do is write a unit test for it. The second thing you should do is determine if there is more information that can be added to the exception. More information in this case usually takes the form of catching the exception higher up the stack and either handling it or throwing a new, more informative exception that has the benefit of additional context. My personal rule is that I don't like catching exceptions from much more than three levels up the stack. If an exception has to travel any further than that, you need to be catching it earlier.
As for exposing errors in the UI, if/case statements are totally OK as long as you don't nest them too deeply. That's when this kind of code gets hard to maintain. You can abstract this if it becomes a problem.
For instance:
def flash_assert(conditional, message)
return true if conditional
flash[:error] = message
return false
end
flash_assert(Post.exists?(params[:post_id]), "Can't find Blog Post") or return

Resources