Last posts:
Web Design » Web Development » I Don't Like Rails
I Don't Like Rails
Views: 3332 | 1-11-2010, 09:28 | Category : Web Development, Other

Something thing I've quietly been up to is taking a bit of time to learn Rails. Not Ruby in its full depth, but enough to understand it in the scope of what pieces you'd use to deliver a Rails app.

Hi all,

Looks like someone linked to this again - comments have been popping up all day.

First, thanks to those in the Rails community who read this post and corrected items that have changed since the book I learned from was published, both in comments and through discussions with me. I'm glad that there are people open to disagreement, and see critical thought as a productive process leading to greater products in the end.

In this post, I'm by no means implying ColdFusion is a perfect product - it's not. There's stuff every day that tweaks me, such as taking a value for its "collection" attribute, but the name of a value for its "query" attribute. It's bizarre and inconsistent.

I _do_ think that Rails has absolutely wonderful intentions. I would never argue that it's not wonderful that someone stood up and said "Hey, look, you're using umpteen design patterns that require you to re-type the same property name in five different places just to add it, and it that's just silly, so here's something better for that kind of task." And I highly respect the people who followed through on that mantra and put the RoR product out there in the world.

If it sounds like I'm apologizing for this post, I'm not. I still don't like Rails.

I think it encourages bad habits, such as letting SQL leak up above the data layer.

It encourages "kitchen sink" classes (the other end of the spectrum from an anemic domain model).

It encourages construction of applications procedural manner, through "actions" that too often become a mixture of application flow control and business logic. (I have the same beef with the Command pattern).

I think too many "this is fun and convenient, let's make it part of the framework" things have been added, such as "2.months.ago" - does that turn April 1, 2007 into February 1, 2007, or does that use the Rails-based assumption that a month is 30 days and give me a date that suddenly isn't February 1, and may vary due to leap years?

So, yes, 1 million points to DHH (and everyone else who's worked on the project) for striking out and making the simple once again simple. If you enjoy working in Rails, please, do so, and I hope it brings you much joy. However, the implementation just isn't something I like. I'll do what I enjoy instead.

Note: this is the longest blog entry I've ever produced. Printed, it's around thirteen pages. If you get tired, please at least read the conclusion at the end, where I state what I really do like about Rails!

What exactly have I done with it? I've tinkered with using it to write CRUD apps, with and without the scaffold bits. I've experimented with what it takes to add real logic (not just CRUD functions) to your apps. I've also hooked it up via AJAX, which is a snap. However, I kept tripping over not knowing things (because, despite all advertisement, some stuff just isn't intuitive).

So, a little while ago, I bought the RoR equivalent of the WACK: Agile Web Development with Rails. I'm happy to say that I've actually read every relevant page, from "Ruby on Rails is a framework..." to the last line of Appendix C.2, "Sample System Notifier." Basically, the only things I haven't read (and re-read or coded myself until grokking is full) is Appendix C.3 "Cross-Reference of Code Samples," Appendix D "Resources," and the Index.

While I may not have a year of experience using RoR (nor do I desire to), I can at least say I've probably done a lot more to educate myself about it than many users of most frameworks, regardless of the language or platform.

After reading the book, I've pretty much concluded that I don't like Rails, don't want to work in it (although Ruby might be fun!), and I'll be giving the book away to the next person who runs into me and asks me for it.

It's easiest to describe why I don't like Rails by simply going back through the book. I'm annotating the points that make me twitch with page numbers.

Introduction

Some of this (like the make routine, or dealing with database drivers) may seem like developer 101 stuff. I've been there, and done that, and I use a product that takes care of that stuff: ColdFusion. Install it (the installer works like a charm), and you've got a RAD platform, drivers for a ton of DBs, interaction with POP, SMTP, FTP, LDAP, SOAP, XML, and many other acronyms, and the ability to create PDFs and GIF/PNG/Flash charts without any extra libraries.

Oops, sorry for the plug. I'm passionate about my platform, too. I'll also be happy to admit that it drives me crazy sometimes, and I spent 30 minutes today seeing why a statement would only run if I aborted the request before it completed.

