Refactoring ActiveRecord::Base 15 Mar 2007
ActiveRecord (AR) is one of my favorite programming libraries ever created. I am very thankful that “DHH”:http://www.loudthinking.com woke up one day and decided to author it.

Recently there has been talk in a few threads (How I Monkey Patched AR and ActiveRecord Refactoring) on the Rails-Core list which are trying to invite AR contributors, plugin authors and all around AR enthusiasts to think about refactoring AR.

The Good

AR is the best “ORM”:http://en.wikipedia.org/wiki/Object-relational_mapping that I know of and actively use. ActiveRecord::Base is the centralized theme of ActiveRecord. It’s what you subclass when you need to model an actual database table. It’s where you declare your associations (hasone, belongs_to, etc…)_. It connects your application code to your database storage.

The Bad

ActiveRecord::Base dishes off some of the SQL generation tasks to the underlying connection adapter. This means that if you’re using MySQL then the MysqlAdapter is going to be responsible for quoting valute (for instance the methods quote and quotevalue)_.

Unfortunately ActiveRecord::Base remains to keep some functionality it should most likely give up to the connection adapters. This functionality includes any partial SQL that you see in ActiveRecord::Base, in my opinion.

The Ugly

Since ActiveRecord::Base is holding onto some implementation it doesn’t deserve you can’t get a clean interface for extending aspects of SQL generation without overriding and reimplementing methods on ActiveRecord::Base.

The downside to this is that it’s not isolated to even just your adapter. Even more so, it’s not isolated to just your functionality. Why is this bad? It increases the risk that a bug in your code is going to bring down other parts of the system which may or may not be totally unrelated.

Examples of this include how queries are built. The attributeconditions_ method on ActiveRecord::Base is responsible for handling Array, Range and nil objects specifically. If your object isn’t one of those then you’re going to get stuck with an equals = comparison. An example of this is when you do a :find method call with conditions Hash. For example:

SomeModel.find :all, :conditions=>{  :id => [ 1, 2, 3 ], :age=>(20..25) }

The above code is great, but what happens when you want to use database specific features (or features which use different SQL dialect) like one regular expression searching, database functions, subqueries. Or even supporting additional query modifier statements like the WITH clause that MS SQL, Oracle, DB2 and PostgreSQL8.2 offer, or even the infamous ON DUPLICATE KEY UPDATE functionality that MySQL offers?

Plugins - An Existing Solution

One solution is to have everyone release plugins which extend, implement, override or re-implement functionality that AR currently offers. Some folks in the Rails community even like to suggest that people create plugins and then if the plugin becomes popular they’ll consider putting it into core.

While I see the benefit in this (if no one uses the functionality why does it belong into core?) I also see the need to start considering and refactoring AR to support what some would consider cleaner, more extendable code.

I have even gone as far as implementing “ActiveRecord::Extensions”:http://www.continuousthinking.com/tags/arext which is a plugin that is trying to solve a lot of these issues. And I believe it has in some regards.

Why Plugins and Method Overriding Isn’t The Final Answer

Plugins and method overriding (for all of those calls to alias, you know who you are) aren’t the solution for where AR probably should go as Rails starts on its sweep of moving to a 2.0 release.

The problem is that when several plugins have to forcefully override core methods on ActiveRecord::Base to extend the functionality it screams out to me that ActiveRecord could use a little refactoring love. This would allow alot less method overriding and aliasing while still allowing plugin developers to extend AR’s functionality.

“Fowler”:http://www.martinfowler.com talks about “code smells”:http://www.martinfowler.com/bliki/CodeSmell.html and the continuous overriding of core methods seems to indicate one or possibly more.

How Can AR Separate Subjective Desires From Objective Needs

A few simple guidelines to use:

  • Domain specific problems shouldn’t be put into ActiveRecord core.
  • Generic database solutions should be put into ActiveRecord core. ** A public interface should be put onto ActiveRecord::Base if it operates on a given table or model objects ** Implementation details should be hidden inside of an adapter somewhere. (ie: no SQL generation in ActiveRecord::Base)
  • Generic solutions which impose different SQL dialect should be put into adapters or another component which adapters know how to use.

A few simple questions to think when deciding to extend AR with a plugin or introduce new ideas to AR core:

  • Is the functionality provided by more then one database even if the how its done differs?
  • Can this functionality be isolated or encapsulated in a way that reduces the risk of breaking other AR functionality should a bug be found? (ie: are you overriding core ActiveRecord::Base methods and aliasing the original? If so this is a very easy bug to introduce here.)
  • Does this functionality make sense in only your problem domain, or does it makes sense in most or all problem domains?

Moving Forward

I am working on a patch to AR core which removes all SQL code in ActiveRecord::Base and extracts it out into the connections adapters. It will provide an API for creating additional or specific query based modifiers. This will provide things like prefix and suffix modifiers. It will also separate generic query generation (SQL dialect that all databases support) from non-generic query generation.

This functionality already exists in “ActiveRecord::Extensions”:http://www.continuousthinking.com/tags/arext and you can see its usage examples in Ruby Insiders “December Article”:http://www.rubyinside.com/advent2006/17-extendingar.html . The implementation will slightly differ since by patching AR core itself I’ll be able to introduce simpler code.

The ability to create and extend custom better finder support will be introduced in this patch which allow core developers and/or plugin developers to isolate their code changes and to include that support into the adapters of their choice, without overriding AR core methods.

Thoughts?

If you’ve read this whole post then you’re most likely interested in AR and it’s future. What do you think?


blog comments powered by Disqus