Tuesday, October 02, 2007

10 Areas Where Rails Fails

Having done both some real world Rails development and a lot of real world Java development, I have accumulated some experience, that I would like to share. In particular about where rails fails to deliver for me.

I should start by saying, that I like Rails on a lot of areas. But we also know, that all technologies and tools have a flip side, which we come to know about only by using it for some time.

Let us jump right in...

1. Domain Modelling
A lot of the domain objects (though, by all means not all) of the domain model will come from representations of relational database tables. In Rails, these need to inherit ActiveRecord::Base, which is definitely polluting the domain model.

But what about behaviour only domain objects? One example is reporting queries and how to model them. Say I need to select some of the attributes of two joined tables Foo and Bar. One way to do it is to add a static finder method on one of them, say Foo (which one can be hard to choose). The finder will then return instances of the class chosen, Foo, but with a "nice" collection of attributes from the two different class types. Rails marks the instance as "readonly", but it really sucks. The model instance simple gets the attributes you select, even though they do not belong on the type. These are incomplete instances and not all domain logic will work on the model instance.

Actually, a lot seems to be hooked up on inheriting ActiveRecord::Base. There is no real is-a relation here between ActiveRecord::Base and the domain object.

2. Transaction Demarcation in Code
I knew this before entering Rails and I have come to hate it. You need to demarcate your transactions yourself in code. It is really not that nice.

3. Deployment
This is totally not as easy and clean as with Java. The sole existence (and need of) of a tool like Capistrano says it all. I have previously blogged about Rails deployment compared to Java and I think that post says it all.

4. Tool Support
Aahh, a much debated subject :-) I for one, can and have worked without nice tools, auto-completion and the like. But, I have also come to love what IDEA can do for me, when coding Java. This includes refactoring, auto-completion, documentation popup etc.

Now, this has changed a lot lately with tools like the IDEA Ruby plugin and NetBeans Ruby and Rails support. But still, there are stuff that is next to impossible to do, simply due to the dynamic nature of the Ruby language and the extensive use of these dynamic features in the Rails framework.

What makes NetBeans and IDEA plugins usable for more than syntax highlightning when doing Rails development is the fact, that they are targeting the Rails framework specificly. Not only the Ruby language.

5. Internationalization
It is not there. It is as simple as that. There are hacks and stuff out there, but in practise, it is missing from the framework.

6. Attributes of ActiveRecord models "Invisible"
Due to the dynamic nature of Rails, the attributes of ActiveRecord models are added dynamically by ActiveRecord, using a lookup of attributes on the database table. At first a nice thing with respect to speed in development. In the long term, I have found it irritating at best. I need to combine a view of a migration that created the table together with the model, to get the full picture. And even then, I am missing migrations that have added or changed the table.

And I am violating the DRY principle, when I am writing stuff like max length on a column and not null constraints in the migrations, but also needs to write the same in validations on the model.

7. Validations outside ActiveRecord
All the nice validations like validates_length_of or validates_uniqueness_of are tied to ActiveRecord. There are solutions out there to reuse this code in other classes, but it seems like a design error from the beginning.

8. Migrations Become a Pain
Talking about migrations. It quickly becomes unfeasible to use migrations to add extra columns or change an existing model. Often, migrations use the model classes to change the data as the schema changes. But, when running all migrations on a clean database, it is done using the latest and greatest version of the model. Not the version of the model when the migration was originally written.

9. Polluted APIs
Rails adds helper methods on various API classes which pollutes the APIs. But I suspect the Ruby APIs themselves to be quite polluted themselves. Try doing a "Date.methods" in the rails console: What the f*** are all those doing on Date? :-) If you could do completion in the IDE, you wouldn't be able to find the tree for the forrest

10. Documentation
The Rails framework documentation is spread all over the apidocs, which is not that good with respect to browsing it. I have found some cookbooks, wikis and other stuff, but there is a real lack of a good users manual and/or wiki controlled by some entity that can keep it together and updated.

So What Then?
Despite the above rants, I do like Rails. And I would also recommend it or use it myself, on some specific type of projects. These are the simple, CRUD-like ones with only a small amount of domain logic and complexity.

What I would love to do next time I startup a completely new project, is to base it on Java technologies this time, but choose them for what they have learnt from Rails. One example could be Tapestry5. Another could be Grails with GORM, but that might be too dynamic, hence experiencing the same problems. And then see how productivity is, while still retaining some of the Java goodies, like nice tool support, static checking, refactorings that works, completion, ...

