ActiveRecord & ARel Awkward friends
ARel is  not ... A replacement for ActiveRecord!
ARel  is ... Active Relation Really neat Still under development The safest way to write SQL SQL engine agnostic! An OOP interpretation of Relational Algebra A DSL Docs? Documentation? Uh, he's sick. Use it! All examples assume PSQL
How to use ARel ARel can be used  independently  of ActiveRecord, but I'm not going to focus on that, because it's not my use case. The easiest way to use ARel, with ActiveRecord, is to access the  #arel_table  of your ActiveRecord models. This method exposes the  Arel::Table  object associated with your model. Through this object you can do many ARel-y things. There are a few other class level methods, which will be covered in a bit.
Accessing attributes Model.arel_table[:attr] Model.arel_table[:attr]. as("alias").to_sql The essential building block for AR & AR interaction. All of the model's attributes are available as keys to the arel_table. "model"."attr" AS alias Notice the result is canonically referenced and properly quoted (very portable)
Predicates Model.arel_table[:id]. eq (1).to_sql Model.arel_table[:id]. not_eq (1).to_sql Model.arel_table[:id]. in ([1,2]).to_sql Model.arel_table[:id]. not_in ([1,2]). to_sql Model.arel_table[:name]. matches (&quot;a%&quot;). to_sql Model.arel_table[:name]. does_not_match (&quot;a%&quot;).to_sql Model.arel_table[:id]. gt (1).to_sql Model.arel_table[:id]. gteq (1).to_sql Model.arel_table[:id]. lt (1).to_sql Model.arel_table[:id]. lteq (1).to_sql &quot;model&quot;.&quot;id&quot;  =  1 &quot;model&quot;.&quot;id&quot;  !=  1 &quot;model&quot;.&quot;id&quot;  IN  (1,2) &quot;model&quot;.&quot;id&quot;  NOT IN  (1,2) &quot;model&quot;.&quot;name&quot;  ILIKE  &quot;a%&quot; &quot;model&quot;.&quot;name&quot;  NOT ILIKE  &quot;a%&quot; &quot;model&quot;.&quot;id&quot;  >  1 &quot;model&quot;.&quot;id&quot;  >=  1 &quot;model&quot;.&quot;id&quot;  <  1 &quot;model&quot;.&quot;id&quot;  <=  1
Predicate alternates Each basic predicate supports two additional types,  _any  and  _all . These methods each accept arrays of values, ie Model.arel_table[:id]. eq_any ([1,2]).to_sql => SELECT &quot;models&quot;.* FROM &quot;models&quot; WHERE (&quot;models&quot;.&quot;id&quot; = 1 OR &quot;models&quot;.&quot;id&quot; = 2) Model.arel_table[:id]. in_all ([[1,2],[3,4]]).to_sql => SELECT &quot;models&quot;.* FROM &quot;models&quot; WHERE (&quot;models&quot;.&quot;id&quot; IN (1,2) AND &quot;models&quot;.&quot;id&quot; IN (3,4))
Aggregate Functions Model.arel_table[:int]. average .to_sql Model.arel_table[:int]. average .as(&quot;alias&quot;).to_sql Model.arel_table[:int]. count .to_sql Model.arel_table[:int]. count .as(&quot;alias&quot;).to_sql Model.arel_table[:int]. maximum .to_sql Model.arel_table[:int]. minimum .to_sql Model.arel_table[:int]. sum .to_sql AVG (&quot;models&quot;.&quot;int&quot;) AS avg_id AVG (&quot;models&quot;.&quot;int&quot;) AS alias COUNT (&quot;models&quot;.&quot;int&quot;) COUNT (&quot;models&quot;.&quot;int&quot;) AS alias MAX (&quot;models&quot;.&quot;int&quot;) AS max_id MIN (&quot;models&quot;.&quot;int&quot;) AS min_id SUM (&quot;models&quot;.&quot;int&quot;) AS sum_id
Infix operators m = Model.arel_table m[:int1] + m[:int2] m[:int1] - m[:int2] m[:int1] * m[:int2] m[:int1] * m[:int2].as &quot;int&quot; m[:int1] / m[:int2] m[:int1] / m[:int2].as &quot;int&quot; Model.select( m[:int1] * m[:int2].     as(&quot;product&quot;)).to_sql No support for bitwise operators or  aliasing addition / subtraction (int1 + int2) (int1 - int2) int1 * int2 int1 * int2 AS int int1 / int2 int1 / int2 AS int SELECT  &quot;models&quot;.&quot;int1&quot; * &quot;models&quot;.&quot;int2&quot; AS product  FROM &quot;models&quot;
Mixing ActiveRecord & ARel Where inconsistencies collide
Selecting results m = Model.arel_table Model.select( [ m[:id], m[:name] ). to_sql Model. select( m[:int1].average.as(&quot;int1&quot; )). to_sql Model.select( m[:int1] + m[:int2] ).to_sql SELECT  &quot;models&quot;.&quot;id&quot;,  &quot;models&quot;.&quot;name&quot; FROM &quot;models&quot; SELECT  AVG(&quot;models&quot;.&quot;int1&quot;) AS int1 FROM &quot;models&quot; SELECT  (&quot;models&quot;.&quot;int1&quot; + &quot;models&quot;.&quot;int2&quot;) FROM &quot;models&quot;
Filtering results (where) m = Model.arel_table Model.where( m[:id].gteq(10) ). to_sql Model.where( m.grouping( m[:id].gt(10).  and(m[:name].matches(&quot;foo%&quot;)). or(m[:int1].in([1,2])) ) Model.where( m[:int1] + m[:int2].gteq(100) ) SELECT &quot;models&quot;.* FROM &quot;models&quot; WHERE  &quot;models&quot;.&quot;id&quot; >= 10 SELECT &quot;models&quot;.* FROM &quot;models&quot; WHERE ( (( &quot;models&quot;.&quot;id&quot; > 10 AND &quot;models&quot;.&quot;name&quot; ILIKE &quot;foo%&quot;) OR &quot;models&quot;.&quot;int1&quot; IN (1,2)) ) SELECT &quot;models&quot;.* FROM &quot;models&quot; WHERE  &quot;models&quot;.&quot;int1&quot; +  &quot;models&quot;.&quot;int2&quot; >= 100
Grouping and Having results ARel Model.select( m[:name] ). group( m[:name] ).to_sql ActiveRecord Model.select( :name ). group( :name ).to_sql The ARel methods are important, because ActiveRecord does not canonically reference fields SELECT  &quot;models&quot;.&quot;name&quot;   FROM &quot;models&quot;  GROUP BY  &quot;models&quot;.&quot;name&quot; SELECT  name FROM &quot;models&quot;  GROUP BY  name
Ordering results m = Model.arel_table m[:id]. asc m[:id]. desc m[:string]. lower ARel Model.order( m[:id].asc ). to_sql ActiveRecord Model.order( :id ).to_sql The ARel methods are important, because ActiveRecord does not canonically reference fields &quot;models&quot;.&quot;id&quot;  ASC &quot;models&quot;.&quot;id&quot;  DESC LOWER (&quot;models&quot;.&quot;string&quot;) SELECT &quot;models&quot;.*  FROM &quot;models&quot;  ORDER BY  &quot;models&quot;.&quot;id&quot; ASC SELECT &quot;models&quot;.*  FROM &quot;models&quot;  ORDER BY  id
Limiting results Model. skip (10).to_sql Model. take (100).to_sql Model. skip (10). take (100). to_sql Take must be the last method in the chain, always. SELECT &quot;models&quot;.* FROM &quot;models&quot; OFFSET  10 SELECT &quot;models&quot;.* FROM &quot;models&quot; LIMIT  100 SELECT &quot;models&quot;.* FROM &quot;models&quot; LIMIT  100 OFFSET  10
Sub Selects sub = Models.joins(:others).        as(&quot;alias&quot;) Models.select('*').from(sub). to_sql SELECT *  FROM (    SELECT &quot;models&quot;.*     FROM &quot;models&quot;       INNER JOIN &quot;others&quot; ON         &quot;others&quot;.&quot;model_id&quot; =            &quot;models&quot;.&quot;id&quot; ) alias
Unions m = Model.arel_table o = Other.arel_table Model.select(m[:id]).union( Other.select(o[:id])).to_sql ( SELECT &quot;models&quot;.&quot;id&quot; FROM &quot;models&quot;  UNION  SELECT &quot;others&quot;.&quot;id&quot; FROM &quot;others&quot; )
Complex joins Polymorphic join m = Model.arel_table o = Other.arel_table sql =  m.joins(o).on(      m[:id].eq(o[:able_id]).      and(o[:able_type].eq(&quot;O&quot;))    ).to_sql Model.joins(sql).to_sql SELECT &quot;models&quot;.* FROM &quot;models&quot;    INNER JOIN &quot;others&quot; ON     (&quot;models&quot;.&quot;id&quot; =          &quot;others&quot;.&quot;able_id&quot;        AND        &quot;others&quot;.&quot;able_type&quot; =           &quot;O&quot;       )
Arbitrary SQL functions m = Model.arel_table Model.select( Arel::Nodes::NamedFunction.new( :coalesce, [m[:id], 0])).to_sql Model.select( Arel::Nodes::NamedFunction.new( :coalesce, [m[:id], 0], &quot;foo&quot;)). to_sql SELECT coalesce(&quot;models&quot;.&quot;id&quot;, 0) FROM &quot;models&quot; SELECT coalesce(&quot;models&quot;.&quot;id&quot;, 0)   AS foo FROM &quot;models&quot;
Easier Arbitrary SQL Functions config/initializers/arel_extensions.rb Arel::Table.class_eval do |base|    def function(name, expr, aliaz = nil)      Arel::Nodes::NamedFunction.new(name, expr, aliaz)    end end m = Model.arel_table Model.select(m.function(:coalesce, [m[:id], 0], &quot;foo&quot;))
Peeking and poking m = Model.arel_table query = Model.   joins(:others).    merge(Other.where(id: 1)).   order(m[:id].desc).    skip(10).    take(100) query. join_sql query. where_sql query. to_sql INNER JOIN &quot;others&quot; ON &quot;models&quot;.&quot;id&quot; = &quot;others&quot;.&quot;model_id&quot; WHERE (&quot;others&quot;.&quot;id&quot; = 1) SELECT &quot;models&quot;.*  FROM &quot;model&quot;   INNER JOIN &quot;others&quot; ON    &quot;models&quot;.&quot;id&quot; =    &quot;others&quot;.&quot;model_id&quot; WHERE (&quot;others&quot;.&quot;id&quot; = 1) ORDER BY &quot;models&quot;.&quot;id&quot; DESC OFFSET 10  LIMIT 100
Links http://magicscalingsprinkles.wordpress.com/2010/01/28/why-i-wrote-arel/ https://github.com/rails/arel http://www.railsdispatch.com/posts/activerelation http://m.onkey.org/active-record-query-interface http://www.bigbinary.com/videos/5-how-arel-works https://github.com/stevecj/arel-presentation-slides/blob/master/one/01_slide.md http://railscasts.com/episodes/215-advanced-queries-in-rails-3

ActiveRecord & ARel

  • 1.
  • 2.
    ARel is not ... A replacement for ActiveRecord!
  • 3.
    ARel is... Active Relation Really neat Still under development The safest way to write SQL SQL engine agnostic! An OOP interpretation of Relational Algebra A DSL Docs? Documentation? Uh, he's sick. Use it! All examples assume PSQL
  • 4.
    How to useARel ARel can be used independently of ActiveRecord, but I'm not going to focus on that, because it's not my use case. The easiest way to use ARel, with ActiveRecord, is to access the #arel_table of your ActiveRecord models. This method exposes the Arel::Table object associated with your model. Through this object you can do many ARel-y things. There are a few other class level methods, which will be covered in a bit.
  • 5.
    Accessing attributes Model.arel_table[:attr]Model.arel_table[:attr]. as(&quot;alias&quot;).to_sql The essential building block for AR & AR interaction. All of the model's attributes are available as keys to the arel_table. &quot;model&quot;.&quot;attr&quot; AS alias Notice the result is canonically referenced and properly quoted (very portable)
  • 6.
    Predicates Model.arel_table[:id]. eq(1).to_sql Model.arel_table[:id]. not_eq (1).to_sql Model.arel_table[:id]. in ([1,2]).to_sql Model.arel_table[:id]. not_in ([1,2]). to_sql Model.arel_table[:name]. matches (&quot;a%&quot;). to_sql Model.arel_table[:name]. does_not_match (&quot;a%&quot;).to_sql Model.arel_table[:id]. gt (1).to_sql Model.arel_table[:id]. gteq (1).to_sql Model.arel_table[:id]. lt (1).to_sql Model.arel_table[:id]. lteq (1).to_sql &quot;model&quot;.&quot;id&quot; = 1 &quot;model&quot;.&quot;id&quot; != 1 &quot;model&quot;.&quot;id&quot; IN (1,2) &quot;model&quot;.&quot;id&quot; NOT IN (1,2) &quot;model&quot;.&quot;name&quot; ILIKE &quot;a%&quot; &quot;model&quot;.&quot;name&quot; NOT ILIKE &quot;a%&quot; &quot;model&quot;.&quot;id&quot; > 1 &quot;model&quot;.&quot;id&quot; >= 1 &quot;model&quot;.&quot;id&quot; < 1 &quot;model&quot;.&quot;id&quot; <= 1
  • 7.
    Predicate alternates Eachbasic predicate supports two additional types, _any and _all . These methods each accept arrays of values, ie Model.arel_table[:id]. eq_any ([1,2]).to_sql => SELECT &quot;models&quot;.* FROM &quot;models&quot; WHERE (&quot;models&quot;.&quot;id&quot; = 1 OR &quot;models&quot;.&quot;id&quot; = 2) Model.arel_table[:id]. in_all ([[1,2],[3,4]]).to_sql => SELECT &quot;models&quot;.* FROM &quot;models&quot; WHERE (&quot;models&quot;.&quot;id&quot; IN (1,2) AND &quot;models&quot;.&quot;id&quot; IN (3,4))
  • 8.
    Aggregate Functions Model.arel_table[:int].average .to_sql Model.arel_table[:int]. average .as(&quot;alias&quot;).to_sql Model.arel_table[:int]. count .to_sql Model.arel_table[:int]. count .as(&quot;alias&quot;).to_sql Model.arel_table[:int]. maximum .to_sql Model.arel_table[:int]. minimum .to_sql Model.arel_table[:int]. sum .to_sql AVG (&quot;models&quot;.&quot;int&quot;) AS avg_id AVG (&quot;models&quot;.&quot;int&quot;) AS alias COUNT (&quot;models&quot;.&quot;int&quot;) COUNT (&quot;models&quot;.&quot;int&quot;) AS alias MAX (&quot;models&quot;.&quot;int&quot;) AS max_id MIN (&quot;models&quot;.&quot;int&quot;) AS min_id SUM (&quot;models&quot;.&quot;int&quot;) AS sum_id
  • 9.
    Infix operators m= Model.arel_table m[:int1] + m[:int2] m[:int1] - m[:int2] m[:int1] * m[:int2] m[:int1] * m[:int2].as &quot;int&quot; m[:int1] / m[:int2] m[:int1] / m[:int2].as &quot;int&quot; Model.select( m[:int1] * m[:int2].     as(&quot;product&quot;)).to_sql No support for bitwise operators or aliasing addition / subtraction (int1 + int2) (int1 - int2) int1 * int2 int1 * int2 AS int int1 / int2 int1 / int2 AS int SELECT &quot;models&quot;.&quot;int1&quot; * &quot;models&quot;.&quot;int2&quot; AS product FROM &quot;models&quot;
  • 10.
    Mixing ActiveRecord &ARel Where inconsistencies collide
  • 11.
    Selecting results m= Model.arel_table Model.select( [ m[:id], m[:name] ). to_sql Model. select( m[:int1].average.as(&quot;int1&quot; )). to_sql Model.select( m[:int1] + m[:int2] ).to_sql SELECT &quot;models&quot;.&quot;id&quot;, &quot;models&quot;.&quot;name&quot; FROM &quot;models&quot; SELECT AVG(&quot;models&quot;.&quot;int1&quot;) AS int1 FROM &quot;models&quot; SELECT (&quot;models&quot;.&quot;int1&quot; + &quot;models&quot;.&quot;int2&quot;) FROM &quot;models&quot;
  • 12.
    Filtering results (where)m = Model.arel_table Model.where( m[:id].gteq(10) ). to_sql Model.where( m.grouping( m[:id].gt(10).  and(m[:name].matches(&quot;foo%&quot;)). or(m[:int1].in([1,2])) ) Model.where( m[:int1] + m[:int2].gteq(100) ) SELECT &quot;models&quot;.* FROM &quot;models&quot; WHERE &quot;models&quot;.&quot;id&quot; >= 10 SELECT &quot;models&quot;.* FROM &quot;models&quot; WHERE ( (( &quot;models&quot;.&quot;id&quot; > 10 AND &quot;models&quot;.&quot;name&quot; ILIKE &quot;foo%&quot;) OR &quot;models&quot;.&quot;int1&quot; IN (1,2)) ) SELECT &quot;models&quot;.* FROM &quot;models&quot; WHERE &quot;models&quot;.&quot;int1&quot; +  &quot;models&quot;.&quot;int2&quot; >= 100
  • 13.
    Grouping and Havingresults ARel Model.select( m[:name] ). group( m[:name] ).to_sql ActiveRecord Model.select( :name ). group( :name ).to_sql The ARel methods are important, because ActiveRecord does not canonically reference fields SELECT &quot;models&quot;.&quot;name&quot;   FROM &quot;models&quot;  GROUP BY &quot;models&quot;.&quot;name&quot; SELECT name FROM &quot;models&quot;  GROUP BY name
  • 14.
    Ordering results m= Model.arel_table m[:id]. asc m[:id]. desc m[:string]. lower ARel Model.order( m[:id].asc ). to_sql ActiveRecord Model.order( :id ).to_sql The ARel methods are important, because ActiveRecord does not canonically reference fields &quot;models&quot;.&quot;id&quot; ASC &quot;models&quot;.&quot;id&quot; DESC LOWER (&quot;models&quot;.&quot;string&quot;) SELECT &quot;models&quot;.* FROM &quot;models&quot; ORDER BY &quot;models&quot;.&quot;id&quot; ASC SELECT &quot;models&quot;.*  FROM &quot;models&quot;  ORDER BY id
  • 15.
    Limiting results Model.skip (10).to_sql Model. take (100).to_sql Model. skip (10). take (100). to_sql Take must be the last method in the chain, always. SELECT &quot;models&quot;.* FROM &quot;models&quot; OFFSET 10 SELECT &quot;models&quot;.* FROM &quot;models&quot; LIMIT 100 SELECT &quot;models&quot;.* FROM &quot;models&quot; LIMIT 100 OFFSET 10
  • 16.
    Sub Selects sub= Models.joins(:others).       as(&quot;alias&quot;) Models.select('*').from(sub). to_sql SELECT *  FROM (   SELECT &quot;models&quot;.*    FROM &quot;models&quot;      INNER JOIN &quot;others&quot; ON        &quot;others&quot;.&quot;model_id&quot; =           &quot;models&quot;.&quot;id&quot; ) alias
  • 17.
    Unions m =Model.arel_table o = Other.arel_table Model.select(m[:id]).union( Other.select(o[:id])).to_sql ( SELECT &quot;models&quot;.&quot;id&quot; FROM &quot;models&quot; UNION SELECT &quot;others&quot;.&quot;id&quot; FROM &quot;others&quot; )
  • 18.
    Complex joins Polymorphicjoin m = Model.arel_table o = Other.arel_table sql =  m.joins(o).on(     m[:id].eq(o[:able_id]).     and(o[:able_type].eq(&quot;O&quot;))   ).to_sql Model.joins(sql).to_sql SELECT &quot;models&quot;.* FROM &quot;models&quot;   INNER JOIN &quot;others&quot; ON     (&quot;models&quot;.&quot;id&quot; =          &quot;others&quot;.&quot;able_id&quot;       AND       &quot;others&quot;.&quot;able_type&quot; =          &quot;O&quot;       )
  • 19.
    Arbitrary SQL functionsm = Model.arel_table Model.select( Arel::Nodes::NamedFunction.new( :coalesce, [m[:id], 0])).to_sql Model.select( Arel::Nodes::NamedFunction.new( :coalesce, [m[:id], 0], &quot;foo&quot;)). to_sql SELECT coalesce(&quot;models&quot;.&quot;id&quot;, 0) FROM &quot;models&quot; SELECT coalesce(&quot;models&quot;.&quot;id&quot;, 0)   AS foo FROM &quot;models&quot;
  • 20.
    Easier Arbitrary SQLFunctions config/initializers/arel_extensions.rb Arel::Table.class_eval do |base|   def function(name, expr, aliaz = nil)     Arel::Nodes::NamedFunction.new(name, expr, aliaz)   end end m = Model.arel_table Model.select(m.function(:coalesce, [m[:id], 0], &quot;foo&quot;))
  • 21.
    Peeking and pokingm = Model.arel_table query = Model.   joins(:others).   merge(Other.where(id: 1)).   order(m[:id].desc).   skip(10).   take(100) query. join_sql query. where_sql query. to_sql INNER JOIN &quot;others&quot; ON &quot;models&quot;.&quot;id&quot; = &quot;others&quot;.&quot;model_id&quot; WHERE (&quot;others&quot;.&quot;id&quot; = 1) SELECT &quot;models&quot;.* FROM &quot;model&quot;   INNER JOIN &quot;others&quot; ON   &quot;models&quot;.&quot;id&quot; =   &quot;others&quot;.&quot;model_id&quot; WHERE (&quot;others&quot;.&quot;id&quot; = 1) ORDER BY &quot;models&quot;.&quot;id&quot; DESC OFFSET 10 LIMIT 100
  • 22.
    Links http://magicscalingsprinkles.wordpress.com/2010/01/28/why-i-wrote-arel/ https://github.com/rails/arelhttp://www.railsdispatch.com/posts/activerelation http://m.onkey.org/active-record-query-interface http://www.bigbinary.com/videos/5-how-arel-works https://github.com/stevecj/arel-presentation-slides/blob/master/one/01_slide.md http://railscasts.com/episodes/215-advanced-queries-in-rails-3