Hacker News new | ask | show | jobs
by Locke 3686 days ago
Nostalgia warning: I also grew frustrated with Rails / ActiveRecord and jumped to Merb / DataMapper with excitement. Perhaps it will sound like a small thing, but I remember feeling especially frustrated that every ActiveRecord model became such via inheritance. This drove me crazy:

    class User < ActiveRecord::Base
Why should my `User` extend from `ActiveRecord::Base`? Is a User of my app also an ActiveRecord::Base? What is an ActiveRecord::Base anyway?

So, when DataMapper came along and favored composition over inheritance I was sold.

However, my experience with DataMapper was that it didn't have the stability of ActiveRecord, nor did it reach feature parity.

And, then Rails killed Merb. At the time I thought it was a tragedy. The competition Merb provided did improve Rails, it would have been nice if that competition had been longer term, though. On the flipside, I wonder if there would have been enough community to support two large Ruby web frameworks.

In the end I made peace with Rails and ActiveRecord (and, believe it or not, even ActiveSupport!). I don't have great love for Rails, but it is a powerful tool, so I accept it for what it is.

That said, I think the author's criticisms are well stated and hopefully there will be some influence on the future direction of Rails, etc, and the way devs approach Rails.

5 comments

Having used many different ORMs that let you think that your business models could just gain persistence with a little sprinking of automagic, I far prefer the non-object-orientation of ActiveRecord.

Databases aren't object-oriented. Thinking of them with an is-a, LSP etc. mindset is a recipe for pain down the road. Databases are containers for facts: a means of storing, finding, synthesizing and manipulating facts. Your User class isn't a user of your application; IMO it's wrong to think of it as an object, because it could just be a slice of the User's facts (e.g. a subset of the columns).

I'm not a big fan of object orientation any more, and I like my databases to be stores of facts. The data in the database is primary; code in the application is secondary. Persistence isn't something I add to my benighted objects to let them be reanimated by query; persistence is what lets me briefly get a handy representation of a tuple locally.

To my mind, ActiveRecord models are little more than hashes with a nicer syntax. I don't need them to be much more; I don't want them to be much more. You can try and add methods and make them smaller, but coordinating the manipulation of facts is problematic in a transactional world, and the responsibilities aren't clear either. Object orientation is predicated on the idea of message sends and hidden state; tables have explicit state and no behaviour. Not a good match.

Honestly, I feel like OO has been a net harm to the practice. It is a useful code reuse pattern but I feel like it has gotten far too much play.
Especially true if there are more than 2 codebases interacting with the same database table.
Have you tried Sequel? Sequel has, for me, filled the void left by DataMapper. Most folks I know that have used both ActiveRecord and Sequel vastly prefer Sequel.

http://sequel.jeremyevans.net/

Something to consider for people interested in Sequel. Sequel is great and all, but it still follows the Active Record pattern (Sequel::Model), not Data Mapper. Also expect to run into some problems with gems that interact with ActiveModel (Devise, CarrierWave, etc.). It's all solvable of course but it might require some hacking. Most popular gems have sequel versions or ship with sequel support, but it's not as well tested and maintained, we had to contribute several patches. Also, if you plan on using Sequel with Rails, don't use any of it's plugins that make it behave closer to ActiveRecord. Stuff like nested_attributes, delay_add_association, association_proxies, instance_hooks. They seem really nice at first, but I guarantee they will cause all sorts of unpredictable problems down the road. I would recommend looking into something like Reform which decouples form logic from your models because working with complex forms is going to be harder without all of the AR magic.
Sequel::Model's quite optional - it doesn't force you to use the AR pattern. It's built on top of Sequel::Dataset, which is entirely usable all on its own.

ROM also uses it as its SQL backend: http://rom-rb.org/

It doesn't force you, but you lose all of it's ORM features. Sequel::Dataset is basically just a query builder so ROM would probably be a better choice then yeah.
Is there an authentication gem which works well with Sequel?
I don't get your complaint about "class User < ActiveRecord::Base".

If you don't want to use ActiveRecord::Base methods, why don't you just declare a class without it? You can just do "class User" you know.

As a thought exercise, suppose I have my User class and now I would like to write it as json. Do you think it would be strange if the established solution were this?

    class User < JSON::Base
Where by extending JSON::Base, I now have access to helpful methods like to_json?

Instead we have JSON.dump(user). And, if I need to customize the way the User json is written, I can opt in by defining a method. Thanks to Ruby's open classes, I can make the definition of such methods optional, suppose a separate file includes something like:

    class User
      def as_json
      end
    end
Requiring this file will bring in the extra json behavior without littering my actual user.rb with the details of how a User is written to json. Whether this is worthwhile or a bad idea is debatable, but I like that it is possible.

Now imagine if persisting a model to the database were similarly flexible? What if we could do this?

    ActiveRecord.save(user)
Maybe it would be a good thing, maybe not.

As it is, I've come to accept the ActiveRecord approach, but when I create a model that extends ActiveRecord::Base, I think of it, not as a User, but an ActiveRecordPersistedUser. Where it makes sense, I separate my BusinessModel's from my ActiveRecordPersistedBusinessModel's for clarity.

I don't ruby but I'm guessing OP prefers the "include" that DataMapper uses since inheritance 'feels' weird ... a weak point nevertheless.
`include` is also inheritance, multiple inheritance. I try to avoid inheritance and prefer composition. It is very simple in ruby and very powerful.
The inheritance approach, used in both ActiveRecord and ActionController is sort of like the original sin of Rails. It affects everything from semantics, to performance, to testability. It is often the reason things eventually seem hard, but also the reason many things initially seem easy.
It's kind of amazing that an open source project was able to aqui-hire another open source project. I'm not sure I've seen that before.

I guess if anything it speaks to the level of centralization in Merb development at that time?