23 comments:

A.A.A said...

you forget to mention:
- performance, when needed..
- when non DB - web things hit. (low level graphics etc.).
- threading, when needed..

Mihai Campean said...

Looking over the screencasts about RoR on the web, I was very impressed with the speed and easiness of the development process, but I could not shake a feeling that it was too good to be true and it had to have some drawbacks. I guess that maybe the conventions on which Rails is build should be refined a bit...

Simon said...

Wow. You don't really seem to have researched this subject thoroughly enough. Yes, Rails has limitations, but many of your points simply seem uneducated.

1) Yes, ActiveRecord is not perfect. You can, however, do Single Table Inheritance without much hassle, and for most cases (i.e. properly normalized relational databases), it works beautifully. The main drawback, which you failed to mention, is the lack of support for multi-column primary key.

2) I'm not sure how you'd do it automatically? I've never seen it, and it seems to me that you do need SOME kind of demarcation to say "these queries are in a transaction", so you can have proper rollback handling code etc.

3) Deployment is definitely an issue. Especially in already-running systems (i.e. Apache).

4) Tool Support... Well, it seems most Rails developers (and Ruby developers in general) are very much satisfied with TextMate, which unfortunately only runs on Mac OS X.
Your complaint about how IDEs can't provide helpful references as is common in Java IDEs... Well, yes, but with Ruby, it's extremely rare that you actually want or need that. One of the founding principles of Ruby is that it should work as you'd think intuitively, and once you learn to trust your intuition, additional screen pollution becomes annoying.

5) Internationalization... Yes, it's not there. If you need it, it's not THAT hard to install ruby-gettext and have it. But yes, for things like currency, date formats, unicode, it's hard.

6) Same as 4, and if you didn't write the validation code in your model, you'd be repeating yourself in every controller that used it. But yes, migrations and models should maybe be fused together... I'm not sure how, because one of the nice things with models is that you don't have to write out all the attributes.

7) Same as 4, and you'd be violating DRY if they were any other place.

8) Yes, migrations need work.

9) Ok, you clearly don't understand how Ruby classes work. When you do "Date.methods" you get a list of class methods (not instance methods) for Date, INCLUDING all methods inherited from base classes... That means you get all methods from the Date class, and all the methods from the Class class, which the Date object is an instance of, AND all the methods from the Object class, since Class is an Object.
In Ruby, everything is an object, so when you declare a method in global scope, it becomes a member of the Object class. Obviously you want some methods in global scope to avoid having to refer to them by instance name, which doesn't always apply.

If you want a list of methods that can be used on an instance of Date, create an instance of Date and run .methods on it.

The Ruby API is all but polluted, you just need to know what it means.

10) WTF? The Ruby/Rails apidocs are excellent, and also very updated all the time (because it's autogenerated from the code). It may not fit your usual way of doing things, but it works for me and most other people. Indeed, the documentation is one of the main reasons Rails has gained so much traction.

- Simon

Anonymous said...

In my opinion GORM is not dynamic in the rails way. In fact, it's the opposite.

You have to define your properties in domain classes. If they're not there you won't be able to call them.

If you want a custom schema mapping you will have to use EJB3 annotations in your classes.

Graeme Rocher said...

@anonymous

There is a fundamental difference in mindset between GORM and ActiveRecord. In our view having your domain model tied to your db is a crap idea. That is why you have to define the properties in GORM.

GORM will then generate your db at runtime as needed.

As for your assertion about needing EJB3, this is incorrect. As of the current state in SVN head and the next release out in a couple of weeks we have a custom ORM mapping DSL: http://grails.org/GORM+-+Mapping+DSL

It even gets round one of the mentioned ActiveRecord weaknesses in multi-column primary keys and much more.

GORM is as dynamic without much of the compromise in performance, domain modelling and so on you get in ActiveRecord

jburrell said...

Simon,
2) I'm assuming he means that transactionality need not be part of the code, but could in fact be declarative (such as Java's Spring and EJB).
4) However much Ruby's guiding principles dictate that it should "work as you think", unfortunately, not all APIs are created equal. When the author of a library thinks differently to you, that's when code-complete helps.
10) I have to agree with the OP here. There is nothing wrong with the Rails apidocs, but there isn't as much in the way of quick recipes or in-depth examples (apidocs only say what each method does, not when to use them). This is somewhere something like Spring does better (although still not well), where it has code snippets and examples in a separate manual.

