Rails: Model.find() or Model.find_by_id() to avoid RecordNotFound - ruby-on-rails

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.

Related

Make 301-redirects administratable by the user in Rails?

we are currently relaunching a bigger website from PHP (Magento with a quite exhaustive forum) into a Rails-app while keeping the forum.
During this undertaking we will migrate quite a lot of content to new URLs, which means we'll have to 301 redirect a lot of them.
Now we all know about Apache/NGINX-rewrites. I also found https://github.com/jtrupiano/rack-rewrite for RACK.
But is there a good way to make 301-redirects administratable by our users with Rails? (I'm basically looking for a GEM or RACK-app, where our users can log in, then see and edit the existing redirects).
Thanks for any help.
You could store all redirects in a model with attributes "from" and "to". Then, you can manage this redirects from your admin area as you want.
Then, in your ApplicationController, you can wrap your actions in a around filter as it says here:
around_filter :catch_not_found
private
def catch_not_found
yield
rescue ActiveRecord::RecordNotFound
redirect = Redirect.where(from: request.original_fullpath).first
redirect_to "#{request.base_url}#{redirect.to}" if redirect
end

Override redirect_to in rails

I use an engine in my rails app that logins the user and redirects to a service param (it's a CAS engine). However, from the host app I want to redirect the user (after he/she has logged in) in a different location sometimes depending on the params. At the moment I can't get it work because rails permits only 1 call of redirect_to/render. The engine inherits from my rails app ApplicationController.
How can I override/redefine redirect_to to call it multiple times?
The problem might be solved in other ways but I really don't want them. I have tried some of them but none can compete with the simplicity of just letting the last defined redirect_to take action.
I'm only interested in solutions that involve redefining redirect_to so that I can invoke it multiple times.
Of course you can "override" it. You can redefine any method in any object at any point in Ruby. However, this is a terrible idea. You should fix your problem, which is that you're calling redirect_to twice, instead of hacking apart Rails in order to allow your problem to continue.
If you're still set on "fixing" this the wrong way, find the source code (this was trivially easy to do), copy it into an initializer/library file of your own, and make the modifications.
module ActionController::Redirecting
def redirect_to(options = {}, response_status = {})
raise ActionControllerError.new("Cannot redirect to nil!") unless options
raise AbstractController::DoubleRenderError if response_body
self.status = _extract_redirect_to_status(options, response_status)
self.location = _compute_redirect_to_location(options)
self.response_body = "<html><body>You are being redirected.</body></html>"
end
end
If you really want to do this, despite being forewarned that it is the wrong solution to your problem and that you're fundamentally altering behavior of Rails that other things may depend on, comment out the line that raises a DoubleRenderError.
It seems it was much easier than I thought. All you need to do is to explicitly modify the response object.
Thus you could declare the following function in ApplicationController:
def re_redirect_to(location, status = 303)
response.location = location
response.status = status
end
That's it basically. Elegant and simple.
As I said in the first post:
I use an engine in my rails app that logins the user and redirects to a service param (it's a CAS engine). However, from the host app I want to redirect the user (after he/she has logged in) in a different location sometimes depending on the params. At the moment I can't get it work because rails permits only 1 call of redirect_to/render. The engine inherits from my rails app ApplicationController.
So basically I had no other option than override the engine's redirect_to in an after_action in ApplicationController. I believe it's much better and more maintainable solution than modifying the engine's source code.
I would like to note here that it's absolutely good to follow the conventions. Definitely calling redirect_to more than once should be avoided in 99% cases. But it's good to know that there is a way to deal with that other 1%. Conventions are just conventions.
This worked for me, in the application controller, override redirect, do your thing, then call super:
class ApplicationControler < ... #depends on your rails version
def redirect_to(options = {}, response_status = {})
# do your custom thing here
super # and call the default rails redirect
Hope this helps,
Kevin

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

Is it possible to specify two root pages in Rails (one for anonymous user another for logged in user)

I am building a product that has static pages and dynamic pages(product related). Both category of pages have different release life cycle. The marketing team working with the designer, release the static pages and the product pages are released by the engineering team.
The static pages reside in public/home and they are self contained. They don't need access to the Rails infrastructure other than providing links.
In this setup, I am trying to implement the following behavior:
When an un-authenticated visitor launches http://www.xyz.com, the user should be taken to the static landing page.
When an authenticated visitor launches http://www.xyz.com, the user should be taken to the product landing page (LandingsController, index action).
In my current implementation, I check if the user is authenticated in the Rails world and render the static page OR the product page.
I want to know the following:
1) How do you handle such scenarios?
2) Is there a way to avoid entering the Rails stack for static home page.
3) Is there a customization for the root_path method to return different root based on the context
1) How do you handle such scenarios?
The common answer would look like this:
class LandingsController < ApplicationController
before_filter :login_required
def index
...
end
...
private
def login_required
if not_logged_in? # This methods depends on your authentication strategy
send_file "/your/static/path/#{params[:action]}", :type => "application/html charset=utf8;"
return false # Halt chain
end
end
send_file documentation
And, depending on the correspondence between each of your actions and your templates, you can further abstract the login_required method into the ApplicationController, and validate if the file exists.
2) Is there a way to avoid entering the Rails stack for static pages
Yes. You have to take my word for it, because I haven't done it myself, but you can use a Rack middleware to do that. Here is an example of how to do something similar, with the exception that instead of a redirect, you would serve the file statically (just set the headers and the results of File.read as content) This depends on the authentication library you're working with, though.
3) Is there a customization for the root_path method to return different
root based on the context
You cannot define a conditional route (that is, defining multiple routes in the routes.rb file), but you can override the root_url method in ApplicationController, assuming you are using a named path root in your route definitions. Something like
class ApplicationController
def root_url(*options)
if logged_in?
"/return/something/custom"
else
super(*options)
end
end
end
This, however, sound really a bad idea, since 1) You should point to the same url, and let the controller handle the request (your links should be blind of where to take you), and 2) It may potentially break other stuff that rely on the root_url and root_path methods.
Unfortunately, Rails' routing can only route requests to different controllers based on something in the request, making the per-request session data just out of reach. Your current implementation is certainly the most common strategy. I am guessing something like this:
def index
if logged_in?
# any logged in logic you need.
else
render :file => 'public/home', :layout => false
end
end
The only way to refactor this to make it feel less "icky" is to move that render call to a before_filter. Since the filter will have rendered?, your action won't get invoked at all. Of course, you could also choose to redirect_to another location for authenticated (or non-authenticated) requests in a before filter, which would solve the problem entirely.
The only thing you could do would be based on the non-existence of the session cookie.
Write a middleware component or Rack application (etc.) that explicitly handles the request if no session cookie is present. Similarly, you could use middleware to re-write the request, and then pass it onto the application layer.
Use a similar strategy as #1, but do it via web server configuration (Apache or nginx), avoiding the Rails app entirely.
But, it's definitely possible for someone to have a session and yet not be logged in (e.g. if they went to another page which you didn't handle this way), or even have invalid session data, so you wouldn't be able to actually eliminate the code you have now. These changes would only serve to increase the performance of the session-less requests, but unless those pages are causing a significant problem (which I doubt), so I would not recommend doing so.

Best way to deal with RoutingError in Rails 2.1.x?

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.

Resources