Don’t Solve The Problem, Prevent It.
When you deal with input in programming, it is always a good practice to make sure you can’t get from the user what you wouldn’t want to get. I know it sounds strange at the beginning, but let me further explain with the simplest example:
Let’s say you want to get a date as an input from the user. A date can have many different formats and dealing with every possible format could become very frustrating. Now if you let the user enter the date in a simple text box you leave the power in the hands of the user. He decides how the input gets to your server, and you find yourself wasting a lot of code lines on the parsing of the date.
That is exactly the reason why graphical date picker components were created. A date picker lets the user choose from a graphical calendar and injects the formatted selection into the text box, which, obviously, should not be editable. So instead of allowing a broad number of date formats and dealing with the input in our code, we narrowed the input format to exactly what we want to get and we can deal now with business logic instead of date parsing. In other words, we did not solve the problem, we prevented it from ever happening. That’s a great attitude which will spare you a lot of code lines.
ActionDispatch and Problem Prevention
In this context, I wanted to talk about Rails routing a little bit. When we think about it, the ActionDispatch mechanism which resolves routes to controller actions is your (as a programmer) first encounter with user input. We already agreed it is best to prevent a problem, than resolving it in our code, so the routing mechanism is definitely the best place to start looking for code smells.
Let’s take a simple example in which we have a Post model with title, content, author_name and post_number attributes. We also want to let the user filter posts by each of these attributes at /posts/filter/content/<some_content_filter>. One might think of following solution :
Although this one will do the job, what we are doing here is solving the problem instead of preventing it. There is no reason, what so ever, to go through a full stack response and not halt the processing of the request the earliest possible – our routing definitions:
This will respond with 404 not found to any filter which does not match the regular expression we defined. A request with an illegal filter won’t even reach our controller code – ActionDispatch deals with it a lot earlier. We benefit both in performance and in less code clutter in our controller.
Correctly Routing Only AJAX Requests
This was a basic example of using the routing mechanism in order to filter unwanted requests.
A more complicated one would be to make some controller actions respond only to AJAX requests. One way to do it would be the following:
respond_to :js and respond_with @posts make sure our controller will only reply to AJAX requests. I think you know where I’m going now – why not stop the request processing with a routing definition?
An ideal solution could be:
Now there are some good news and some bad news regarding this code snippet:
The bad news: The :constraints hash does not have a :only_ajax => true option.
The good news: It is very easy to implement one yourself:
All that is needed is an object which responds to the matches? method. The Request object is passed as a parameter to it so you can query it for whatever you’d like, including it being an xhr request using the xhr? method.
This way non-AJAX requests won’t even reach your controller, and you don’t have to deal with scary
respond_to do |format| blocks.
#1 by sygendev on October 9, 2012 - 7:59 am
Gary Bernhardt agrees: https://www.destroyallsoftware.com/blog/2011/burn-your-controllers
#2 by sygendev on October 9, 2012 - 7:59 am
Reblogged this on SyGen development blog and commented:
Burn your controller a la Erez Rabih
#3 by SutoCom on October 15, 2012 - 4:15 pm
Reblogged this on Sutoprise Avenue, A SutoCom Source.
#4 by Gastón Sánchez on April 5, 2013 - 3:59 pm
Great! very neat solution. Thanks.