On the other hand, I'm still learning Rails. Maybe this will ease out as I move along.

Per Olesen said...

@Simon:

Thank you very much for your insightful comments. It is clear, that you know much about the subject, and I always like to be further educated. I do not completely agree, with what you write, though:

2) Declarative Transaction Demarcation

In Java, there are at least two frameworks, that can help with this: EJBs or springframework. In both cases, it is done by the "container", when entering a specific "layer" of the application (actually, this is a simplification, as the possibilities are many). What I like about it, is that it is NOT inside the application code, that I write where begin, commit, rollback is done. This is defined either in config files or lately in annotations.

4) Tool Support

Well, maybe it is too high on my list, as I really can live without. But, when you write "....but with Ruby, it's extremely rare that you actually want or need that. One of the founding principles of Ruby is that it should work as you'd think intuitively, and once you learn to trust your intuition, additional screen pollution becomes annoying..." I really must disagree.

6) Attributes invisible

Yes, I know that it really adds to productivity, that I do not need to define my properties. Only thing is, that my experience with it, was that I ended up finding it irritating in the long run. Can't change my experience.

7) Validations and reusability

My point was, that I would like it to be easier to reuse the validations in non-table-backed models. That is, models not inherting ActiveRecord::Base. Actually, I saw a post on the upcoming 2.0 features, and it looks like this has been extracted into a plugin. Nice.

9) Polluted API

Looks like you are right. Sorry, that was a wild shot from me.

10) Documentation

Yes, there are lots of documentation in the API-docs, but what I complain about, is that it seems to be the only documentation from the project itself. Lots of the documentation on classes in the apidocs fits better into a users manual or a wiki that acts as a manual. There is no "manual like structure" over apidocs, and currently, they try to document much more, than the apis. I think that is a mistake.

You cannot properly document how the overall framework works, by putting it into apidocs on methods and classes.

Anonymous said...

Hmmmmm...no Ruby/Rails trolls yet...thats wierd....well allow me :

1. How dare you !!
2. Rails is not slow.
3. Are you a programmer??
4. Do you even know what Rails is?
5. Speed doesnt matter...DHH says so !!

Anonymous said...

Grame Rocher said:

> GORM will then generate your db at runtime as
> needed.

That's great until you go to production. Migrating your schema after that requires more that. That's where the Rails approach really shines -- it's better for ongoing maintenance, as opposed to one-off demos. Migrations plus the fact that attributes are specified only once (DRY) make maintenance easier. DRY is crucial for maintenance. Of course dynamically adding attributes to your models would be impossible in Java, thus it's foreign and "crap" from a Java mindeset. That's why I'm not a big fan of Grails -- it takes the dynamic potential of a language like Groovy and shackles it to a static mindset.

> There is a fundamental difference in mindset
> between GORM and ActiveRecord. In our
> view having your domain model tied to your
> db is a crap idea. That is why you have to
> define the properties in GORM.

Why is it a "crap idea [sic]". We can argue back and forth as to which approach is better, but the Rails approach is clearly not just a "crap idea". Saying things like that just makes you sound ignorant and uneducated, and turns people of to Grails. It's ironic that the author of a framework named after Rails would hate Rails so much; no one wants to use a hate driven frameowkr.

davidmathers said...

1:

a. If the alternative is maintaing a mapping layer, I'll take pollution.

