ActiveRecord & ARel

18,428 views

Published on

Published in: Technology, Business

ActiveRecord & ARel

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

×