404s in Ruby on Rails

One of the things that has been irking me while using Ruby on Rails is the difficulty involved in returning a 404 page. For the most part RoR does a good job of Doing-the-Right-ThingTM when encountering an exception, for example, it converts RecordNotFound and RoutingErrors into 404 pages in production mode; however, there are times when I (the programmer) know that a page doesn't exist but RoR has not yet raised an exception. In such cases, it feels truly wrong to simply raise a RecordNotFound exception, as the semantics are questionable and sometimes I'd rather see my 404 page rather than the traceback page, even in development mode. I've found an example of how to coerce Rails to exhibit the desired behaviour; however, it's kind of messy in that it forces every request to be considered public. The following is a far cleaner solution.

class HttpStatus < Exception
  attr_reader :status
  attr_reader :template

  def initialize(status, template)
      @status = status
      @template = template
  end
end

class Http404 < HttpStatus
  def initialize
      super(404, "#{RAILS_ROOT}/public/404.html")
  end
end

class ApplicationController < ActionController::Base
  protected
      def rescue_action(exception)
          if exception.is_a? HttpStatus
              render :status => exception.status, :file => exception.template
          else
              super
          end
      end
end

Essentially, we override ApplicationController::rescue_action to special case exceptions derived from HttpStatus to set the response status code and render a template. This solution is ideal as it works with both public and local requests and does not interfere with the handling of existing exceptions which already have their own meanings.

The mechanics of how it works are fairly simple: rescue_action is the method that dispatches between rescue_action_in_public and rescue_action_locally, thus by overriding rescue_action rather than either rescue_action_in_public or rescue_action_locally we can define the behaviour of rescue independant of whether or not a request is considered local and thus avoid messing with either local_request? or consider_all_requests_local. Furthermore, by defining our own class of exceptions to invoke the special processing in rescue_action, the applications behaviour is only changed when handling the custom exception, rather than overloading the meaning of existing exceptions.

Initial Impressions of Ruby on Rails: An Excess of Magic [1]

After a solid 24hrs of working on a school project involving Ruby on Rails I've decided that RoR relies on more magic that I really feel comfortable with. Admittedly, I've only been working with the language/framework for a short time so it's unlikely that I've "got it" yet, but it just seems that magically choosing the template and then magically packing up the controller's instance variables to provide context is just too much magic. The fact that the session and request objects are magically present and that the response materializes out of a method that as far as I can tell returns @user bothers me. Sure it makes getting that initial CRUD application running dirt simple, but once you want to actually start programing you have to figure out where this stuff comes from and goes to anyway (And then there's the generated code2). That being said, once I got a handle on the basic stuff, I essentially started writing Django applications using RoR :D. The fact that I've basically reverted to writing Django applications using a slightly different syntax and API probably says more about my relative level of comfort with Python+Django vs. Ruby+Rails than anything else.

One thing that does stand out as cool is RoR's use of Ruby as a template language. After all, if I'm a Ruby programmer and I like Ruby, using Ruby to write templates sounds like a great idea. This is exactly the reason that I'm not particularly fond of RoR's approach to ORM. If I'm a Ruby programmer and I like Ruby, wouldn't it be great to define my database schema using Ruby. Except that I don't. At least, not if I'm using Rails+ActiveRecord. I define my schema as a series of diffs, serialized as ActiveRecord calls. This approach provides some cool features, except that if I want to know what instance variables an object has I don't look at it's class, I look at the DB which doesn't feel quite right.

I think both Ruby and Rails could provide a productive application development environment on par with what I'm used to with Python+Django; however, RoR does not inspire a profound sense of "Oh-my-god!-I've-been-doing-web-apps-wrong-all-along." but that might just be the fact that I write MVC web apps whether I'm writing Python or Ruby or even Java. That being said, the project's not over yet, I may yet stop worrying and learn to love Rails.

[1]I fully realize that writing about Ruby on Rails was cutting edge about 3 years ago; however, I've only just started using it, so I only have initial impressions now.
[2]I don't really like generated code. Not all code generators obviously. I like compilers and JIT compilers and even it's-a-code-generator-that's-logically-equivalent-to-a-compiler, but I'm really not a fan of here-I'll-generate-this-boilerplate-code-for-you which works fine until you realize that you should have generated the boilerplate code with option Y. Now you have to decide are you going to port your changes to a freshly generated template or are you going to port option Y to your current code? Neither of which is particularly attractive. In the case of Rails, I find that the code generation doesn't get you much and at the same time obscurs how the framework actually works.