bi. Database views.
bii. You can also use a non-persistent model combined with find_by_sql (I haven't actually tried this). http://snippets.dzone.com/posts/show/1963

c. ???

2: Works for me.

3: This is rails #1 drawback, but slowly improving. jruby on rails might be the answer.

4: Vim forever.

5: I assume you're right, but I've never needed it.

6: I completely agree. So do the guys at http://hobocentral.net/ and they are working on a solution. I hope it's good because I want to use it.

7. Use a non-persistent model. I think the design here is good and fits with the rails philosophy.

8. I don't love migrations, but this don't sense. Any time a model is used to manipulate data in a migration it is, by definition, the version of the model for that migration.

9. It's all ruby goodness.

10. Works for me.

Umm, "10 areas where rails fails" ???

#3 isn't a failure, just a difficulty. The rest are either "ok, but could be better" or "totally fine as is".

This post should be titled "1 area where rails gets a D"

Per Olesen said...

@davidmatters:

Thanks for your comments.

With "no real is-a relation", I talk about domain modelling and OO terminology. In the cases where inheritance makes sense in domain modelling, there should be a "is-a" relationship. Hence, I should be able to say for example, "Employee is-a ActiveRecord::Base", which doesn't make sense. As an example of something, that might make more sense is "an Employee is-a Person". Inheriting ActiveRecord::Base is purely to reuse some (infrastructural) functionality, not because there is a "is-a" relation.

Kornelis said...

Just a comment on #8 (migrations) - I tend to agree, but you can usually work around this pretty easily by redefining your active record objects in the migration:
class FooBar < ActiveRecord::Base; end
def self.up
add_column :foo_bar, :baz, :integer
end

Not much extra code, and the migration now only cares what is in the database, not what is in your model.

Per Olesen said...

@Kornelius:

Interesting simple work-around. Thanks.

For simple model classes, this could be it. For those with complex business logic, that migrations need to do their work, I think this work-around might be less valuable.

But again, thanks.

E. James said...

Again, a flame post from someone who doesn't know rails enough to rail on it:

1. Domain Modelling
No they don't have to be tied to ActiveRecord, that is a choice

2. Transaction Demarcation in Code
This is a good thing, the code drives the DB not the other way around.

3. Deployment
What the hel are you talking about??? Do you prefer FTP over a deployment process?

4. Tool Support
I have no complaints here, TextMate, ruby-debugger, capistrano, and ample support and plugins from the community

5. Internationalization
Try a plugin, or write one, this doesn't need to be in the framework.

6. Attributes of ActiveRecord models "Invisible"
Again, they don't have to be.

7. Validations outside ActiveRecord
Sure you can write validations outside of ActiveRecord.

8. Migrations Become a Pain
Don;t spread your migrations around. 1 migration == 1 table, then remigrate frequently.

9. Polluted APIs
This was fixed in the last release and isn't the way I prefer to get my stuff anyway.

10. Documentation
Again, never had a lack of documentation issue.

Per Olesen said...

@E. James:

Thanks for showing interest. You are a bit short in your responses, though. Simply saying I am wrong does not do much. Explaining why, whould be better.

First, my post is not a flame. I like rails, as I write, but, I have had some experience, that I am writing about.

Let me try to answer, maybe clearing something up:

1. Domain Modelling
You write: "..No they don't have to be tied to ActiveRecord, that is a choice..". Well, if the model is to map a database table, it surely has to inherit ActiveRecord::Base.

2. Transaction Demarcation in Code
You write: "This is a good thing, the code drives the DB not the other way around.".

Hugh!? That I really dont get. How is that just "a good thing"? And what do you mean about "the code driving the DB and not the other way around"? I am surely not talking about the DB driving the code (how that could be I don't know).

Have you an idea about what I am talking about when I mention "declarative transaction demarcation outside the (application) code"? Like spring does and like what EJBs have done always!

3. Deployment

No, I surely do not prefer FTP. I prefer something standardized like .war or .ear files, as is widely used in the Java world. Something that I believe is missing from Ruby/Rails as a platform.

4. Tool Support

You are properly right. We can do without the fancy tools.

5. Internationalization

Well, in just about any Java framework, this is baked in, which I prefer.

6. Attributes of ActiveRecord models "Invisible"

What do you mean. I should just make them visible by defining them as attributes then?

7. Validations outside ActiveRecord

I am sure you are right. And seems like (upcoming) Rails 2.0 has put it into system.

8. Migrations

Could you elaborate on "Don;t spread your migrations around. 1 migration == 1 table, then remigrate frequently."?

I might actually learn something, if you explain yourself a little :-)

9. Polluted APIs

I was wrong. I know.

10. Documentation

I fealt a lack of a good "Manual" or "Users Guide". Of course, I could just buy the book, which I then did. That is also a (perfectly viable) way for the Rails guys to make money.

Augusto said...

"5: I assume you're right, but I've never needed it."

It's quite amazing that in the 21st century, there's still developer who think internationalization and localization are things they don't need to worry about.

Anonymous said...

# 4. Tool Support.

Regarding TextMate only being available on Mac, this simply is no longer true. Check out the "e" editor http://www.e-texteditor.com which is a TextMate clone for Windows.

Personally, I have found Aptana IDE with the RadRails add-in quite productive for Rails development on Windows. It's Eclipse based and coming from a Java background, it is not only familiar but also very handy as opposed to command line when it comes to rails and rake tasks.

Doug said...

I always thought it was me not 'getting' migrations as I've always hated them. Glad to see its not just me though.

On more than one occasion I've just cleared them all out and started with my latest schema. Such a pain and messy.

Wes Maldonado said...

"What I like about it, is that it is NOT inside the application code, that I write where begin, commit, rollback is done. This is defined either in config files or lately in annotations."

First you should read Ola Bini's Languages in Future Systems to get an idea of where a Rails application might fit in a more "enterprise oriented view" of a tiered system where you absolutely must have transactions.

Secondly: Where would you put these configuration fil... oh, wait, "Convention over Configuration" is one of the Rails principles. It might just be best that you only declare transactions where they are required in the code. When you can't manage the transactions in your code because it's no longer DRY perhaps you're missing a layer?

Kabuto said...

Rails is not the final solution to all web development problems. Rails is great for a certain set of applications and extensible and flexible enough to be used for a wide range of tasks. But there are more than enough cases left for small Phython - and yes - even PHP scripts.

It's not perfect, but show me a framework that is. Rails applications are fun to develop, but you have to trade off some aspects for the comfort you get. One is speed, others are for example the aforementioned issues with Rails' core classes.

I don't see a big problem in this. If you need certain features then extend or rewrite parts of Rails or use another framework.

In my oppinion Rails is best suited for small to medium sized applications that take data from web forms on the one end, stuff it into a database and present it in a nice HTML view on the other end. I would never use Rails to output binary data either from filesystem or the database or do some advanced calculations, because it (and inherently Ruby) is in fact too slow for that.

Assaf said...

3) If you like the Java deployment model better, try JRuby. There's a command line tool that will WAR your Rails app, so you can deploy it like any other WAR (and a wizard for that in NetBeans 6).