Since it's in a logical progression, let's start at the beginning of Agile Web Developments with Rails and see what makes me twitch. If you're just skimming, look for headings in bold for the "worst of" items. Or, if you want to feel that I'm not completely bitter, look for things in green.

Page 11-13 ? A little more on MVC, please

MVC (Model View Controller) is defined. They never mention that your Model shouldn't know about your View layer. Not a specific "Rails" thing, but I just sort of think that's fundamental knowledge that really needs to be there in the next edition.

Page 15-17 ? Rails' ORM is datacentric, not object driven

According to the book, database-centric programming is stated to be intertwining SQL in with application logic. The opposite, "Object/Relational Mapping," maps tables to classes, stating that if "a database has a table called orders, our program will have a class named Order."

Now that's just backwards, and the definition of ORM given is actually 100% data-centric design to my small minded overly literal self. Shouldn't it be that ORM relates classes to tables? Domain model and OOD approaches rely on classes first, with persistence to tables second. Otherwise, you're missing out on a large part of OO.

In other words, there's a reason it isn't "Relational/Object Mapping," and it's this error that pervades throughout Rails.

Page 18 ? Wow, that's clean CRUD

On page 18, they show how easy it is to hook up the order table to an Order class. It's like six lines of code to get a row from the table, change a value, and save it back (configuration aside. Yes, there is some, in YAML instead of XML). That's very, very cool, and it makes things really simple and tight.

In Reactor, though, I could do it in a few less:

<cfset order = reactorFactory.createRecord("order") />
<cfset order.load(orderId=1) />
<cfset order.setDiscount(0.5) />
<cfset order.save() />


Page 21-22 ? Installing OSS is Fun!

Installing Rails is a treat.

Windows:

There's a one-click installer that installs Rails, including Gems (Gems is the package manager. I hate package managers. Just give me a .zip of the current version, ok?). Once you're done with the one-click, go run a command line command called "install rails." It sort of does something but you're not really sure what at this point. If you want to update later, run the command line "gem update rails."

OS/X

It's full-blown open-source build time. Have fun with tar, ./configure, make, and make install. Ever seen all of that actually work and have all dependencies in place the first time?

When you're done finding out what you had out of sync, it's time to install gems. Then you can install Rails.

IMHO, don't ever say "one-click" unless you've gotten it as smooth as my most recent MySQL 5.0 experiences on both Windows and my wife's Mac.

Page 28

To create a new web application, you run an executable called "rails," like "rails crappr" to create a Web 2.0 app where users will collaborate, sharing stories of how to eat more fiber (sorry for the vulgarity, it's the only *ppr thing that came to mind). It's kinda harmless, but this is the first of many "we need to ignore the fact that this is already invented" (NIH syndrome) pieces of Rails.

In MG:U, we do the same using something many people already have, and I don't have to deal with learning a new thing: I can type "ant" and slam enter.

Oh, and don't forget to start the Web server when you're done. Now you've put Ruby on Rails.

There's more in Part I, but it's better covered in detail later on in the book

Page 51

Well, we're building Depot, a catalog/shopping cart app, and we've written some Use Cases, wireframed some page flow, and talked with the client about what data will need to be tracked. Very FLiP, and I kinda like FLiP, because it's really common sensical.

Page 55


Configure the application. That's the name of the section Wait, Rails is convention over configuration! Doesn't matter, we still need a text file somewhere, and XML apparently sucks. We'll use YAML, because it stands for "YAML Ain't a Markup Language." Instead, it's a "simple way of storing structured information in flat files."

Used properly, so is XML. I'm not defending some of the XML abominations that have occurred, but...ummm...if you can't read a Spring IoC XML file, you probably aren't going to understand YAML either.

And this "Ain't a Markup Language" - c'mon, you lay out text according to rules to structure key value pairs. It's markup, man.

XML situps? You know what situps do? They keep your abs hard. Which helps when the gut-punch of needing strong separation of configuration from source code kicks in. As we'll see later, Rails is really all about configuration ? it's just added through source code, and spread everywhere. Yes, I said it, and I'll say it again: Rails is really all about configuration. It's just in Ruby, and it's scattered through many, many classes.

Page 55

Time to generate a "scaffold" providing UI for editing the "products" table. Generating code through scripts is ugly. Inline, on-demand code generation such as Reactor is much more elegant.

Oh, and don't forget to restart the web server when you're done. Because we just can't get enough of the command line.

Page 59

Scaffolds are cool. DHH did the right thing in limiting how far they go: no auto-AJAX or JS validation, etc. They're a kick start, not production UI.

Page 62


We need to add a DB column. Rerun the script. Be sure to tell the thing _not_ to overwrite the controller it generated that you may have added custom logic too ? you've got a 50/50 shot of it listening, from what I've heard. If Rails is "smarter than I am" (a popular quote from Rails fanboys), can it just diff the code it wants to generate with the code it's about to replace?

Oh, and don't forget to restart the web server when you're done. Because we just can't get enough of the command line. So much for Don't Repeat Yourself applying everywhere.

Wanna know how fast the MG:U crowd does it? We add the column to the DB. That's it. Litera-fricking-ly.

Page 64

Validation.

What do you mean, validation? We're looking at the DB table for its structure, and they all contain "nullable" (that's "nilable," for those who must change everything used commonly and call it "Ruby") in their column metadata.

