Rodrigo Rosenfeld Rosas
Rails: the Good and the Bad
A while ago I wrote an article explaining why I don't like Grails. By that time I was doing Grails development daily for almost 2 years. Some statements there are no longer true and Grails has really improved a lot since 2.0.0. I still don't like Grails for many more reasons I didn't find time (or interest) on writing about.
Since almost 2 years ago I was back to Rails programming and the application I currently maintain is a mix of Grails, Rails and Java Spring working together. I feel it is now time to reflect about what I like and what I don't in Rails.
What kind of web application I'm talking about?
I've been working solely on single-page-applications since 2009. All opinions reflected here apply to such kind of application, although some of them will apply to any web application. This is also what I consider the current tendency for web applications, like Twitter, Facebook, Google+, GMail and most applications I've seen out there.
When designing such applications one doesn't use make heavy use of server-side views (ERB, GSP, JSP, you name) but usually render your views in the client-side, although some will prefer to render partial content generated in the server. In the applications I've written in those 4 years in different companies and products I've been mostly rendering the views in the client-side so also keep that in mind when reading my review.
How does Rails help me on getting my job done?
The Asset Pipeline
The Asset Pipeline is also well integrated with the routing system.
Automatic code reloading during development
Booting a Rails application may take a few seconds, so you can't just load the entire application on each request as you used to do in the CGI era. It would slow down the development a lot. Being able to automatically reload your code so that you have a faster development experience is a great tool provided by Rails. It is far from simple to implement it properly and people often overlook this feature because it always worked great for most people. Creating an automatic-reloading framework for other languages can be even harder. Try to take a look at what some Java reloading frameworks are doing if you don't believe.
Control over routes
This is supported by most frameworks nowadays but I always wanted this feature when I used to create web sites in Perl long ago. But not all frameworks will make it easy for you to get a "site map" and see all your application routes at once.
Rails is the main reason why the genius Yehuda Katz decided to create Bundler, the best software dependency management software I know about. Bundler is independent from Rails but I'd say Rails has the credits for inspiring Yehuda to create Bundler but I may be wrong, of course. Ruby had RubyGems for a long while but it suffered from the same problems as Maven.
Without a tool like Bundler you have two options. Always specify the exact version of the libraries you depend on (like Maven users often do) or be prepared to face several issues that may arise from different gem versions that are resolved in different times cause by loose version requirements as it used to be the case with RubyGems users.
Bundler stores a snapshot of the current resolved gems in a file called Gemfile.lock so that it is possible to replicate the entire gem versions under production or other developer's computer without having to specify exact version matches in your dependency file (Gemfile).
Great testing tools availability
For simple integration tests that only touch the database I don't need to even load the entire Rails application, which is much faster. I find this flexibility really awesome and makes test writing a much pleasant task.
Other tools that help writing tests in Rails apps are RSpec and FactoryGirl among many others. Most of them can be used outside of Rails scope, but when comparing Rails to non-Ruby web frameworks, it is great to point out how writing web applications with Rails will make automatic testing an easier task than with other languages.
The Rails guides and community
The Rails guides are really fantastic and cover most of the common tasks you need when programming a web applications with Rails. Also, anyone is free to commit any changes to the guides through the public repository docrails and that seems to work great. I've even suggested this approach to the Grails core developers a while ago and it also seems this is working great for them as well as their documentation improved a lot since then.
Rails has a solid community behind it. There are several Rails committers applying many patches everyday and the framework seems to be stronger than ever. You'll find many useful gems for most tasks you'd think of. They're usually well integrated to Rails and you may have a hard time if you decide to use another Ruby web framework.
Most of the gems are hosted on GitHub, which is part of the Rails culture I'd say. That helps a lot to contribute back to those gems by adding new features or fixing bugs. And although pull requests are usually merged pretty fast, you don't even have to wait for it to be merged. You can just instruct Bundler to get that gem from your own fork on GitHub and that is amazing (I wasn't kidding when I said Bundler is the best software management tool I'm aware of).
Despite all critical security holes found on Rails and other Ruby libraries/gems that popped out recently, Rails takes security very seriously. Once security issues are found they're promptly fixed and publicly communicated so that users can upgrade their Rails applications. I'm not used to see this attitude in most other frameworks/ libraries I've worked with.
Rails also employs some security enhancements to web applications out-of-the-box by default, like CSRF protection and provides a really great security guide that everyone should read, even non-Rails developers.
How Rails gets on my way?
Even though Rails is currently my favorite web framework, it is not perfect. As a matter of fact there are actually many things I don't like in Rails and this is what this section is all about and also the main motivation for writing this article. The same can be told about Ruby, which is my preferred language, but also has its drawbacks. Not exactly Ruby the language, but the MRI implementation. I'll get in details in the proper section.
Rails is not only a web framework and this is really bad from my point of view.
Rails release strategy is to keep the version of all its major components the same one. So, when Rails 3.2.12 is released it will also release ActiveRecord 3.2.12, ActiveSupport 3.2.12, ActionPack 3.2.12, etc. Even if it is a single security fix on ActiveRecord all components will have their version increased. This will also force you to upgrade your ORM if you decide to upgrade your web framework.
ActiveSupport should be maintained in a separate repository for instance as it is completely independent from Rails. The same should be true for ActiveRecord.
The ActiveRecord case
The ORM is a critical part of a web application built on top of a RDBMS. It doesn't make any sense to me to assume it is part of a web framework. It is not. Its concerns are totally orthogonal (or at least they should be). So, what happens if you want to upgrade your web framework to make use of a new feature like streaming support? What if the newest ActiveRecord bundled with the latest Rails release has incompatible changes in its API? Why should you be forced to upgrade ActiveRecord when you're only interested in upgrading Rails, the web framework?
Or, what if you love ActiveRecord but are not developing web applications or you're using another web framework? Why would you have to contribute to Rails repository when you want to contribute to ActiveRecord? Or why don't you have a separate discussion list for ActiveRecord? A separate site and API documentation?
I solved this problem myself a while ago by replacing ActiveRecord by Sequel and disabling AR completely in my application. Luckily enough I find Sequel has a much better API and solid understanding about how RDBMS are supposed to be used and knows how to take advantage of their features, like transactions, triggers and many others. Sequel will actually advise you to prefer triggers over before/after/around callbacks in your code for many tasks. This is in line with my own feelings about how RDBMS should be used.
Also, for a long while ActiveRecord didn't support lazy interfaces. Since I've stumbled over Sequel several years ago I really loved its API and always used it instead of AR for some of my Ruby scripts, that weren't related to Rails apps. But for my Rails applications I always tried to avoid adding more dependencies because most gems will just assume you're using ActiveRecord.
But I couldn't be more wrong. Since I decided to move over to Sequel I never regretted my decision. It is probably one of the best decisions I've made in the last few years. I'm pretty happy with Sequel and its mailing list support. The documentation is great and I have great control over the generated queries, which is very important to me as I often need complex queries in my applications. ActiveRecord is simply too way limited.
And even if Arel could help me to write such queries it is badly documented and is considered a private interface, which means I shouldn't be relying on its API when using ActiveRecord because theorically AR could change its internal implementation anytime. And the public API provided by AR is simply too poor for the kind of usage I need.
Migrating to Sequel brought other benefits as well. Now the ORM and the web framework can be independently upgraded. For instance, recently there was a security issue found in ActiveRecord which triggered a whole Rails release which I didn't have to upgrade because it didn't affect Sequel.
Also, I requested a feature in Sequel a while ago and it got implemented and merged in master a day or two after my request. I tested it on my application by just instructing Bundler to use the version on master. Then I found a concurrency issue with the new feature that affected our deployment on JRuby. In the same day I reported the issue it got fixed on master and I could promptly use it without having to change any other bit of my application.
Jeremy Evans is also very kind when replying to questions in Sequel's mailing list and will provide great insightful advices once you explain what you're trying to achieve in your application. He is also very knowledgeable with regards to relational databases. Sequel is really carefully thought and cares a lot about databases, concurrency and many more details. I couldn't recommend it better to anyone that cares about RDBMS.
Lack of a solid database understanding from the main designer
When I first read about Rails, in 2007, my only previous experience with databases was with Firebird when people used to use Delphi a lot in Brazil. I really loved Firebird but I knew I would have to find something else because Firebird wasn't often used in web applications and I wanted to use something that was well supported by the community. I also wanted a free database so the options were basically either MySQL or PostgreSQL. I wasn't really much interested on what database to use since I believed all RDBMS would be essentially the same and I haven't experienced any issues with Firebird. "It all boils down to SQL" I used to think. So I've just made a small research in the web and I found lots of people complaining about MySQL and no one complaining about PostgreSQL. I wasn't really interested in knowing what people were talking about MySQL and simply decided to go with PostgreSQL at the time since I had to choose one.
A few years later I moved to another company that also happened to use PostgreSQL. Then I used it for 2 more years (4 in total). When I moved my job again, this time the application used a MySQL database. "No problems" I thought as I still believe it all boils down to SQL in the end. Man, I was completely wrong!
After a few days working with MySQL, I noticed too many bugs and bad design decisions that I decided after an year to finally migrate the database to PostgreSQL.
But with so many good conventions that you get when you decide to use Rails, the documentation initially used to use MySQL in the examples. Since lots of people really didn't have a strong opinion about which database vendor to choose from. That lead the community that was being formed to adopt MySQL in mass initially.
Fortunately it seems the community understands now that PostgreSQL is a much better database but I'd still prefer Rails to recommend towards PostgreSQL in the Getting Started guides.
An example of how bad Rails opinions are over RDBMS is that ActiveRecord doesn't even support foreign keys, one of the key concepts in RDBMS, in their migrations DSL. That means that the portable Ruby format of the current database schema is not able to restore foreign keys. Hibernate, the de-facto ORM solution for Java-based applications, does support foreign keys. It will even create the foreign keys for you if you declare a belongs-to relationship in your domain classes (models) and ask Hibernate to generate the migration SQL.
If your application needs to support multiple database vendors, I'd recommend you to forget about schema.rb and simply run all migrations whenever you want to create a new database (like a test db, for instance). If you only have to care about a single DB vendor, like me, then just change the AR schema_format to use :sql instead of :ruby. If you don't care about foreign keys, you're just plain wrong.
I believe David Heinemeier Hansson is really a smart guy despite what some people might say. I just think he hasn't focused much on databases before creating Rails or he wouldn't use MySQL. But there are many other right decisions behind Rails and I find it really impressive the boom DHH has brought to web development frameworks. People often say he is arrogant between other adjectives. I don't agree. He has a strong opinion about many subjects. So have I and many others. This shouldn't be seen as impoliteness or arrogance.
Some arrogant core members
People have similar opinion about Linus Torvalds when he is right to the point in his phrases and opinions. He also has strong opinions and a sense of humor that many don't understand. I just feel people get often easily offended for no good reason these days, which is unfortunate. I have to be extra careful when writing to some lists in the Internet that seems to be even more affected than the usual ones. I have received often really aggressive responses in a few mailing lists for stating my opinions in direct ways that people often consider a rude behavior when I call it a honest and direct opinion. I'm trying to avoid those opinions in some list so that people don't get mad with me.
I really don't know those people and I don't have anything against them. Believe me or not, I'm a good person and have tons of friends and I meet with them very often and they don't get offended when I'm direct to the point or when I state my strong opinions even when they don't agree with me. With my closest friends (and even some not that close) I would refer this as the expression "after all, I'm not a girl" in a tone of joke but I can't tell such things in the Internet or people will criticize me to dead. "You sexist! What do you have against girls?" Nothing at all, it is just an expression often used with humor in my city at least... I love my wife and my daughter is about to born and I'm pretty excited about that. I just think people take some phrases or expressions too seriously.
If you ever have the chance to talk to my friends they will tell you I'm not the kind of guy seeking conflicts but they will tell you that I have lots of strong opinions and that I'm pretty honest and direct about them. They just don't find it rude but healthy. And I expect the same from them.
It is just sad when I find some angry response from Rails core members in the mailing list for no good reason. If I call some Rails behavior stupid that take it on personal and will threaten stopping helping me because they take my opinion as a personal attack as if I was calling them stupid people. I don't personally know any of them. How could I find any of them stupid? They are probably much smarter than me but that doesn't mean I can't have my own opinions about some decisions behind Rails and find some of them stupid, which doesn't mean others can disagree with me and think that my way of thinking is stupid. I won't take it as a personal attack. I swear.
On the other way, I find some of their attitudes really bad. For instance, if you ask for change some behavior in Rails or any of its components some will reply: "send a pull request and we can discuss it. Otherwise we won't take time to just discuss the ideas with words. Show us code". I don't usually see this behavior in most other communities I've participated. That basically means: "we don't care that you spend your valuable time in a code that wouldn't ever be merged to our project because we don't agree with the base ideas". There are many things that can be discussed without code. Asking someone to invest their time writing some code that will be later rejected when it could be rejected before is quite offending in my point of view.
By the way, that is the reason I don't spend much time in complex patches to Rails. I've done that once long ago and I didn't get feedback from core developers after a while even after spending a considerate amount of time in the patch and adapting many requested changes to it even though I didn't agree with the changes. So I'd say that my user experience for many libraries is just great but that is not usually the case with the Rails core mailing list. Some of those core developers really believe they're God gifts to the world which makes it hard to argument with them in several aspects. And if you state your strong opinion about some subject you may be seen as rude and they won't want to talk to you anymore...
Of course different people will have different experiences but I believe Rails is not the friendlier web framework in my particular case. The Ruby-core list is a totally different beast and I can't remember any bad experience I had when talking to Matz, Kosaki, Shugo, Nobu and many others. I also had a great experience in the JRuby mailing list, with Charles Nutter and many others. I've also talked about the great experience with Jeremy Evans in the Sequel mailing list. I just don't understand why the Rails core team doesn't seem to tolerate me. I don't have any personal issues with any of them. But I don't usually have a great experience there either so I avoid writing to that list sometimes.
Even after publishing my article with my strong (bad) opinions about Grails I don't remember any bad experience when talking to them in their list. And I know they read my article as it became somewhat popular in the Grails community and I got even some replies from some of the Grails maintainers themselves.
The Rails API documentation
I remember that one of strong features of Rails 1 was the great API documentation. During the rewrite of Rails 3 lots of great documentation was deleted in the process and either got lost or was moved to the Rails guides.
Currently I just stop trying to find any documentation by looking at the API documentation site. I used to do that a lot in the Rails 1 era. So sad the current state is really bad to the point that I find it almost unusable preferring to find the answers to what I'm looking for on StackOverflow, asking on mailing lists, digging into the Rails source code or by other means. If I'm lucky, the information I'm looking for is documented in the guides, but otherwise I'll have to spend some time searching for it.
YAML used instead of plain Ruby to store settings
Rails provides us 3 environments by default: development, production and test. But in all projects I've worked with I always had a staging environment as well. Currently our deployment strategy involves even more environments. Very soon we realized that it wasn't easy to manage all those environments by having to tweak so many configuration files: config/database.yml, config/mongo.yml, config/environments/(development|test|production).rb and many other kept popping up. Also, when you run tasks like "rake assets:precompile" it will use the production environment by default while it would use development by default for most tasks.
Every time we needed to create a new environment it was too much work for us to manage. So we ended up by dropping all those YAML files and simple symlink config/settings.rb to config/settings/environment_name.rb. We also symlinked config/environments/*.rb to all point to the same file. We would also manage the different settings in config/settings.rb. So we have staging.rb, production.rb, test.rb, development.rb and a few others under config/settings. We simply symlink the one of interest in config/settings.rb, which is ignored by Git.
The only exception is that test.rb is always used when running tests. That worked out much better for us and it is much easier for us to create a new environment and have all settings, like Redis, Mongo, PostgresSQL, integration URLs and many more settings grouped in a single file symlinked as settings.rb. Pretty simple to figure out what needs to be changed as well as base our settings on top of another existing environment.
For instance, staging.rb would require production.rb and overwrite a few settings. This is a much improved way of handling multiple environments than the standard way most Rails applications implement, by maintaining sparse YAML files among some DSLs written in Ruby (like Devise and others).
I believe the Grails approach of allowing external overrides Groovy files to better configure the application in a per environment basis a better convention to follow than the one suggested by Rails. What is the advantage of YAML(.erb) files over plain Ruby configuration files?
Deployment / scalability
One of the main drawbacks of Rails in my opinion is that it waited too long to start thinking seriously about threaded deployment. Threads were often successfully used by many web frameworks in many languages but for some reason it has been neglected in the Ruby/Rails community.
I believe there are two major reasons for that. The Ruby community usually focus on MRI as the Ruby implementation of choice and MRI has a global interpreter lock that prevents multiple threads running Ruby code to be executed in parallel. So, unless your application is IO intensive you wouldn't get much benefits from using a threaded approach. I blame MRI for this as they don't really seem to be bothered by GIL. I mean, they would probably accept a patch to fix the issue but they're not willing to tackle the issue themselves as they believe forking is just as good solution. And this leads to the next reason, but before that I'd just like to notice that JRuby always performed great in multi-thread environments and that I think Rails took too long before taking this approach more seriously and consider JRuby as a viable deployment environment for the threaded approach. Threads are in my opinion the proper way of handling concurrency in most cases and I really think that should be the default one as in most other web frameworks in other languages.
Now to the next reason why people usually prefer multi-process over multi-thread deployment in the Ruby community. I've asked once on the MRI mailing list what was the status of threads support in MRI. Some core committers told me that they wouldn't invest time on getting rid of the GIL mainly because they feel forking was a better fit most of the times. It avoided some concurrency issues one might experience when using threads. They also argued that they didn't want Ruby programmers to have to worry about thread-safety, locks, etc. I don't really understand why people are so afraid of threads and why they think they're so hard to use in a safe way. I've worked with threaded applications for many years and I didn't have this bad experience several developers complain about.
I really miss proper threading support in MRI because a threaded deployment strategy allows much better memory usage under high load than the multi-process approach and it is much easier to scale. That is also the reason why I think it should be the default. It would avoid the situation where people have to worry about deployment strategies too early in the process. They think about load balancers, proxy, etc. when a single threaded instance would be enough for a long time before your application starts having throughput issues. But if you deploy a single process using a single-thread approach, you'll very soon realize it doesn't scale even to your few users. That's why I believe Rails should promote threaded deployment by default since it is easier to start with.
But the MRI limitation makes this decision hard to make. Specially because the development experience is usually much better on MRI than it is on JRuby. Tests will start running much faster on MRI and some tools that will speed up it even more won't work well on JRuby, like Spork and similar gems.
So, I can't really recommend any solution to this deployment problem with Rails. Currently we're using Unicorn (multi-process) + MRI to deploy our application but I really believe this isn't the optimal solution to web deployment and I'd really love to see this situation improved in the next years.
Apart from the deployment issues I always missed streaming support in Rails but I haven't created a section about it in this article because Rails master already seems to support it and Rails 4 will probably be released soon.
The MRI shortcomings
When it comes down to the MRI implementation itself, the lack of a good thread support isn't the only thing that annoys me.
Symbols vs Strings confusion
I can't really understand the motivation for symbols to exist in Ruby. They cause more harm than good. I've discussed my opinions already a lot here if you're curious about it.
To make things worse, if the harm and confusion caused by symbols with no apparent benefits wasn't a reason good enough to get rid of them, attackers are often trying to find new ways to create symbols in web applications. The reason for that is that symbols are not garbage collected. If you employ the threaded strategy when deploying your application and an attacker could get your application to create more symbols your application would crash at some point due to memory leak since symbols are never garbage collected, although it might change at some point.
Autoload is a Ruby feature that allows some files to be lazy loaded, thus improving the start-up time to boot Rails in development mode for instance. I'm curious to know if the lazy approach really makes such a big difference when comparing to just require/load all files. And if it does, couldn't this load time be improved somehow?
The problem with autoload is that it can create bugs that are hard to track and I indeed have been bitten by a bug caused by autoload. Here is an example of how it can be triggered:
#./test.rb: autoload :A, 'a' require 'a/b' #./lib/a.rb: require 'a/b' #./lib/a/b.rb: module A module B end end #ruby -I lib test.rb
I really prefer code that makes its dependencies very explicit. Some languages, like Java and most static ones, will force this to happen. But that is not the case in Ruby.
Rails prefers to follow the Don't-Repeat-Yourself principle instead of being always explicit about each file dependencies. That makes it impossible for a developer to use a small part of some Rails component because they are designed in such a way that you have to require the entire component and not just part of it even if that file is pretty independent from everything else.
Recently I wanted to use some code in ActionView::Helpers::NumberHelper in my own class ParseFormatUtils. Even though my unit tests worked fine when doing that, my application would fail due to circular dependencies issues caused by autoload and the way the Rails code is designed.
In my applications it is always very clear what each class is responsible for. Rails controllers will only be concerned about the web layer and most of the logic will be coded in a separate class or module and tested independently. That makes testing (both manual and automated) much easier and faster and also makes it easier for the project developers to understand and follow the code.
I'm really sad that Rails doesn't share my point of view with regards to that and thinks DRY principle is more important than being explicit about all dependencies in each file.
Even though there are several aspects of Rails I dislike I couldn't actually suggest a better framework for a web developer. If I weren't using Rails I'd probably be using some other Ruby web framework and create some kind of Asset Pipeline and automatic reload mechanism but I don't really think it would worth the benefits.
All Rails issues are manageable in my opinion. I think other frameworks I've worked with are not manageable. The have some fundamental flaws that prevent me from actually considering them if the choice is mine to make.
I've reported some serious bugs to Grails JIRA almost an year ago for instance with test cases included and they haven't been fixed yet. This is something to be really worried about. All Rails issues are easily manageable in my opinion.
I may not deploy my application they way I'd prefer but Unicorn is currently fitting our application needs well enough. I can't require just 'action_view/helpers/number_helper' but requiring full 'action_view' instead isn't that bad either.
I'd just like to state that even though I don't consider Rails/Ruby to be perfect, they're still my choice when it comes down to general web development.