Using arel with rails 2.3.4+

by ewout

Arel, a relational algebra, allows expressing complex relational (SQL) queries in ruby. It is used underneath ActiveRecord 3 for generating queries, where it is underapreciated in my opinion. Arel is extremely powerful and should become a weapon of choice for query manipulation in ruby. Although it is still in beta, it is very stable and usable.

There is one flaw: it depends on ActiveSupport 3 and has a hidden dependency on ActiveRecord 3 for the database drivers. Although the latter will be resolved by releasing the database drivers as a seperate gem, or inside arel itself, the ActiveSupport dependency prevents using arel in earlier versions of rails. Turns out this dependency is really artificial: arel plays nice with rails 2.3.4 and up. I forked the arel project and added some minor modifications so it can be installed and used in these older versions of rails.

Usage

# config/environment.rb
config.gem :arel-compat, :lib => 'arel'

Now to integrate arel on a low-level with the models, add an initializer.

# config/initializers/arel_integration.rb
class ActiveRecord::Base
   class << self
     delegate :[], :to => :arel_table

     def arel_table
       @arel_table ||= Arel::Table.new(table_name, :engine => arel_engine)
     end

     def arel_engine
       # Not correct when working with multiple connections.
       @@arel_engine ||= Arel::Sql::Engine.new(ActiveRecord::Base)
     end
  end
end

After that the fun starts. Note that arel is low level: when executing an arel query, an arel result will be returned instead of model objects. However, it is easy to use the sql returned by arel to select model objects.

arel = Person.arel_table.where(
  Person[:first_name].matches("test%").and(
  Person[:last_name].eq(nil)))
Person.find_by_sql(arel.to_sql)

Known issues

One spec fails on the mysql driver: offset without a limit.

Person.arel_table.skip(10)
=> SELECT `people`.* FROM `people`

When a limit is specified, it will behave correctly.

Person.arel_table.skip(10).take(5)
=> SELECT `people`.* FROM `people` LIMIT 10, 5

Since offset is seldomly used without a limit, we did not bother to patch our arel fork.

Postscript

With the rails ecosystem growing, dependencies become an important issue. A medium sized application can easily depend on 50 gems. Bundler solves the gem resolution problem so an application has a compatible set of gems. However, bundler can only resolve gems whose declared dependencies are compatible. When adding arel to a rails 2.3.4 project, bundler fails.

That is why the other problem with gem dependencies lies with the gem developers themselves. Arel should not depend on ActiveSupport, period. When presented as a “framework to build ORM frameworks”, it should not bring in a massive dependency that is incompatible with some environments.

Fork me on GitHub