My project, ONEIS, was started using an early version of Ruby on Rails. Over two years of development, there’s been an ever growing mismatch between what I need and how Rails works, so I’ve been writing a replacement framework which does exactly what I need.
I’ve found Rails is great for smaller applications, but a bit painful when the code gets more complex. I think this is largely due to some of the underlying philosophy and the style of coding it promotes.
A DSL for web apps
Rubyists love Domain Specific Languages, and Rails is no exception. It’s a DSL for web applications, and concentrates on making it possible to develop an application in as few lines of code as possible.
Reducing the amount of code you have to write a good thing, but after a few years of using Rails, I’ve come to the conclusion that it’s been taken too far. It can make the meaning of the code unclear and introduce subtle flaws into applications.
Reduction in clarity
To illustrate the potential lack of clarity, here’s a controller specifying the layout it requires:
def ExampleController < ApplicationController layout ‘example’ end
I’d prefer to see this written using the standard object orientated feature of overriding a method in the derived class, which returns the layout required. For example:
def ExampleController < ApplicationController def determine_layout ‘example’ end end
Of course, this adds two lines of code. But let’s see what happens when you want to make the layout conditional on something other than which controller is used:
def ExampleController < ApplicationController layout :determine_layout def determine_layout (params[:context] == ‘embedded’) ? ‘minimal’ : ‘example’ end end
In the OOP version, it looks like
def ExampleController < ApplicationController def determine_layout (params[:context] == ‘embedded’) ? ‘minimal’ : ‘example’ end end
So, the effect of the
layout statement depends on whether you use a
String or a
Symbol, and you have to read the documentation to work out which it is. And it’s potentially confusing, as
layout ‘example’ is very similar to
layout :example but has a different effect.
Sticking to conventional OO methods means it’s obvious when reading. You might argue that it reduces the flexibility in naming methods, but isn’t Rails supposed to be opinionated and only have one way of doing things?
It also introduces lots more code into the framework, which is more code to test and takes more time to run. There’s lots of infrastructure in Rails to support this DSL style, which could be omitted by using a more OO style.
Here’s another controller, which implements a quick security check before doing something bad:
def ExampleController < ApplicationController def delete_something if params[:password] == TOP_SECRET_PASSWORD do_the_deletion end end private def do_the_deletion # Delete everything! end end
To avoid adding characters and extra things to remember to the code, Rails effectively allows you to call any public method in a controller by including the method name in the URL. This is all very well and good, but if the
private declaration had been omitted, then anyone with a web browser could bypass the security check.
If handler methods had to be named with a
handle_ prefix, this problem would be eliminated. If you made a mistake in your controller, it would be immediately obvious instead of introducing a potential security flaw.
Too much magic
My main concern is that a lot of things are “magic”. For example, the View code is executed in a different context to the Controller, yet the @instance variables are magically available, along with the magically included helper modules.
This is, of course, to rigorously enforce the controller/view separation in the MVC pattern. But Rails itself acknowledges the occasional need to have access to controller functions with the
helper_method declaration, which make a Controller method available in the View.
This magic makes it possible to implement an absolutely minimal DSL for web apps. But sometimes it feels it’s adding additional barriers and complexity where it’s not needed. And it does give practical difficulties: it’s very easy to write code which only works in the context of a controller, so re-factoring it to work outside the request handling process is tricky.
Opinionated software is good when the opinions are right for you
Rails’ opinionated nature has contributed greatly to it’s success. There is one way of doing things, and that’s the way you have do it. I’m especially keen on this approach when writing software as an external development team, because it reduces the client’s risk by allowing any Rails developer to pick up the project later.
The downside is that when you stray outside the boundaries created by those choices, you get into difficulties. For a web application driven by a relational data model which works in a very standard way, Rails really shines. But if you do anything else, it’s going to be painful.
But that is to be expected, since you’re trying to do something it isn’t designed for. Every software product delivers certain functionality, and if you want it to do something else you’re going to be disappointed.
Why I’m not using Rails
None of this is a reason to abandon Rails. Everything is a trade-off, and Rails has chosen to be as terse as possible.
My main reasons for stopping using Rails are:
- ONEIS has a non-relational data model. This breaks all Rails’ assumptions, so most of the really good bits of the framework aren’t available in the majority of the application code.
- It’s a multi-tenant system, where many customers share the same application server, but their data is completely isolated. There’s no support for this in Rails, and you can’t do things like rate limiting customer requests to ensure fair access to resources.
- I’m building as much as I can as plugins, and each customer can use a different set of plugins. This is made quite tricky as request handling can only happen in a Controller.
I found myself spending quite a lot of time working around Rails, and finally something had to give. With my own framework, it’s really quite liberating to remove these ugly bits of code and integrate support into the core of the system.
Why I’m using it
Of course, ripping out a web application framework and building something new is not easy. While I’m writing my own request handling and MVC system, I’m borrowing lots of good ideas from Rails. And indeed, using the code where I can. I’m still using ActiveRecord for the small amount of relational data in the application.
But despite my reservations about some of the design, I will keep on using Rails for my supporting applications, and I suspect for anything I write in the future which has a purely relational model. Rails gets the job done extremely effectively.
General purpose frameworks aren’t easy
The amazing thing about Rails is that it works so well for so many people.
Before I used Rails, I wrote my own general purpose framework, which amusingly took a perl ‘description’ of the application and generated C++ code. This compiled to a single executable with an embedded web server to serve the application. While performance was great, it wasn’t as pleasant to work with as I had hoped. After writing two web apps with it, I abandoned it for Rails.
Writing a special purpose framework, as I’m doing, is much easier. You only have to serve one application, which you can do without supporting any configuration options. This simplifies the task no end.
Even though I’m going off the Rails now, starting the project with Rails was absolutely the right choice. At the beginning of a long development process, you’re largely exploring the problem and trying out ways of solving it. Even if you knew exactly what you needed to build, writing the code reveals things you didn’t think of at the start.
COMMENTSblog comments powered by Disqus