Rodrigo Rosenfeld Rosas
Ruby on Rails: the Bad and Good parts
In my previous article, I had a hard time trying to explain why I wanted to replace Rails with something else in the first place. This article is my attempt to write more specifically about what I dislike in Rails for the purpose of the single page application we maintain.
In summary, in the previous article I explained that I preferred to work with more focused and independent libraries, while Rails prefers to adopt a somewhat integrated and highly coupled solution, which is a fine approach too. There are trade-offs involved with either approach and I won't get into the details for this article. As I said in my previous article this is mostly about developer's personal taste and mindset, so by no means I ever wanted to bash on Rails. Quite the opposite. Rails served me pretty well for a long time and I could live with it for many more years, so getting it out of our stack wasn't an urgent matter by any means.
For the purpose of this article, I won't discuss the Good and Bad of Ruby, since it was mainly written to explain why choosing another Ruby framework instead of Rails.
In case you didn't read the previous article, the kind of application I work with is a single page application, so keep this in mind when trying to understand my motivations for replacing Rails.
Unused Rails features
So, here are some features provided by Rails which I didn't use when I took the decision to remove Rails from our stack:
- ActiveRecord (used Sequel instead);
- Turbolinks (it doesn't make much sense for the kind of SPA we build);
- YAML configuration files (we use regular Ruby files for configuration);
- minitest or test/unit (used RSpec instead);
- fixtures (used factories instead);
- Devise (we have a very particular authentication strategy and authentication frameworks wouldn't add much to the table);
- we have just a handful views and forms rendered by Rails (most are generated with JS);
- REST architecture (we deal with very specific requests rather than generic ones over common resources, which translates to specialized queries that run very quickly without having to resort to complicated caching strategies for most cases in order to get fast responses);
- responds_to (most requests will simply respond with JSON);
- Sprockets, also known as the Rails Assets Pipeline (not sure if this holds true after Rails 5.1 added integration to Webpack);
- generators (I don't use them for a long time because they aren't really needed and it's pretty quick and easy to add new controllers, models, mailers or tests manually);
So, for a long while I have been wondering how exactly Rails was helping us to build and maintain our application. The application was already very decoupled from Rails and its code didn't rely on ActiveSupport core extensions either. We tried to keep our controllers thin, although there's still quite some work to do before we get there.
On the other side, there were a few times I had trouble trying to debug some weird problems after upgrading Rails and it was I nightmare when I had to dig into Rails' source code and I wasted a lot of time in the process, so I did have a compelling reason to not stick with Rails. There were other parts I disliked in Rails, which I describe in the next section.
The Bad Parts
- can't upgrade individual parts, it's all or nothing. If you're using ActiveRecord, for example you're forced to upgrade all Rails parts if you want to upgrade ActiveRecord to get support for some feature. Or the opposite: you might want to upgrade just the framework to get ActionCable support for example, but then you'd have to fix all deprecated usage from your ActiveRecord usage in the process;
- hard to follow code base, when debugging edge cases, which makes it hard to estimate tasks involving debugging weird issues that happened after upgrading Rails for example;
- buggy streaming support through ActionController::Live (had to work around them many times after upgrading Rails). Try to read its source to understand how it works and you'll understand when I say its implementation is quite complicated;
- occasional dead-locks, specially when ActionController::Live was used. That's why those few actions were the first one I moved out of Rails;
- ActiveSupport::Dependencies: implicit autoloading and their problems. You must require full
action_vieweven if you only need
- monkey patches to Ruby core classes and methods pollution (it's my opinion that libraries shouldn't freely patch core Ruby classes except for very exceptional cases such as code instrumenting, implementing a transparent auto-reloading tool and so on, and should be avoided whenever possible);
- automatic/transparent params binding (security concerns, I often wrote code such as
param[:text].to_sbecause I didn't want to get a hash or an array when accessing some param because they were injected by some malicious request taking advantage of Rails automatic params binding rules);
- slow to boot when compared to other Ruby frameworks (more of a development issue), spring is not perfect and shouldn't be required in the first place;
- increased test load time, which is quite noticeable when running individual tests;
- the API documentation is incomplete. The guides are great though, but often I wasted a lot of time trying to look for the documentation of some parts of the API;
- lack of full understanding of the boot process and requests cycle;
- I won't get into the many details why I don't like ActiveRecord because I don't use it for several years and it's not a requirement to use Rails, but if you're curious I wrote an article comparing it to Sequel long ago. My main annoyance with ActiveRecord is related to its pooling implementation and its ability to checkout a connection from the pool outside of a block that would ensure it's checked in again into the pool;
The Good Parts
Rails is still great as an entrance framework for beginners (and some experts as well). Here are the good parts:
- handles static resources (assets in Rails terminology) bundling and integrates with Webpack out of the box;
- good safe default HTTP headers;
- CSRF protection by default;
- SQL injection protection in bundled ActiveRecord by default;
- optimizations to traditional web pages through Turbolinks;
- bin/console and great in-site debugging with the
web-consolegem bundled by default in development mode;
- separate configuration per environment (development/production/test) with good defaults;
- e-mail integration;
- jobs integration;
- integrated database migrations;
- great automatic code reloading capabilities in the development environment (as long as you stick with Rails conventions and don't specify your dependencies manually);
- fast to boot (when comparing to frameworks in other languages, such as Java);
- awesome guides and huge community to ask your questions and get an answer very quickly;
- great community and available gems for all kind of tasks;
- very much audited by security experts and any discovered issues are quickly fixed and new releases are made available with responsible disclosure;
- Github issues are usually quickly fixed;
- Rails source code has an extensive test coverage;
- provide tons of generators, including test, models, controllers, for those who appreciate them;
- provides great performance-related data in the application's logs (time spent rendering views and partials and in the database);
- highly configurable;
- internationalization support;
- helpful view helpers such as number and currency formatting;
- a big team of active maintainers and contributors;
- easy websockets API through ActionCable;
- flexible routing;
- there are probably many more great features I can't remember out of my head because I didn't use myself such as RESTful resources and so on;
- conventions such as paths organizations help a lot teams with lots of developers and frequent turnovers, and when hiring new members in general, or when handing the project to someone else and the like. By knowing Rails conventions, when joining an existing Rails application for the first time the newcomer will know exactly where to find controllers, models, views, workers, assets, mailers, tests and so on. It's also very likely they will be used with many gems commonly used altogether with Rails.
So, Rails is not only a framework but a set of good practices (among a set of questionable practices that will vary accordingly to each one's taste) bundled together as well. It's not the only solution trying to provide a solid ground for web developers though. Another similar solution with similar goals seems to be Hanami for example, although Rails seems to be more mature to me. For example, I find code reloading to be a fundamental part of developing web applications and Hanami doesn't seem to provide a very solid solution that would work across different Ruby implementation such as JRuby for example, accordingly to these docs.
But overall, I still find Rails to be one of the best available frameworks for developing web applications. It's just that for my personal tastes and mindset I'm more aligned to something like Roda than to something like Rails but one should understand the motivations behind one's decisions in order to figure out by themselves which solution works best for their own taste rather than expecting some article to tell you what is the Right Solution ™.