I literally almost just stopped bothering when I found out Rails doesn't detect this. If we want to actually force a user to enter some data, we have to manually use the validates_presence_of function inside our class to tell it to require the field.

Not to beat a self-serving drum again, but if you'd like to know how we do it in MG:U, we click the wee little "not null" checkbox in our favorite schema editor (or type NOT NULL in DDL), and Reactor writes its equivalent function into the class for us, because we really don't like repeating ourselves for the simplest of things. That's much more common than allowing for empty strings, which requires about one line of code in Reactor-generated classes.

Same goes for checking type, such as date, time, "numericality" (which isn't even a word, guys, so can we change validates_numericality_of to something logical? ColdFusion has isNumeric, isDate, etc.).

Page 92

The Flash! What could something be to get such a great name?

It's a hash (structure to the ColdFusion crowd) that can be set on one page, is automatically stuck in session, and then cleared at the end of the next request, so that anything you store in it is available on the next request.

It's mainly used to carry things like error messages across redirects.

In MG, if you do a redirect, _everything_ is simply carried to the next request. No need to remember to put it in your user's pocket and get it back out later. Yes, the session mechanics can handle it.

Page 192

I guess numbers just aren't good enough. Rails needs smart numbers. Remember, in Ruby, everything's an object. So let's go absolutely method crazy, adding a method for everything you could ever do to a number to the number object. 20.kilobytes = 20480.

Ok, that's great.

Here's a cuter one: time-based 'scaling methods' convert things to seconds. 1.minute = 60.

1.month = 2592000.

At least, it sometimes does. Rails assumes that a month is 30 days long and that years are 365 days long. So don't ever bother using those.

Just do these have to do with a Web framework to begin with?

Page 193

More I-can-write-more-simple-mathematical-functions than you: this time, the Time class gets all kind of stuff.

Now.yesterday gives you yesterday's date. Ok.

Now.Monday gives you the date of the most recent Monday. I'm not sure why it's here, or why there's not a Now.Sunday, Now.Tuesday. It's like a Rail author needed it once, added it, and committed to trunk for giggles.

Oh, and if we're going to spend man-years writing functions to do the most basic of tasks, let's go all the way. Instead of now.months_ago(2), how about now.2.months_ago? Or does method messaging two levels up not work?

Page 207

This one's just so fouled up I'm going to just quote the book.

"If you need to work with an existing schema, Active Record gives you a simple way of overriding the default name of the primary key for a table."

class BadBook < ActiveRecord::Base
set_primary_key "isbn"
end



This is where it gets fun.

"Normally, Active Record takes care of creating new primary key values for records that you create and add to the database ? they'll be ascending integers (possibly with some gaps in the sequence). However, if you override the primary key column's name, you also take on the responsibility of setting the primary key to a unique value before you save a new rule. (Here comes the pain) Perhaps surprisingly, you still set an attribute called id to do this. As far as Active Record is concerned, the primary key attribute is always set using an attribute called id....(Want more?) Just to make things more confusing, the attributes of the model object have the column names isbn and title ? id doesn't appear. When you need to set the primary key, use id. At all other times, use the actual column name."

Page 208

"Active Record application use generic calls, delegating the details to a set of database-specific adapters."

Yep, that's about the best way to handle what it does. However, if you need to write something very real-world, like an aggregating query that uses case statements to pivot data across columns, you're in the oh-so-fun world of writing SQL.

That's not bad ? it's exactly as expected. It just that Rails' implementation breaks the whole idea of your app automatically working across multiple DB engines, because you write your SQL above the ORM level, not in a way where you extend your data access tier.

Reactor's a smart cookie though ? because it follows some nice design patterns, you can just stick DB-specific versions of the query inside of db-specific classes, and Reactor knows (see, configuration can be nice) which type to use at runtime.

What this really represents, though, is a problem in Active Record's design. It's designed to façade the use of SQL to persist and recall your object model ? SQL should _not_ leak to a higher tier! The data layer "generated" should be extended to include this persistence function.

Page 216

Active Record allows you to use the :joins parameter to the find() method to specify table joins. You'd think it'd be elegant, but joining list_items to products looks like this:

:join => "as li inner join products as pr on li.product_id = pr.id"


Yeah. Write a DB-specific SQL fragment that lives above the level of db-specific code.

Look at the first two words, and you get the idea of how this works.

Reactor again stomps all over Active Record, and in this case especially, makes it look like a child's toy. With Reactor, it'd look like this:

Query.join("listItem", "product")


F'n a. Doug Hughes took the "let's not deal with SQL in our object model for simple stuff" concept seriously.

Notice that with Reactor, you don't repeat yourself in describing the relationship ? it's been described once already in the framework config, so Reactor knows how to join the two already.

However, in Rails, there's a constant pattern of SQL leaking outside of the data (ORM) level of the framework. It shows a serious problem in design and lack of forethought, because it instantly strips the framework of its cross-db-platform capability.

Page 230

When Order has_one Invoice and Invoice belongs_to Order, assigning an Order to an Invoice won't cause any saving to the db, but assigning an Invoice to an Order will immediately cause a write to the db. "This is because the invoices table is the only one that holds information about the relationship."

Ok, I understand the mechanics behind this. I've implemented a few things that are very similar to ActiveRecord. I understand that this setup may be able to save a possibly unnecessary update statement on the invoice table.

However, if you're going to even begin to walk the OO and ORM route, you're accepting that you'll probably sacrifice some milliseconds. Let's not bring in a possibly seriously-wtf-just-happened-this-is-a-pain-to-debug inconsistency just for the sake of the 6ms this crupdate should take.

Seriously, Rails has a bad habit of letting the inner workings of its data layer leak up to the controller / service layer.

Page 231

Ok, something I really like here. Your belongs_to, has_many, etc. statements can take a "conditions" parameter. This means you could create relation properties that are conditional, such as a cart having an out_of_stock_products property by adding

:conditions => "in_stock = 0"


Page 249

Transactions in Rails are both kind of nifty and kind of like having to remember to "var" scope local vars in CFCs (which drives me insane. Yes, I have a few points of serious wrath for ColdFusion, but they mainly focus on various syntax inconsistencies that I forget to meet.).

Basically, transactions in Rails allow you to state that a transaction starts and then roll back any database changes if an exception is thrown, just like CF's
<cftransaction>
tag. What's nice, though, is it'll reverse any changes to the state of specifically identified Active Record instances.

That's one up on CF, which'd rollback the DB transaction but leave your CFCs for you to "unchange." Which is actually really easy in Reactor, or a nice exercise in learning the command pattern.

Page 284

In the footnote, they state "Inches, of course, are also a legacy unit of measure."

Please, Rails folks, don't go thinking all of use Americans want inches to stay. I'd like nothing more for the whole world to wake up one morning using metric, measuring their speed in KpH, driving on the right side of the road..

Page 287

"The Case of the Missing ID"

Here's an illustration of particularly nasty side effect of letting SQL "leak" to tiers above ORM, as well as insisting that there should never be recordsets, but always arrays of objects:

What happens when someone only selects a few columns?

Well, you get an array of objects that aren't fully instantiated, which is something I don't think any ORM should ever allow.

What happens if the primary key isn't part of the select clause?

The query will run, and you'll get an array of objects.

What happens if you change and save an object?

Nothing. Literally. No save. No exception. Nothing.

This should throw an exception, because it's going to be a nightmare to debug. Think about it ? you do this ugly query. You pass the object off to a collaborator. Somewhere in there, it saves the object. Except it doesn't save. Ummm....

Page 290

Now we're out of Active Record (Model) and going into the Action Controller (Controller) layer.

Basically, crappr.com/someController/someAction states that the someAction method of someController handles this request.

Sounds pretty screwed up if you're used to Mach-II or MG. Yep, that's right, no implicit invocation, - just one "action method" call is all you get.

Got some logic you need to spread through multiple requests? Welcome to filters and / or repeating yourself and / or configuration.

Filters are fun. You get to either have the logic happen globally, or write in :except or :only parameters, where you specify, by name, the name of an action method to apply to.

Yeah, uh, you gotta repeat the name of the action.

And I'm sure that'll never change when marketing decides they don't like the URL, and your teammate changes the name of the action, not knowing that there's a filter applied in the controller's base class (not the one they had open) that states :except someAction.

Oh, crap. We've entered the world of "Don't repeat yourself and favor convention over configuration unless you need configuration in which case we'll repeat ourselves and hard-code configuration into our code's nether regions."

Yes, I consider Rails very heavily steeped in configuration. Configuration is much better kept in XML than in Ruby code spread throughout classes, especially an inheritance tree!

Hani says it really well on bileblog: "that moronic f***witty we-separate-concerns-but-still-demand-a-1to1mapping-between-pages-and-code. If my action class is GenitaliaManager, I should be able to have genitaliaUpdate.jsp, genitaliaRemove.jsp, and /genitalia/contribute.jsp if I want, all going off that same class."

Page 291-302

I'm sorry, but I really don't want to read twelve pages to figure out how to construct URLs. If there's a single place where I think the "simple framework for the rest of us" falls apart, it's right about here.

Yes, I know it's all about building URLs that can shift when your application changes as well as human-readable URLs, but that was solved in Fusebox a long time ago with the eXitFuseAction. But that requires configuration. And we're all about "don't repeat yourself and favor convention over configuration unless you need configuration in which case we'll repeat ourselves and hard-code configuration into our code's nether regions."

Page 345 ? The "h" function

Rails is supposed to be about pretty code that reads in a way where you'll just "get it." So why are they recommending to use a function called "h" all over view templates?

It does something obvious, but now's a good time to experience Rails' documentation.

Here's a hint: it's not in Rails documentation. Don't bother. Look in Ruby's documentation. Actually, ERB's documentation (a Ruby templating engine used in Rails). In fact, Google for it. It's easier to find the method's _source code_ than the actual method documentation.

Ok, you don't deserve the pain if you've read this far. If we're all about adding methods to primitive things like strings, h()'s should be completely deprecated and Rails should "improve" the string type with something like this:

some_string_variable.escape_html

Page 357 ? Building a better...input box?

I don't get why all HTML form controls needed Rails methods written to wrap them. Instead of the tag, Rails uses text_field(:variables, :attribute, options), where it's assumed it'll act upon an ActiveRecord instance. From why I can tell, it does this largely to format the name in a way that the controller layer can assemble its data back onto the ActiveRecord instance.

However, MG:U does this just fine, not needing any special name format. I just don't quite get it.

Page 388 ? Building a better script tag???

Everyone know how to use
<script src="foo.js"></script>
. So why do we need:

<%= javascript_include_tag "prototype" %>


What's more, this assumes your source file ends in ".js," and isn't actually the result of a request to something (like, say, Rails!) to generate dynamic .js, and that you know how to configure (yes, configure!) Rails to look in non-default directories if you'd like to share .js between apps.

As an aside CSS works the same blasted way!

Page 374
- Enough with view options!

When you want to display HTML in Rails, you can use a template, a layout, a component, or a partial. Each has their own rules. How about just an include?

Components (where another action's request cycle is invoked ? hope you remembered to make sure it doesn't wrap itself in the overall site template or use the "I'm not really configuration" configuration methods to exclude itself from it in this one-off edge case) are actually kind of nifty, but they only places I can find real uses for them are places solved nicely by view stacking and implicit invocation frameworks.

Page 472

"Performance is not a problem before performance is a problem."

Damn skippy. Don't tune for the sake of tuning. I'd like a lot of people to read this book just to read this section (Finding and Dealing with Bottlenecks).

If you're using a production version of a public Web app framework and your app is slow, odds are that it's not the framework. Don't blame Rails. Monitor your code, especially your SQL, and how many queries you're running: it's easy to hit the N+1 query problem with ORM.

Ok, Conclusion Time

I really do think the Rails' authors "get it" in terms of movitivation. Writing a database-driven web application should be nowhere near as hard at Java/Struts/C#/ASP.NET makes it out to be. I believe the solution is not better developer tooling, such as Visual Studio .NET, but more targeted tools, such as platforms built specifically for browser-based application delivery.

I completely agree with their configuration motivitations: web.xml and the .NET web.config XML files are like hunting flies with an elephant swatter. However, I don't think all XML files are evil: Reactor, Spring, and Model-Glue XML is like reading Dick and Jane instead of the Crime and Punishment that is xml:ns hell.

However, I think Rails has some serious issues in architecture.

On one level, I don't like the way SQL is constantly floating up above the data tier and into your object model. It's both ugly, and leads to some seriously interesting debugging issues. Objects produced via ORM should be atomic, and never allowed to be partially instantiated. ORM platforms should allow the data layer to be self contained. If they aim to be DB-independent, they damn well better let you write DB-specific SQL at a level below your domain model.

On another level, I think the whole "convention over configuration" thing is a crock. The configuration is there. Methods like has_many and belongs_to are configuration: they configure relationships in your object model. Changing column names and table names is configuration (face it, we're not all Web 2.0 startups that can create our own schemas for every app with names that developers like instead of DBA-happy prefixes). The whole concept of routes is configuration. Creation of text fixtures in via the "I'm not a markup language, I'm YAML with a cute recursive acronym" markup languages is configuration (it's also an absolutely fantastic idea). Selective application of filters to action methods is configuration (really, really evil configuration, because it relies on names of methods not changing).

The end result of this is that your configuration isn't stored in a central place. It's part of your source code.

Worse, it's spread throughout your source code. I don't particularly think XML is the greatest thing since sliced bread, but it's better than having the distribution of configuration follow the approximate organizational level of a post-Gallagher watermelon's insides.

Lastly, Rails re-invents wheels that work perfectly fine. My
<script>
tag works fine, and I'm smart enough to both use Prototype and script.aculo.us without wrappers, and to not tie myself to a framework that's going to make it doubly hard to change my mind about which of the 37-million-new-flavors-this-month javascript toolkits I want to use.

Let me end on a positive note, though: I'm glad DHH wrote Rails. Someone needed to stand up and kick the Web development world's arse into realizing that the majority of Web apps don't require the complication that's inherent in J2EE or .NET. It's just not my cup of tea, and I felt it'd be OK to say why.

Holy crap, Floyd's gonna win the Tour. I'm going to go watch OLN.


Tags: Causing Trouble



Read also:
  • ActiveRecord has (bad!) consequences.
  • Rails' ActiveRecord, Reactor, Illudium: All backwords for OO.
  • CFCUnit + FlexUnit How-To: No excuses now!
  • (Parody) My New Web 2.0 App
  • ColdFusion double-checked locking demystified

  • Vote
    What do you need?

    Website design
    Website development
    PHP Tutorials
    Other


    Hire Desk
    »
    Website Design & Development, Php Tutorials
    This is Web developer Blog TheDesignMag
    Only high quality web developming service