6) rake annotate_models. It's a plugin that reads the database schema and documents it, as a comment appearing at the top of your model. Works like a charm, you just run it after the migration and it updates the comments. You don't need to peek into the migrations or guess what the attributes are.

7) I think that's being separated into a module that can be shared by ActiveRecord, ActiveResource and other models.

8) Migrations are intended to upgrade from one working version to the other, and then discarded, hence their name. But they're so nifty many developers use them to build the database (myself included). The problem is not migrations per se, they do what they're designed for, but that we still don't have anything as sleek for building a databases from scratch. The schema dumper is supposed to do that, but it doesn't store all the information a migration may produce.

9) Date.methods returns class methods, Date.instance_method the instance methods (no need to create an instance). Either one returns all methods available on the class/object, which can be a handful. But you can call with false to get only methods defined in that class, e.g. Date.instance_methods(false). And if you're looking for something specific, you can grep for it, e.g. Date.instance_methods.grep(/year/).

Regarding 3, 5, 6 and 8, my experience is that basing an application entirely on Rails is very limiting. You're better off starting with Rails as the base framework, and picking up the appropriate plugin to create a framework for your application. In fact, you may have noticed Rails 2.0 moved some code from core to plugins, the basic idea is to keep the framework light and building a thriving ecosystem.

Tech Per said...

@Wes Maldonado:

Hi Wes, thanks for the comment.

I read Olas post. I guess what you are hinting at, is the new kind of layering, that Ola talks about. I think it needs a bit more explanation than that of Olas (which some of the comments there also mention). But from what he writes, application code should be either in a dynamic language or in a DSL. Okay, that is his opinion. I am not in a position to agree or disagree. I would say, ... it depends.

What I do know, is that an important technical part of domain logic is transaction boundaries. They are not part of the domain logic, but none the less, important for the correctness of the application. Hmm, so what I would like is declarative transactions. Something I believe is not part of rails.

Okay then. Fine for some. It sure is simpler. I just came to dislike that part, when making the move to rails.

BTW: With respect to "Convention over Configuration" and configuration files, you are of course right. But, there is nothing stopping Rails in having a Convention saying, for instance, that all db-logic done in an action, is done as one single transaction, and that the framework (Rails) does the begin and end of the transaction for you. It just hasn't got this. That is my point.

Si said...

Hi,

2) Declarative Transactions -

Take a look at the Rails Declarative Transactions Plugin (dectxn.rubyforge.org), for a way to provide declarative transaction demarcation in a Rails application. It's based on the aquarium AOP framework to give you transaction management through class and method selectors (full regex support).

[Disclaimer - I'm the author of the plugin.]